├── .gitattributes ├── .rubocop.yml ├── .github ├── CODEOWNERS └── workflows │ ├── delivery.yml │ └── branchcleanup.yml ├── test ├── cookbooks │ └── test │ │ ├── attributes │ │ └── default.rb │ │ ├── recipes │ │ ├── constrained.rb │ │ ├── rubygems_url.rb │ │ ├── noop.rb │ │ ├── kill.rb │ │ ├── default.rb │ │ ├── license.rb │ │ ├── no_service_restart.rb │ │ ├── direct_url.rb │ │ └── gem_server.rb │ │ ├── metadata.rb │ │ └── templates │ │ └── default │ │ └── ufw_chef.erb └── integration │ ├── license │ └── license_spec.rb │ ├── default │ └── default_spec.rb │ └── noop │ └── noop_spec.rb ├── CODE_OF_CONDUCT.md ├── TESTING.md ├── CONTRIBUTING.md ├── .delivery └── project.toml ├── Berksfile ├── .vscode └── extensions.json ├── spec ├── unit │ ├── recipes │ │ └── default_spec.rb │ └── libraries │ │ └── chef_client_updater_helper_spec.rb └── spec_helper.rb ├── libraries ├── matchers.rb └── chef_client_updater_helper.rb ├── .editorconfig ├── Gemfile ├── .gitignore ├── metadata.rb ├── kitchen.dokken.yml ├── recipes └── default.rb ├── chefignore ├── .travis.yml ├── resources └── default.rb ├── attributes └── default.rb ├── kitchen.yml ├── LICENSE ├── CHANGELOG.md ├── README.md └── providers └── default.rb /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetChefVersion: 11 -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @chef-cookbooks/windows-team 2 | -------------------------------------------------------------------------------- /test/cookbooks/test/attributes/default.rb: -------------------------------------------------------------------------------- 1 | default['chef_client_updater']['version'] = '13.10.0' 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Please refer to the Chef Community Code of Conduct at 2 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | Please refer to 2 | 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please refer to 2 | -------------------------------------------------------------------------------- /.delivery/project.toml: -------------------------------------------------------------------------------- 1 | remote_file = "https://raw.githubusercontent.com/chef-cookbooks/community_cookbook_tools/main/delivery/project.toml" 2 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://supermarket.chef.io' 2 | 3 | metadata 4 | 5 | group :integration do 6 | cookbook 'test', path: 'test/cookbooks/test' 7 | end 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "chef-software.chef", 4 | "rebornix.ruby", 5 | "editorconfig.editorconfig" 6 | ] 7 | } -------------------------------------------------------------------------------- /test/cookbooks/test/recipes/constrained.rb: -------------------------------------------------------------------------------- 1 | chef_client_updater 'Install constrained version' do 2 | version '12.22.5' 3 | post_install_action 'exec' 4 | end 5 | -------------------------------------------------------------------------------- /test/integration/license/license_spec.rb: -------------------------------------------------------------------------------- 1 | describe command('chef-client -v') do 2 | target_version = '15' 3 | its('stdout') { should match "^Chef Infra Client: #{target_version}" } 4 | end 5 | -------------------------------------------------------------------------------- /test/cookbooks/test/recipes/rubygems_url.rb: -------------------------------------------------------------------------------- 1 | chef_client_updater 'Install latest Chef' do 2 | version '13.6.0' 3 | rubygems_url 'http://localhost:8808' 4 | post_install_action 'exec' 5 | end 6 | -------------------------------------------------------------------------------- /test/cookbooks/test/recipes/noop.rb: -------------------------------------------------------------------------------- 1 | chef_client_updater "Install Chef #{node['chef_client_updater']['version']}" do 2 | channel 'stable' 3 | version '12.13.37' 4 | post_install_action 'kill' 5 | end 6 | -------------------------------------------------------------------------------- /test/cookbooks/test/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'test' 2 | maintainer 'Chef Software, Inc.' 3 | maintainer_email 'cookbooks@chef.io' 4 | license 'Apache-2.0' 5 | version '1.0.0' 6 | 7 | depends 'chef_client_updater' 8 | -------------------------------------------------------------------------------- /test/cookbooks/test/recipes/kill.rb: -------------------------------------------------------------------------------- 1 | chef_client_updater "Install Chef #{node['chef_client_updater']['version']}" do 2 | channel 'stable' 3 | version node['chef_client_updater']['version'] 4 | post_install_action 'kill' 5 | end 6 | -------------------------------------------------------------------------------- /test/cookbooks/test/templates/default/ufw_chef.erb: -------------------------------------------------------------------------------- 1 | # position 50 2 | ufw allow in proto tcp to any port 22 from any 3 | <% @rubygems_ips.each do |rubygems_ip| %> 4 | <%= "ufw deny in proto tcp to #{rubygems_ip} from any" %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /spec/unit/recipes/default_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'test::default' do 4 | platform 'ubuntu', '16.04' 5 | 6 | it 'converges successfully' do 7 | expect { :chef_run }.to_not raise_error 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/cookbooks/test/recipes/default.rb: -------------------------------------------------------------------------------- 1 | chef_client_updater "Install Chef #{node['chef_client_updater']['version']}" do 2 | channel 'stable' 3 | version node['chef_client_updater']['version'] 4 | post_install_action 'exec' 5 | install_timeout 599 6 | end 7 | -------------------------------------------------------------------------------- /test/cookbooks/test/recipes/license.rb: -------------------------------------------------------------------------------- 1 | node.override['chef_client']['chef_license'] = 'accept' 2 | 3 | chef_client_updater 'Install Chef Infra Client 15' do 4 | channel 'stable' 5 | version '15' 6 | post_install_action 'exec' 7 | install_timeout 599 8 | end 9 | -------------------------------------------------------------------------------- /test/cookbooks/test/recipes/no_service_restart.rb: -------------------------------------------------------------------------------- 1 | chef_client_updater "Install Chef #{node['chef_client_updater']['version']}" do 2 | channel 'stable' 3 | version node['chef_client_updater']['version'] 4 | event_log_service_restart false 5 | post_install_action 'kill' 6 | end 7 | -------------------------------------------------------------------------------- /test/integration/default/default_spec.rb: -------------------------------------------------------------------------------- 1 | describe command('chef-client -v') do 2 | target_version = '13.10.0' 3 | its('stdout') { should match "^Chef: #{target_version}" } 4 | end 5 | 6 | describe command('/opt/chef/embedded/bin/gem -v') do 7 | its('stdout') { should cmp >= '2.6.11' } 8 | end 9 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'chefspec' 2 | require 'chefspec/berkshelf' 3 | 4 | RSpec.configure do |config| 5 | config.color = true # Use color in STDOUT 6 | config.formatter = :documentation # Use the specified formatter 7 | config.log_level = :error # Avoid deprecation notice SPAM 8 | end 9 | -------------------------------------------------------------------------------- /libraries/matchers.rb: -------------------------------------------------------------------------------- 1 | # cookstyle: disable ChefModernize/DefinesChefSpecMatchers 2 | if defined?(ChefSpec) 3 | ChefSpec.define_matcher(:chef_client_updater) 4 | 5 | def update_chef_client_updater(resource_name) 6 | ChefSpec::Matchers::ResourceMatcher.new(:chef_client_updater, :update, resource_name) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /.github/workflows/delivery.yml: -------------------------------------------------------------------------------- 1 | name: delivery 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | delivery: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Check out code 12 | uses: actions/checkout@main 13 | - name: Run Chef Delivery 14 | uses: actionshub/chef-delivery@main 15 | env: 16 | CHEF_LICENSE: accept-no-persist 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root=true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 2 space indentation 12 | indent_style = space 13 | indent_size = 2 14 | 15 | # Avoid issues parsing cookbook files later 16 | charset = utf-8 17 | 18 | # Avoid cookstyle warnings 19 | trim_trailing_whitespace = true 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # TAS50: cookbook will break with the standard Gemfile, will break 2 | # testing with the latest version of kitchen-dokken in chef-dk 3 | source 'https://rubygems.org' 4 | 5 | gem 'community_cookbook_releaser' 6 | gem 'stove' 7 | gem 'test-kitchen' 8 | # testing in this cookbook will current fail with kitchen-dokken 2.x 9 | gem 'berkshelf' 10 | gem 'json' 11 | gem 'kitchen-dokken', '< 2.0' 12 | gem 'kitchen-inspec' 13 | gem 'kitchen-vagrant' 14 | -------------------------------------------------------------------------------- /test/cookbooks/test/recipes/direct_url.rb: -------------------------------------------------------------------------------- 1 | raise 'This test is only for RHEL 6' unless platform_family?('rhel') && node['platform_version'].to_i == 6 2 | 3 | chef_client_updater 'Install latest Chef' do 4 | version '12.19.36' 5 | download_url_override 'https://packages.chef.io/files/stable/chef/12.19.36/el/6/chef-12.19.36-1.el6.x86_64.rpm' 6 | checksum '89e8e6e9aebe95bb31e9514052a8926f61d82067092ca3bc976b0bd223710c81' 7 | post_install_action 'exec' 8 | end 9 | -------------------------------------------------------------------------------- /.github/workflows/branchcleanup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Branch Cleanup 3 | # This workflow is triggered on all closed pull requests. 4 | # However the script does not do anything if a merge was not performed. 5 | "on": 6 | pull_request: 7 | types: [closed] 8 | 9 | env: 10 | NO_BRANCH_DELETED_EXIT_CODE: 0 11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: jessfraz/branch-cleanup-action@master 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | .config 3 | InstalledFiles 4 | lib/bundler/man 5 | pkg 6 | test/tmp 7 | test/version_tmp 8 | tmp 9 | _Store 10 | *~ 11 | *# 12 | .#* 13 | \#*# 14 | *.un~ 15 | *.tmp 16 | *.bk 17 | *.bkup 18 | 19 | # editor temp files 20 | .idea 21 | .*.sw[a-z] 22 | 23 | # ruby/bundler files 24 | .ruby-version 25 | .ruby-gemset 26 | .rvmrc 27 | Gemfile.lock 28 | .bundle 29 | *.gem 30 | coverage 31 | spec/reports 32 | 33 | # YARD / rdoc artifacts 34 | .yardoc 35 | _yardoc 36 | doc/ 37 | rdoc 38 | 39 | # chef infra stuff 40 | Berksfile.lock 41 | .kitchen 42 | kitchen.local.yml 43 | vendor/ 44 | .coverage/ 45 | .zero-knife.rb 46 | Policyfile.lock.json 47 | 48 | # vagrant stuff 49 | .vagrant/ 50 | .vagrant.d/ 51 | -------------------------------------------------------------------------------- /metadata.rb: -------------------------------------------------------------------------------- 1 | name 'chef_client_updater' 2 | maintainer 'Chef Software, Inc.' 3 | maintainer_email 'cookbooks@chef.io' 4 | license 'Apache-2.0' 5 | description 'Upgrades chef-client to specified releases' 6 | version '3.12.2' 7 | 8 | chef_version '>= 11' if respond_to?(:chef_version) # cookstyle: disable ChefModernize/RespondToInMetadata 9 | 10 | %w(amazon centos debian mac_os_x opensuseleap oracle redhat scientific solaris2 suse ubuntu windows aix).each do |os| 11 | supports os 12 | end 13 | 14 | unless defined?(Ridley) # cookstyle: disable ChefModernize/RespondToInMetadata 15 | source_url 'https://github.com/chef-cookbooks/chef_client_updater' if respond_to?(:source_url) # cookstyle: disable ChefModernize/RespondToInMetadata 16 | issues_url 'https://github.com/chef-cookbooks/chef_client_updater/issues' if respond_to?(:issues_url) # cookstyle: disable ChefModernize/RespondToInMetadata 17 | end 18 | -------------------------------------------------------------------------------- /test/integration/noop/noop_spec.rb: -------------------------------------------------------------------------------- 1 | # For the noop suite, currently-installed and target Chef versions are equal. 2 | gem_path = '/opt/chef/embedded/bin/gem' 3 | chef_version = Gem::Version.new(ENV['CHEF_VERSION'] || '12.22.5') 4 | 5 | describe command("#{gem_path} -v") do 6 | # First version of Chef with bundled Rubygems >= 2.0.0. 7 | min_chef_version = '11.18.0' 8 | 9 | min_rubygems_version = '2.0.0' 10 | target_rubygems_version = '2.6.11' 11 | 12 | if Gem::Requirement.new(">= #{min_chef_version}").satisfied_by?(chef_version) 13 | its('stdout') { should cmp >= min_rubygems_version } 14 | else 15 | # Old Chef versions should upgrade old Rubygems to the target version. 16 | its('stdout') { should match target_rubygems_version } 17 | end 18 | end 19 | 20 | describe gem('mixlib-install', gem_path) do 21 | it { should be_installed } 22 | end 23 | 24 | describe gem('mixlib-versioning', gem_path) do 25 | it { should be_installed } 26 | end 27 | -------------------------------------------------------------------------------- /test/cookbooks/test/recipes/gem_server.rb: -------------------------------------------------------------------------------- 1 | # Build a gem server for testing 2 | 3 | rubygems_ips = shell_out('dig +short rubygems.org').stdout.split("\n") 4 | deny_rubygems = rubygems_ips.map do |rubygems_ip| 5 | "ufw deny out from any to #{rubygems_ip}" 6 | end.join("\n") 7 | 8 | bash 'local gem server' do 9 | code <<-EOH 10 | #!/bin/bash -x 11 | pkill -9 gem 12 | apt-get -q -y update 13 | apt-get -q -y install ruby2.3 14 | if [[ -z `/usr/bin/gem list 'mixlib-install'` ]]; then 15 | /usr/bin/gem install 'mixlib-install' 16 | /usr/bin/gem install 'mixlib-versioning' 17 | /usr/bin/gem install 'thor' -v '0.20.0' 18 | fi 19 | /usr/bin/gem server -d /var/lib/gems/2.3.0 2>/dev/null 1>/dev/null ' 16 | chef_omnibus_url: "https://omnitruck.chef.io/install.sh" 17 | data_path: test/fixtures 18 | retry_on_exit_code: 19 | - 213 20 | max_retries: 1 21 | wait_for_retry: 1 22 | client_rb: 23 | exit_status: :enabled 24 | client_fork: false 25 | 26 | verifier: 27 | name: inspec 28 | 29 | platforms: 30 | - name: ubuntu-16.04 31 | driver: 32 | image: dokken/ubuntu-16.04 33 | pid_one_command: /bin/systemd 34 | intermediate_instructions: 35 | - RUN /usr/bin/apt-get update 36 | - RUN /usr/bin/apt-get install sudo cron -y 37 | 38 | suites: 39 | - name: default 40 | run_list: 41 | - recipe[test::default] 42 | - name: license 43 | run_list: 44 | - recipe[test::license] 45 | # Test the upgrade from 14 -> 15 where the license is accepted even if all other tests upgrade 46 | # their original version to 15 47 | provisioner: 48 | product_version: '14' 49 | - name: kill 50 | run_list: 51 | - recipe[test::kill] 52 | - name: direct_url 53 | run_list: 54 | - recipe[test::direct_url] 55 | includes: centos-6 56 | - name: constrained 57 | run_list: 58 | - recipe[test::constrained] 59 | - name: noop 60 | run_list: 61 | - recipe[test::default] 62 | attributes: 63 | chef_client_updater: 64 | version: '<%= ENV["CHEF_VERSION"] || "12.22.5" %>' 65 | -------------------------------------------------------------------------------- /recipes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Author:: Tim Smith () 3 | # Cookbook:: chef_client_updater 4 | # Recipe:: default 5 | # 6 | # Copyright:: 2016-2020, Chef Software, Inc. 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | 20 | chef_client_updater 'update chef-client' do 21 | channel node['chef_client_updater']['channel'] 22 | version node['chef_client_updater']['version'] 23 | prevent_downgrade node['chef_client_updater']['prevent_downgrade'] 24 | post_install_action node['chef_client_updater']['post_install_action'] 25 | download_url_override node['chef_client_updater']['download_url_override'] if node['chef_client_updater']['download_url_override'] 26 | checksum node['chef_client_updater']['checksum'] if node['chef_client_updater']['checksum'] 27 | upgrade_delay node['chef_client_updater']['upgrade_delay'] unless node['chef_client_updater']['upgrade_delay'].nil? 28 | product_name node['chef_client_updater']['product_name'] if node['chef_client_updater']['product_name'] 29 | handle_zip_download_url node['chef_client_updater']['handle_zip_download_url'] if node['chef_client_updater']['handle_zip_download_url'] 30 | event_log_service_restart node['chef_client_updater']['event_log_service_restart'] 31 | rubygems_url node['chef_client_updater']['rubygems_url'] if node['chef_client_updater']['rubygems_url'] 32 | end 33 | -------------------------------------------------------------------------------- /chefignore: -------------------------------------------------------------------------------- 1 | # Put files/directories that should be ignored in this file when uploading 2 | # to a Chef Infra Server or Supermarket. 3 | # Lines that start with '# ' are comments. 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | ehthumbs.db 9 | Icon? 10 | nohup.out 11 | Thumbs.db 12 | .envrc 13 | 14 | # EDITORS # 15 | ########### 16 | .#* 17 | .project 18 | .settings 19 | *_flymake 20 | *_flymake.* 21 | *.bak 22 | *.sw[a-z] 23 | *.tmproj 24 | *~ 25 | \#* 26 | REVISION 27 | TAGS* 28 | tmtags 29 | .vscode 30 | .editorconfig 31 | 32 | ## COMPILED ## 33 | ############## 34 | *.class 35 | *.com 36 | *.dll 37 | *.exe 38 | *.o 39 | *.pyc 40 | *.so 41 | */rdoc/ 42 | a.out 43 | mkmf.log 44 | 45 | # Testing # 46 | ########### 47 | .circleci/* 48 | .codeclimate.yml 49 | .delivery/* 50 | .foodcritic 51 | .kitchen* 52 | .mdlrc 53 | .overcommit.yml 54 | .rspec 55 | .rubocop.yml 56 | .travis.yml 57 | .watchr 58 | .yamllint 59 | azure-pipelines.yml 60 | Dangerfile 61 | examples/* 62 | features/* 63 | Guardfile 64 | kitchen.yml* 65 | mlc_config.json 66 | Procfile 67 | Rakefile 68 | spec/* 69 | test/* 70 | 71 | # SCM # 72 | ####### 73 | .git 74 | .gitattributes 75 | .gitconfig 76 | .github/* 77 | .gitignore 78 | .gitkeep 79 | .gitmodules 80 | .svn 81 | */.bzr/* 82 | */.git 83 | */.hg/* 84 | */.svn/* 85 | 86 | # Berkshelf # 87 | ############# 88 | Berksfile 89 | Berksfile.lock 90 | cookbooks/* 91 | tmp 92 | 93 | # Bundler # 94 | ########### 95 | vendor/* 96 | Gemfile 97 | Gemfile.lock 98 | 99 | # Policyfile # 100 | ############## 101 | Policyfile.rb 102 | Policyfile.lock.json 103 | 104 | # Documentation # 105 | ############# 106 | CODE_OF_CONDUCT* 107 | CONTRIBUTING* 108 | documentation/* 109 | TESTING* 110 | UPGRADING* 111 | 112 | # Vagrant # 113 | ########### 114 | .vagrant 115 | Vagrantfile 116 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # TAS50: THIS COOKBOOK DELIBERATELY DOES NOT USE THE STANDARD TRAVIS.YML FILE 2 | dist: xenial 3 | cache: bundler 4 | 5 | addons: 6 | apt: 7 | sources: 8 | - chef-current-xenial 9 | packages: 10 | - chef-workstation 11 | 12 | branches: 13 | only: 14 | - master 15 | 16 | before_install: 17 | - gem install bundler 18 | - bundle --version 19 | - gem update --system 20 | - gem --version 21 | 22 | env: 23 | - CHEF_LICENSE=accept 24 | 25 | matrix: 26 | include: 27 | - rvm: 2.4.5 28 | services: docker 29 | before_script: 30 | - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) 31 | env: 32 | - CHEF_VERSION=14.12.9 33 | - KITCHEN_YAML=kitchen.dokken.yml 34 | script: 35 | # this tests that an upgrade to 15 succeeds when the license is accepted 36 | - bundle exec kitchen test license-ubuntu-1604 37 | - rvm: 2.4.5 38 | services: docker 39 | sudo: required 40 | before_script: 41 | - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) 42 | env: 43 | - CHEF_VERSION=13.6.0 44 | - KITCHEN_YAML=kitchen.dokken.yml 45 | script: 46 | # this tests a downgrade from latest "13" to latest "12" via partial versions 47 | - bundle exec kitchen test constrained-ubuntu-1604 48 | - rvm: 2.4.5 49 | services: docker 50 | before_script: 51 | - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) 52 | env: 53 | - CHEF_VERSION=14.1.1 54 | - KITCHEN_YAML=kitchen.dokken.yml 55 | script: 56 | - bundle exec kitchen test default-ubuntu-1604 57 | - rvm: 2.4.5 58 | services: docker 59 | before_script: 60 | - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) 61 | env: 62 | - CHEF_VERSION=13.8.5 63 | - KITCHEN_YAML=kitchen.dokken.yml 64 | script: 65 | - bundle exec kitchen test default-ubuntu-1604 66 | - rvm: 2.4.5 67 | services: docker 68 | before_script: 69 | - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) 70 | env: 71 | - CHEF_VERSION=12.21.31 72 | - KITCHEN_YAML=kitchen.dokken.yml 73 | script: 74 | - bundle exec kitchen test default-ubuntu-1604 75 | -------------------------------------------------------------------------------- /resources/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook:: chef_client_updater 3 | # Resource:: updater 4 | # 5 | # Copyright:: 2016-2018, Will Jordan 6 | # Copyright:: 2016-2020, Chef Software Inc. 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | 21 | # NOTE: this cookbook uses Chef-11 backwards compatible syntax to support 22 | # upgrades from Chef 11.x and this pattern should not be copied for any modern 23 | # cookbook. This is a poor example cookbook of how to write Chef. 24 | 25 | provides :chef_client_updater 26 | 27 | actions [:update] 28 | default_action :update 29 | 30 | attribute :channel, kind_of: [String, Symbol], default: :stable 31 | attribute :prevent_downgrade, kind_of: [TrueClass, FalseClass], default: false 32 | attribute :version, kind_of: [String, Symbol], default: :latest 33 | attribute :post_install_action, kind_of: String, default: 'kill' 34 | attribute :exec_command, kind_of: String, default: $PROGRAM_NAME.split(' ').first 35 | attribute :exec_args, kind_of: Array, default: ARGV 36 | attribute :download_url_override, kind_of: String 37 | attribute :checksum, kind_of: String 38 | # If the package takes too long to install, mixlib-shellout will time out. 39 | # This enables users to adjust the timeout. 40 | attribute :install_timeout, kind_of: Integer, default: 600 41 | # If the upgrade is scheduled in the same minute that Chef runs, 42 | # there is a risk the upgrade will not be triggered. 43 | # Lowering upgrade_delay limit is not recommended. 44 | attribute :upgrade_delay, kind_of: Integer, default: 60 45 | attribute :product_name, kind_of: String, default: 'chef' 46 | attribute :rubygems_url, kind_of: String, default: lazy { Chef::Config[:rubygems_url] } 47 | attribute :handle_zip_download_url, kind_of: String, default: 'https://download.sysinternals.com/files/Handle.zip' 48 | attribute :event_log_service_restart, kind_of: [TrueClass, FalseClass], default: true 49 | attribute :install_command_options, kind_of: Hash, default: {} 50 | -------------------------------------------------------------------------------- /attributes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook:: chef_client_updater 3 | # Attributes:: default 4 | # 5 | # Copyright:: 2016-2020, Chef Software Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the 'License'); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an 'AS IS' BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | # stable or current channel 20 | default['chef_client_updater']['channel'] = 'stable' 21 | 22 | # prevent a newer client "updating" to an older client 23 | default['chef_client_updater']['prevent_downgrade'] = false 24 | 25 | # the version to install (ex: '12.12.13') or 'latest' 26 | default['chef_client_updater']['version'] = 'latest' 27 | 28 | # kill the client post install or exec the client post install for non-service based installs 29 | default['chef_client_updater']['post_install_action'] = Chef::Config[:no_fork] ? 'exec' : 'kill' 30 | 31 | # the download URL (for use in an air-gapped environment) 32 | default['chef_client_updater']['download_url_override'] = nil 33 | 34 | # the checksum of the package from "download_url_override" 35 | default['chef_client_updater']['checksum'] = nil 36 | 37 | # Root installation path for chef-client for when a custom path is used. 38 | # Defaults to 'C:/opscode/chef' on Windows and '/opt/chef' for everything else. 39 | default['chef_client_updater']['chef_install_path'] = nil 40 | 41 | # delay for triggering Chef Infra Client upgrade in seconds 42 | default['chef_client_updater']['upgrade_delay'] = nil 43 | 44 | # name of the product to upgrade (chef or chefdk) 45 | default['chef_client_updater']['product_name'] = nil 46 | 47 | # download URL for Sysinternals handle.zip (Windows only) 48 | default['chef_client_updater']['handle_zip_download_url'] = nil 49 | default['chef_client_updater']['handle_exe_path'] = "#{Chef::Config[:file_cache_path]}/handle.exe" 50 | 51 | # The Eventlog service will be restarted immediately prior to cleanup broken chef to release any open file locks. 52 | default['chef_client_updater']['event_log_service_restart'] = true 53 | 54 | # Set to 'accept' or 'accept-no-persist' to accept the license. Provided to client execution 55 | # in a backwards compatible way. Use the same attribute from the chef-client cookbook to 56 | # avoid duplication. 57 | default['chef_client']['chef_license'] = nil 58 | 59 | # Set this to use internal or custom rubygems server. 60 | # Use the same attribute from the chef-client cookbook to avoid duplication. 61 | # Example "http://localhost:8808/" 62 | default['chef_client_updater']['rubygems_url'] = Chef::Config[:rubygems_url] 63 | -------------------------------------------------------------------------------- /kitchen.yml: -------------------------------------------------------------------------------- 1 | driver: 2 | name: vagrant 3 | 4 | provisioner: 5 | name: chef_solo 6 | product_name: chef 7 | product_version: '<%= ENV["CHEF_VERSION"] || "12.13.37" %>' 8 | retry_on_exit_code: 9 | - 213 10 | max_retries: 1 11 | wait_for_retry: 1 12 | solo_rb: 13 | exit_status: :enabled 14 | client_fork: false 15 | log_level: :info 16 | 17 | verifier: 18 | name: inspec 19 | 20 | platforms: 21 | - name: centos-6 22 | - name: centos-7 23 | - name: centos-8 24 | - name: debian-9 25 | - name: debian-10 26 | - name: fedora-latest 27 | - name: freebsd-12 28 | - name: ubuntu-16.04 29 | - name: ubuntu-18.04 30 | - name: ubuntu-20.04 31 | - name: windows-2012r2 32 | driver: 33 | box: tas50/windows_2012r2 34 | customize: 35 | memory: 4096 36 | provisioner: 37 | wait_for_retry: 300 38 | transport: 39 | name: winrm 40 | elevated: true 41 | - name: windows-2016 42 | driver: 43 | box: tas50/windows_2016 44 | customize: 45 | memory: 4096 46 | provisioner: 47 | wait_for_retry: 300 48 | transport: 49 | name: winrm 50 | elevated: true 51 | - name: windows-2019 52 | driver: 53 | box: tas50/windows_2019 54 | customize: 55 | memory: 4096 56 | provisioner: 57 | wait_for_retry: 300 58 | transport: 59 | name: winrm 60 | elevated: true 61 | - name: macos-10.15 62 | driver: 63 | box: tas50/macos_10.15 64 | customize: 65 | memory: 4096 66 | 67 | suites: 68 | - name: default 69 | run_list: 70 | - recipe[test::default] 71 | excludes: 72 | - windows-2012r2 73 | - windows-2016 74 | - windows-2019 75 | - name: license 76 | run_list: 77 | - recipe[test::license] 78 | # Test the upgrade from 14 -> 15 where the license is accepted even if all other tests upgrade 79 | # their original version to 15 80 | provisioner: 81 | product_version: '14' 82 | - name: kill 83 | run_list: 84 | - recipe[test::kill] 85 | verifier: 86 | inspec_tests: 87 | - path: ./test/integration/default/ 88 | - name: no_service_restart 89 | run_list: 90 | - recipe[test::no_service_restart] 91 | verifier: 92 | inspec_tests: 93 | - path: ./test/integration/default/ 94 | - name: direct_url 95 | run_list: 96 | - recipe[test::direct_url] 97 | includes: centos-6 98 | - name: constrained 99 | run_list: 100 | - recipe[test::constrained] 101 | includes: centos-6 102 | - name: noop 103 | run_list: 104 | - recipe[test::noop] 105 | includes: centos-6 106 | - name: rubygems-url 107 | run_list: 108 | - recipe[test::gem_server] 109 | - recipe[test::rubygems_url] 110 | includes: ubuntu-16.04 111 | provisioner: 112 | product_version: 12.13.37 113 | verifier: 114 | inspec_tests: 115 | - path: ./test/integration/default/ 116 | -------------------------------------------------------------------------------- /spec/unit/libraries/chef_client_updater_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../../libraries/chef_client_updater_helper' 3 | 4 | describe ChefClientUpdaterHelper do 5 | let(:provider) { Object.new.extend(ChefClientUpdaterHelper) } 6 | let(:product_name) { 'chef-client' } 7 | let(:channel) { 'stable' } 8 | let(:product_version) { 'latest' } 9 | let(:install_command_options) { { option1: 'value1', option2: 'value2' } } 10 | let(:download_url_override) { 'https://my-url' } 11 | let(:checksum) { 12345 } 12 | let(:resource) do 13 | double('Chef::Resource::ChefClientUpdater', 14 | product_name: product_name, channel: channel, version: product_version, install_command_options: install_command_options, 15 | download_url_override: download_url_override, checksum: checksum) 16 | end 17 | let(:platform) { 'aix' } 18 | let(:platform_version) { '4.5' } 19 | let(:architecture) { 'x86' } 20 | let(:detected_platform) { { platform: 'aix', platform_version: '4.5', architecture: 'x86' } } 21 | let(:mixlib) { double('Mixlib::Install', new: true, detect_platform: detected_platform) } 22 | let(:options) do 23 | { 24 | product_name: product_name, 25 | platform_version_compatibility_mode: true, 26 | platform: platform, 27 | platform_version: platform_version, 28 | architecture: architecture, 29 | channel: channel.to_sym, 30 | product_version: product_version.to_sym, 31 | install_command_options: install_command_options, 32 | } 33 | end 34 | 35 | before do 36 | stub_const('::Mixlib::Install', mixlib) 37 | allow(provider).to receive(:new_resource).and_return(resource) 38 | end 39 | 40 | describe '#mixlib_install' do 41 | before do 42 | allow(provider).to receive(:load_mixlib_install) 43 | allow(Chef::Log).to receive(:debug).and_call_original 44 | allow(Chef::Log).to receive(:debug).with('Platform detected as aix by mixlib_install') 45 | end 46 | 47 | it 'calls add_download_url_options with the expected options Hash' do 48 | expect(provider).to receive(:add_download_url_override_options).with(options) 49 | provider.mixlib_install 50 | end 51 | 52 | it 'calls Mixlib::Install.new with the correct options hash' do 53 | allow(provider).to receive(:add_download_url_override_options).with(options).and_return(options) 54 | expect(Mixlib::Install).to receive(:new).with(options) 55 | provider.mixlib_install 56 | end 57 | end 58 | 59 | describe '#add_download_url_override_options' do 60 | # download_url_override nil default is never false so this is never true unless resource attribute is set to nil explicitly 61 | context 'when download_url_override is false' do 62 | before do 63 | allow(resource).to receive(:download_url_override).and_return(false) 64 | end 65 | 66 | it 'never raises an error' do 67 | expect { provider.add_download_url_override_options(options) }.to_not raise_error 68 | end 69 | 70 | it 'does not call new_resource.checksum' do 71 | expect(resource).to_not receive(:checksum) 72 | provider.add_download_url_override_options(options) 73 | end 74 | 75 | it 'does not add download url override key value pair' do 76 | expect(provider.add_download_url_override_options(options)[:install_command_options].key?(:download_url_override)).to be false 77 | end 78 | end 79 | 80 | context 'when new_resource.download_url_override is not false' do 81 | # checksum nil default is never false so this is never true unless resource.checksum is set to nil explicitly 82 | context 'when new_resource.checksum is false' do 83 | before do 84 | allow(resource).to receive(:checksum).and_return(false) 85 | end 86 | 87 | it 'raises an error that checksum must be set if download_url_override is set' do 88 | expect { provider.add_download_url_override_options(options) }.to raise_error('Using download_url_override in the chef_client_updater resource requires also setting checksum property!') 89 | end 90 | end 91 | 92 | context 'when new_resource.checksum is not false' do 93 | it 'does not raise an error' do 94 | expect { provider.add_download_url_override_options(options) }.not_to raise_error 95 | end 96 | 97 | it 'logs a debug message to Chef log' do 98 | expect(Chef::Log).to receive(:debug).with("Passing download_url_override of #{download_url_override} and checksum of #{checksum} to mixlib_install") 99 | provider.add_download_url_override_options(options) 100 | end 101 | 102 | it 'sets the options variable hash to include the download_url_override and checksum as key value pairs' do 103 | provider.add_download_url_override_options(options) 104 | expect(options[:install_command_options][:download_url_override]).to eq(download_url_override) 105 | expect(options[:install_command_options][:checksum]).to eq(checksum) 106 | end 107 | end 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # chef_client_updater Cookbook CHANGELOG 2 | 3 | This file is used to list changes made in each version of the chef_client_updater cookbook. 4 | 5 | ## 3.12.2 (2021-11-10) 6 | 7 | - Repackage to remove macOS specific files 8 | 9 | ## 3.12.1 (2021-11-04) 10 | 11 | - Fix previously incorrect changelog entry in 3.12.0 12 | 13 | ## 3.12.0 (2021-11-04) 14 | 15 | - Bump version of mixlib-install we install to 3.12 - [@gscho](https://github.com/gscho) 16 | - Enable unified_mode if possible to prevent deprecation warnings [@detjensrobert](https://github.com/detjensrobert/) 17 | 18 | ## 3.11.1 (2020-08-25) 19 | 20 | - Fix license acceptance on non-windows - [@dheerajd-msys](https://github.com/dheerajd-msys) 21 | 22 | ## 3.11.0 (2020-08-12) 23 | 24 | - #209 Fixed Windows PowerShell task reschedule by using Scheduled Task cmdlets available on PowerShell 3+ on Windows 2012+- [@jwdean](https://github.com/jwdean) 25 | - resolved cookstyle error: providers/default.rb:314:3 convention: `Style/RedundantAssignment` 26 | 27 | ## 3.10.1 (2020-05-27) 28 | 29 | - Catch when the windows upgrades fail and make sure we leave the C:\opscode\chef dir - [@teknofire](https://github.com/teknofire) 30 | - Improve logging on Windows - [@teknofire](https://github.com/teknofire) 31 | - More chef-client / chef -> Chef Infra Client - [@tas50](https://github.com/tas50) 32 | - Improve the exit message so users realize it was a success - [@tas50](https://github.com/tas50) 33 | - Expand testing with new platforms - [@tas50](https://github.com/tas50) 34 | 35 | ## 3.10.0 (2020-05-04) 36 | 37 | - Disable if_respond to cop for legacy support - [@tas50](https://github.com/tas50) 38 | - Use source parameter for chef_gem if it exists - [@ramereth](https://github.com/ramereth) 39 | - Include cinc as an allowed product_name - [@ramereth](https://github.com/ramereth) 40 | 41 | ## 3.9.0 (2020-04-14) 42 | 43 | - Remove #to_s conversion of attributes - [@jasonwbarnett](https://github.com/jasonwbarnett) 44 | - More robust eventlog and dependent services restarts on Windows - [@rabidpitbull](https://github.com/rabidpitbull) 45 | 46 | ## 3.8.6 (2020-03-06) 47 | 48 | - Fixed restart eventlog and dependent services - [@sanga1794](https://github.com/sanga1794) 49 | 50 | ## 3.8.5 (2020-03-05) 51 | 52 | - Fix nil:NilClass error - [@dheerajd-msys](https://github.com/dheerajd-msys) 53 | 54 | ## 3.8.4 (2020-02-20) 55 | 56 | -Fix the type for the rubygems_url property 57 | 58 | ## 3.8.3 (2020-02-20) 59 | 60 | - Updated rubygems_url resource property default and removed attribute - [@BrandonTheMandon](https://github.com/BrandonTheMandon) 61 | 62 | ## 3.8.2 (2020-01-14) 63 | 64 | - Remove mandatory parameters for WaitForChefClientOrRescheduleUpgradeTask - [@bdwyertech](https://github.com/bdwyertech) 65 | 66 | ## 3.8.1 (2020-01-14) 67 | 68 | - Necessary changes in PS script to accept the chef-client license while chef client upgrade in Windows. - [@Nimesh-Msys](https://github.com/Nimesh-Msys) 69 | 70 | ## 3.8.0 (2019-12-20) 71 | 72 | - Add install_command_options to enable installing Chef as a scheduled task on Windows when updating - [@gholtiii](https://github.com/gholtiii) 73 | - Fix Chef_upgrade scheduled task to reschedule itself when it fails due to running ruby.exe - [@gholtiii](https://github.com/gholtiii) 74 | 75 | ## 3.7.3 (2019-12-09) 76 | 77 | - Minor Fixes While detecting chef DK version - [@Nimesh-Msys](https://github.com/Nimesh-Msys) 78 | 79 | ## 3.7.2 (2019-11-29) 80 | 81 | - fix windows chef install path - [@phomein](https://github.com/phomein) 82 | 83 | ## 3.7.1 (2019-11-19) 84 | 85 | - Minor fixes while upgrading ChefDK - [@Nimesh-Msys](https://github.com/Nimesh-Msys) 86 | - Replace hardcode value for chef_install_path on Windows with a variable - [@dheerajd-msys](https://github.com/dheerajd-msys) 87 | 88 | ## 3.7.0 (2019-11-18) 89 | 90 | - Added noproxy support for airgapped artifact solutions [@Romascopa](https://github.com/romascopa) 91 | - Fix for using custom rubygem server - [@dheerajd-msys](https://github.com/dheerajd-msys) 92 | - Remove opensuse from the list of platforms we support as the correct platform is opensuseleap - [@tas50](https://github.com/tas50) 93 | - Remove the long_description metadata that is unused - [@tas50](https://github.com/tas50) 94 | - Disable some Cookkstyle cops that would break Chef 12 compatibility - [@tas50](https://github.com/tas50) 95 | 96 | ## 3.6.0 (2019-10-14) 97 | 98 | - Updated provider so that EventLog is properly restarted without error during convergence 99 | - Adding license acceptance support - [@tyler-ball](https://github.com/tyler-ball) 100 | - Fix creation of cron error while licence acceptance - [@NAshwini](https://github.com/NAshwini) 101 | 102 | ## 3.5.3 (2019-06-11) 103 | 104 | - Add event_log_service_restart attribute to fix issue of non chef service restart. - [@NAshwini](https://github.com/NAshwini) 105 | - Use new ChefSpec format - [@tas50](https://github.com/tas50) 106 | - Allow path to handle.exe to be configured - [@stefanwb](https://github.com/stefanwb) 107 | 108 | ## 3.5.2 (2019-01-30) 109 | 110 | - Fix rubygems upgrade logic to prevent breaking older chef-client releases such as Chef 12.X 111 | 112 | ## 3.5.1 (2018-12-22) 113 | 114 | - use --no-document for rubygems 3.0 to prevent upgrade failures - [@lamont-granquist](https://github.com/lamont-granquist) 115 | 116 | ## 3.5.0 (2018-08-31) 117 | 118 | - Only run the cron job on *nix 5 minutes from now 119 | 120 | ## 3.4.2 (2018-08-15) 121 | 122 | - Fix to retrieve current version for angrychef nodes 123 | 124 | ## 3.4.1 (2018-08-02) 125 | 126 | - Allow for a configurable package install timeout. (#130) 127 | 128 | ## 3.4.0 (2018-07-23) 129 | 130 | - Prevent failures on Chef-DK 131 | - rubygems_url: Allow the rubygems source to be specified 132 | - Require mixlib-install 3.11+ for Amazon Linux 2.0 support 133 | - Attempt to move the chef install directory if it still exists before giving up to workaround failures on Windows 2008 134 | 135 | ## 3.3.5 (2018-06-20) 136 | 137 | - Do not attempt EventLog restart on Windows 7 or Windows Server 2008 editions. 138 | 139 | ## 3.3.4 (2018-05-30) 140 | 141 | - Improve install success rate on Windows by destroying existing Chef handles prior to upgrade. 142 | 143 | ## 3.3.3 (2018-04-26) 144 | 145 | - Better support AIX by also restarting the chef-client service via cron on AIX 146 | 147 | ## 3.3.2 (2018-04-11) 148 | 149 | - Improve how we handle updating rubygems: 150 | - Update RubyGems without shelling out 151 | - Don't upgrade to the very latest version unless we have to (due to old current rubygems) 152 | 153 | ## 3.3.1 (2018-04-11) 154 | 155 | - Stop push jobs before upgrading Chef on Windows and then start it back up. This prevents failures to update. 156 | 157 | ## 3.3.0 (2018-04-10) 158 | 159 | - Post action support on Windows 160 | - Prevent failure when the user doesn't use the chef-client cookbook 161 | 162 | ## 3.2.9 (2018-04-05) 163 | 164 | - Don't run cron on Windows 165 | 166 | ## 3.2.8 (2018-04-04) 167 | 168 | - Restart chef-client with cron under sysvinit to ensure that chef-client properly restarts 169 | 170 | ## 3.2.7 (2018-03-29) 171 | 172 | - Update log message to KILL from TERM. We updated the behavior but missed the logging 173 | 174 | ## 3.2.6 (2018-03-16) 175 | 176 | - Added additional logic to decide if a 'kill' or 'exec' should be done post upgrade. If the chef-client isn't running with the supervsior process then we will no longer try to use kill. 177 | 178 | ## 3.2.5 (2018-02-28) 179 | 180 | - Use KILL instead of TERM on Windows since TERM isn't always actually killing the process 181 | - Updated the upgrade_delay value on Windows to be 60 seconds since anything less causes issues 182 | 183 | ## 3.2.4 (2018-02-05) 184 | 185 | - Fix warning '/ST is earlier than current time' on Windows 186 | 187 | ## 3.2.3 (2018-01-25) 188 | 189 | - Make product_name attribute into a real property on the resource 190 | 191 | ## 3.2.2 (2018-01-25) 192 | 193 | - Make upgrade_delay a true resource property 194 | 195 | ## 3.2.1 (2018-01-25) 196 | 197 | - Added new attribute 'upgrade_delay' that defines delay in seconds before upgrading Chef client. 198 | 199 | ## 3.2.0 (2018-01-22) 200 | 201 | - Require mixlib-install 3.9 which includes initial support for proxies and support for Amazon Linux 2.0 202 | - Add additional debug logging for the installation 203 | - If the user provides an X.Y.Z format version don't contact Chef Inc's servers to validate the version 204 | - error out chef run if shell update fails 205 | 206 | ## 3.1.3 (2017-12-20) 207 | 208 | - Support custom paths to chef-client 209 | 210 | ## 3.1.2 (2017-11-22) 211 | 212 | - Windows: Support for downgrading the Chef client 213 | 214 | ## 3.1.1 (2017-11-14) 215 | 216 | - Windows: Bypass Powershell Execution Policy for upgrade scheduled task 217 | 218 | ## 3.1.0 (2017-11-01) 219 | 220 | - Raise if download_url_override is set but checksum isn't 221 | - Require a mixin-install that supports proxies 222 | - Improve how we perform the cleanup of the previous install directory on Windows 223 | - Remove a hardcoded path to the chef-client on Windows 224 | - Improve how we perform the Windows upgrade by using a scheduled task to avoid failures during the upgrade 225 | 226 | ## 3.0.4 (2017-08-17) 227 | 228 | - Fix gem install to actually install mixlib-install 3.3.4 229 | - Fix :latest resulting in chef installing on every run 230 | 231 | ## 3.0.3 (2017-08-10) 232 | 233 | - Add accurate logging for the rubygems upgrade to reflect that we're actually upgrading to the latest release. 234 | - Require mixlib-install 3.3.4 to prevent failures on Windows nodes due to architecture detection failing within mixlib-install 235 | - Add debug logging for the desired version logic 236 | - Improve logging of the version we're upgrading to in situations where the user provides either :latest or a partial version like '12'. Show the version we're upgrading to instead of what the user passed 237 | 238 | ## 3.0.2 (2017-08-08) 239 | 240 | - Improve logging to actually log when the upgrade occurs before we kill or exec the current chef-client run 241 | 242 | ## 3.0.1 (2017-07-14) 243 | 244 | - adding check for gem.cmd on chef-client 13 windows 245 | 246 | ## 3.0.0 (2017-07-14) 247 | 248 | ### Breaking Changes 249 | 250 | - The default post install action for the resource has been switched from exec to kill. We believe that kill is the most likely default that users would expect as this allows a chef-client daemon running under a modern init system to cleanly upgrade. We highly recommend you check the readme and understand the exec and kill functions to determine which makes the most sense for how you run chef-client and how you execute the upgrade. 251 | - The prevent downgrade attribute for the default recipe has been changes from true to false as this is the default for the resource. 252 | 253 | ### Other Changes 254 | 255 | - If chef-client is forked and the user specifies an 'exec' post install action we will now warn and then kill as exec will not worked in a forked run 256 | - Updated the windows task example in the readme to properly set the start time 257 | - Updated the minimum version of mixlib-install to download 3.3.1 which includes many fixes for windows clientsA 258 | - The resource now works around situations where mixlib-install may return an Array of versions 259 | 260 | ## 2.0.3 (2017-06-27) 261 | 262 | - Fix #31 detect centos platform correctly 263 | 264 | ## 2.0.2 (2017-06-22) 265 | 266 | - Fix air-gapped installation regression introduced by support for partial versions 267 | 268 | ## 2.0.1 (2017-06-16) 269 | 270 | - Add information on upgrading Windows nodes and upgrading from Chef 11 to the readme 271 | 272 | ## 2.0.0 (2017-06-15) 273 | 274 | - The custom resource has been converted to a LWRP so that we can support Chef Client updates from chef-client 11.6.2 to current. This also removes the need for the compat_resource cookbook. 275 | - Support for upgrading Windows clients has been added 276 | - A potential infinite loop in the upgrade process has been fixed 277 | - The existing /opt/chef directory will now be cleaned up before the reinstall so leftover files will not carry over during upgrades 278 | - Full Travis testing of the cookbook has been added 279 | 280 | ## 1.1.1 (2017-05-11) 281 | 282 | - Fix the initial load of mixlib-install failing 283 | 284 | ## 1.1.0 (2017-05-10) 285 | 286 | - Add support for download URL overrides via new properties on the resource and attributes for the default recipe. This requires mixlib-install 3.2.1, which we now ensure we install in the updater resource. 287 | - Update the default post_install action in the recipe to match the resource (exec). 288 | - Remove usage of class_eval in the resource since we use compat_resource and class_eval causes issues with some later Chef 12 releases. 289 | - Fix the solaris platform name in the metadata.rb. 290 | - Remove disabling FC016 and FC023 Foodcritic rules as these no longer alert. 291 | - Avoid infinite loops if trying to install the latest chef-client version from any channel. 292 | - Add a true test recipe and remove unused inspec tests 293 | - Add debug logging of the current vs. desired versions to help troubleshooting 294 | - Added a blurb in the readme outlining init system issues surrounding kill and the chef-client starting back up 295 | 296 | ## 1.0.2 (2017-04-07) 297 | 298 | - Fix Chef 13 compatibility by using Kernel.exec not exec 299 | 300 | ## 1.0.1 (2017-04-07) 301 | 302 | - point the URLs at the new project repo 303 | - Add ChefSpec matcher 304 | 305 | ## 1.0.0 306 | 307 | - Initial release of chef_client_updater 308 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chef_client_updater 2 | 3 | ![delivery](https://github.com/chef-cookbooks/chef_client_updater/workflows/delivery/badge.svg) [![Cookbook Version](https://img.shields.io/cookbook/v/chef_client_updater.svg)](https://supermarket.chef.io/cookbooks/chef_client_updater) 4 | 5 | This cookbook updates the Chef Infra Client 6 | 7 | ## Requirements 8 | 9 | ### Platforms 10 | 11 | - All platforms with a Chef Infra Client package on downloads.chef.io 12 | 13 | ### Chef 14 | 15 | - Chef 11.6.2+ 16 | 17 | ## Usage 18 | 19 | This cookbook provides both a custom resource and a default recipe. The default recipe simply uses the custom resource with a set of attributes. You can add chef_client_updater::default to your run list or use the custom resource in a wrapper cookbook. 20 | 21 | ### Init System Caveats 22 | 23 | When Chef Infra Client runs as a service under a system init daemon such as Sys-V or systemd each Chef Infra Client run forks off from the main chef-client process being managed by the init system. For a Chef Infra Client upgrade to occur, the running Chef Infra Client as well as the parent process must be killed, and a new Chef Infra Client must start using the updated binaries. This cookbook handles killing the chef-client process, but your init system must properly handle starting the service back up. For systemd this can be handled via configuration, and the chef-client cookbook 8.1.1 or later handles this by default. This functionality is not available in sys-v (RHEL 6, AIX, and older platforms). 24 | 25 | For systems where the init system will not properly handle starting the service back up automatically (like Sys-V or SRC) this cookbook will attempt to restart the service via a temporary cron job when either of the following conditions are met: 26 | 27 | - node['chef_client']['init_style'] == 'init' 28 | - node['chef_client_updater']['restart_chef_via_cron'] == true 29 | 30 | ### Updating Windows Nodes 31 | 32 | On Windows, a one time scheduled task `Chef_upgrade` is created in combination with a PowerShell-based upgrade script and the downloaded [Handle](https://docs.microsoft.com/en-us/sysinternals/downloads/handle) tool. After the resource stages everything for the upgrade it will terminate the client execution with exit code `213`, then the `Chef_upgrade` scheduled task will execute after `upgrade_delay` seconds (defaults to 60). The scheduled task executes the PowerShell upgrade script, which first determines whether it can successfully stop any existing Chef Infra Client service and associated Ruby processes, since the MSI Installer will fail if there is a running Chef Infra Client service or ruby.exe process. If it cannot it will sleep for one minute and try again up to five times. If after five tries it still hasn't been successful it modifies the `Chef_upgrade` scheduled task to run 10 minutes in the future to try the process again. This eliminates the situation where the scheduled task fails and the Chef Infra Client is no longer configured to run automatically (since the service has been stopped), which in large environments is costly to recover from (requiring reboots of many servers). 33 | 34 | The PowerShell upgrade script then moves the current installation to a `chef.upgrade` staging directory and that clears the way for the newer installer to run. Any existing file handles to the old installation folder are forcibly removed and the Eventlog service will be restarted immediately prior to the new installation to release any open file locks. After installation, a log file from the upgrade can be found at `c:\opscode\chef_upgrade.log` until the next Chef Infra Client run where it will be cleaned up along with the backup folder. Upon successful installation the `Chef_upgrade` scheduled task is deleted. 35 | 36 | On Windows the only supported `post_install_action` is `kill` (the property will be forced to `kill` if given `exec` and platform is Windows) because the Chef Infra Client process must be terminated before the `Chef_upgrade` scheduled task starts in order to release all open file locks by the client, otherwise the upgrade will fail with file-in-use errors. 37 | 38 | Root installation path for chef-client in cookbooks/chef_client_updater/attributes/default.rb when a custom path is used. 39 | `default['chef_client_updater']['chef_install_path'] = 'CHEF_CLIENT_FOLDER_PATH'` 40 | Defaults to 'C:/opscode/chef' on Windows and '/opt/chef' for everything else. 41 | 42 | #### Running Chef Infra Client as a Scheduled Task 43 | 44 | If you run as a scheduled task, then this will work smoothly. The path to the newly installed Chef Infra Client will be the same and the scheduled task will launch it. Part of this resource's job on the next run is to make sure the staging directory with the older client is removed. 45 | 46 | #### Running Chef Infra Client As A Windows Service 47 | 48 | If you run Chef Infra Client as a service, things get a tiny bit more complicated because when the new installer runs, the service is removed. It's recommended you migrate to running Chef Infra Client as a scheduled task as described below. 49 | 50 | #### Migrating from Running Chef Infra Client as a Windows Service to Running as a Scheduled Task During the Upgrade 51 | 52 | If you run Chef Infra Client as a service, but want to upgrade to a version of the client with an MSI unstaller that supports running as a scheduled task (any Chef Infra Client >= 12.18) it is now possible with the `install_command_options` property (added in version 3.8.0 of the chef_client_updater cookbook). This property accepts a Hash of key/value pairs, with `{daemon: 'task'}` being the necessary pair to notify the MSI Installer to install Chef Infra Client as a scheduled task. 53 | 54 | ### Upgrading from Chef Infra Client 11 55 | 56 | Moving from Chef Infra Client 11 has a few challenges when we are dealing with public update sources. Chef Infra Client 11 ships with a very old `cacert.pem`. To work through this, we need to get a more current `cacert.pem` file and point OpenSSL to it. Unfortunately, for this to work consistently on Windows, we'll need to reboot. Chef Infra Client 11 does not have the reboot resource, so this isn't a graceful process. However, on the next Chef run after the reboot, things will be back on track and the upgrade will perform as on other platforms. 57 | 58 | Below is an example of a recipe that can set up Chef Infra Client 11 to work using public update sources. 59 | 60 | ```ruby 61 | if platform_family?('windows') && (Chef::VERSION < '12') 62 | new_cert_file = File.join(ENV['USERPROFILE'], 'cacert.pem') 63 | 64 | remote_file new_cert_file do 65 | source 'https://curl.haxx.se/ca/cacert.pem' 66 | action :create 67 | end 68 | 69 | powershell_script 'restart' do 70 | code <<-EOH 71 | restart-computer -force 72 | EOH 73 | action :nothing 74 | end 75 | 76 | env 'SSL_CERT_FILE' do 77 | value new_cert_file 78 | notifies :run, 'powershell_script[restart]', :immediately 79 | end 80 | end 81 | 82 | chef_client_updater 'Install latest Chef' do 83 | post_install_action 'kill' 84 | end 85 | ``` 86 | 87 | ## Chef EULA 88 | 89 | Set the `node['chef_client']['chef_license']` attribute to `accept` or `accept-no-persist` to accept the Chef EULA 90 | when upgrading to Chef Infra Client 15 or higher. 91 | 92 | ## Resources 93 | 94 | ### chef_client_updater 95 | 96 | Installs the mixlib-install/mixlib-install gems and upgrades the Chef Infra Client. 97 | 98 | #### properties 99 | 100 | - `channel` - The chef channel you fetch the Chef Infra Client from. `stable` contains all officially released Chef Infra Client builds where as `current` contains unreleased builds. Default: `stable` 101 | - `prevent_downgrade` - Don't allow this cookbook to downgrade the Chef Infra Client version. Default: `false` 102 | - `version` - The version of the Chef Infra Client to install. Default `:latest` 103 | - `post_install_action` - After installing the Chef Infra Client what should we do. `exec` to exec the new client or `kill` to kill the client and rely on the init system to start up the new version. On Windows only `kill` is supported. Default: `kill` 104 | - `exec_command` - The chef-client command. default: $PROGRAM_NAME.split(' ').first. You can also enter a custom post-action command. 105 | - `exec_args` - An array of arguments to exec the Chef Infra Client with. Default: `ARGV` 106 | - `download_url_override` - The direct URL for the Chef Infra Client package. 107 | - `checksum` - The SHA-256 checksum of the Chef Infra Client package from the direct URL. 108 | - `install_timeout` - The install timeout for non-windows systems. The default is `600`, slow machines may need to extend this. 109 | - `upgrade_delay` - The delay in seconds before the scheduled task to upgrade Chef Infra Client runs on windows. Default: `60`. Lowering this limit is not recommended. 110 | - `product_name` - The name of the product to upgrade. This can be `chef`, `chefdk`, or `cinc`. Default: `chef` 111 | - `install_command_options` - A Hash of additional options that will be passed to the Mixlib::Install instance responsible for installing the given product_name. To install Chef Infra Client as a scheduled task on windows, one can pass `{daemon: 'task'}`. Default: `{}` 112 | - `rubygems_url` - The location to source rubygems. Default: https://www.rubygems.org 113 | - `handle_zip_download_url` - Url to the Handle zip archive used by Windows. Used to override the default in airgapped environments. Default: https://download.sysinternals.com/files/Handle.zip (Note that you can also override the `default['chef_client_updater']['handle_exe_path']` attribute if you already have that binary somewhere on your system) 114 | - `event_log_service_restart` - Whether the Event Log Service on Windows should be restarted to release any locked files. Default: `true` 115 | - `install_command_options` - Additional parameters to pass to the [mixlib-install script](https://github.com/chef/mixlib-install/tree/main#install-scripts). Default: `{}` 116 | 117 | #### examples 118 | 119 | ```ruby 120 | chef_client_updater 'Install latest' 121 | ``` 122 | 123 | ```ruby 124 | chef_client_updater 'Install latest Chef Infra Client 16.x' do 125 | version '16' 126 | end 127 | ``` 128 | 129 | ```ruby 130 | chef_client_updater 'Install 12.13.36 and kill' do 131 | version '12.13.36' 132 | post_install_action 'kill' 133 | end 134 | ``` 135 | 136 | #### Test Kitchen Testing 137 | 138 | In order to test this cookbook it will be necessary to change the `post_install_action` to `exec` from `kill`. While `kill` is better in most actual production use cases as it terminates the chef-client run along with cleaning up the parent process, the use of `kill` under test kitchen will fail the chef-client run and fail the test-kitchen run. The use of `exec` allows test-kitchen to complete and then re-runs the recipe to validate that the cookbook does not attempt to re-update the chef-client and will succeed with the new chef-client. This, however, means that it is not possible to exactly test the config which will be running in production. The best practice advice for this cookbook will be to ignore common best practices and not worry about that. If you change your production config to use `exec` in order to run what you test in test-kitchen, then you will find sharp edge cases where your production upgrades will hang and/or fail, which testing will not replicate. In order to test you should most likely test upgrades on your full-scale integration environment (not under test-kitchen) before rolling out to production and not use test-kitchen at all. If you think that there's a rule that you must test absolutely everything you run under test-kitchen, you should probably [read this](http://labs.ig.com/code-coverage-100-percent-tragedy) or [this](https://coderanger.net/overtesting/). 139 | 140 | In order to test that your recipes work under the new chef-client codebase, you should simply test your cookbooks against the new version of chef-client that you wish to deploy in "isolation" from the upgrade process. If your recipes all work on the old client, and all work on the new client, and the upgrader works, then the sum of the parts should work as well (and again, if you really deeply care about the edge conditions where that might not work -- then test on real production-like images and not with test-kitchen). 141 | 142 | #### Use of 'exec' in production 143 | 144 | This is highly discouraged since the exec will not clean up the supervising process. You're very likely to see it upgrade successfully and then see the old chef-client process continue to run and fork off copies of the old chef-client to run again. Or for the upgrade process to hang, or for other issues to occur causing failed upgrades. 145 | 146 | You can use 'exec' in production if you are running from cron or some other process manager and firing off single-shot `--no-fork` chef-client processes without using the `--interval` option. This will have the advantage that the new chef-client kicks off immediately after the upgrade giving fast feedback on any failures under the new chef-client. The utility of this approach is most likely is not enough to justify the hassle. 147 | 148 | ## A note about purpose 149 | 150 | While this cookbook supports running on Chef Infra Client versions back to 11/12, the supported behavior of the cookbook is to upgrade those versions to 13/14 or newer. It is not intended that users would maintain old Chef-11/12 versions with this cookbook. The latest released version of Chef Infra Client 12 (12.22.1 or later) is still be supported as a target. Older versions of Chef Infra Client will have their embedded rubygems force upgraded by this cookbook to avoid having to regression test against 5+ years of rubygems bugs and establish a stable basis for the cookbook to use. 151 | 152 | ## License 153 | 154 | ```text 155 | Copyright:: 2016-2020, Chef Software, Inc 156 | 157 | Licensed under the Apache License, Version 2.0 (the "License"); 158 | you may not use this file except in compliance with the License. 159 | You may obtain a copy of the License at 160 | 161 | http://www.apache.org/licenses/LICENSE-2.0 162 | 163 | Unless required by applicable law or agreed to in writing, software 164 | distributed under the License is distributed on an "AS IS" BASIS, 165 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 166 | See the License for the specific language governing permissions and 167 | limitations under the License. 168 | ``` 169 | -------------------------------------------------------------------------------- /providers/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook:: chef_client_updater 3 | # Resource:: updater 4 | # 5 | # Copyright:: 2016-2018, Will Jordan 6 | # Copyright:: 2016-2020, Chef Software Inc. 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | 21 | # NOTE: this cookbook uses Chef-11 backwards compatible syntax to support 22 | # upgrades from Chef 11.x and this pattern should not be copied for any modern 23 | # cookbook. This is a poor example cookbook of how to write Chef Infra code. 24 | 25 | include ::ChefClientUpdaterHelper 26 | 27 | use_inline_resources # cookstyle: disable ChefDeprecations/UseInlineResourcesDefined 28 | 29 | provides :chef_client_updater if respond_to?(:provides) # cookstyle: disable ChefModernize/RespondToProvides 30 | 31 | def load_mixlib_install 32 | gem 'mixlib-install', '~> 3.12' 33 | require 'mixlib/install' 34 | rescue LoadError 35 | Chef::Log.info('mixlib-install gem not found. Installing now') 36 | chef_gem 'mixlib-install' do 37 | version '~> 3.12' 38 | compile_time true if respond_to?(:compile_time) # cookstyle: disable ChefModernize/RespondToCompileTime 39 | if new_resource.rubygems_url 40 | clear_sources true if respond_to?(:clear_sources) 41 | if respond_to?(:source) 42 | source new_resource.rubygems_url 43 | elsif respond_to?(:options) 44 | options "--source #{new_resource.rubygems_url}" 45 | end 46 | end 47 | end 48 | 49 | require 'mixlib/install' 50 | end 51 | 52 | def load_mixlib_versioning 53 | require 'mixlib/versioning' 54 | rescue LoadError 55 | Chef::Log.info('mixlib-versioning gem not found. Installing now') 56 | chef_gem 'mixlib-versioning' do 57 | compile_time true if respond_to?(:compile_time) # cookstyle: disable ChefModernize/RespondToCompileTime 58 | if new_resource.rubygems_url 59 | clear_sources true if respond_to?(:clear_sources) 60 | options "--source #{new_resource.rubygems_url}" if respond_to?(:options) 61 | end 62 | end 63 | 64 | require 'mixlib/versioning' 65 | end 66 | 67 | def update_rubygems 68 | compatible_rubygems_versions = '>= 2.6.11' 69 | target_version = '2.7.8' # should be bumped to latest 2.x, but ruby < 2.3.0 support is necessary 70 | nodoc_rubygems_versions = '>= 3.0' 71 | 72 | rubygems_version = Gem::Version.new(Gem::VERSION) 73 | Chef::Log.debug("Found gem version #{rubygems_version}. Desired version is #{compatible_rubygems_versions}") 74 | return if Gem::Requirement.new(compatible_rubygems_versions).satisfied_by?(rubygems_version) 75 | 76 | # only rubygems >= 1.5.2 supports pinning, and we might be coming from older versions 77 | pin_rubygems_range = '>= 1.5.2' 78 | pin = Gem::Requirement.new(pin_rubygems_range).satisfied_by?(rubygems_version) 79 | 80 | converge_by "upgrade rubygems #{rubygems_version} to #{pin ? target_version : 'latest'}" do 81 | if new_resource.rubygems_url 82 | gem_bin = "#{Gem.bindir}/gem" 83 | if !::File.exist?(gem_bin) && windows? 84 | gem_bin = "#{Gem.bindir}/gem.cmd" # on Chef Infra Client 13+ the rubygem executable is gem.cmd, not gem 85 | end 86 | raise 'cannot find omnibus install' unless ::File.exist?(gem_bin) 87 | source = "--clear-sources --source #{new_resource.rubygems_url}" 88 | if Gem::Requirement.new(nodoc_rubygems_versions).satisfied_by?(rubygems_version) 89 | shell_out!("#{gem_bin} update --system #{target_version} --no-document #{source}") 90 | else 91 | shell_out!("#{gem_bin} update --system #{target_version} --no-rdoc --no-ri #{source}") 92 | end 93 | else 94 | require 'rubygems/commands/update_command' 95 | args = if Gem::Requirement.new(nodoc_rubygems_versions).satisfied_by?(rubygems_version) 96 | ['--no-document', '--system' ] 97 | else 98 | ['--no-rdoc', '--no-ri', '--system' ] 99 | end 100 | args.push(target_version) if pin 101 | Gem::Commands::UpdateCommand.new.invoke(*args) 102 | end 103 | end 104 | end 105 | 106 | def load_prerequisites! 107 | update_rubygems 108 | load_mixlib_install 109 | load_mixlib_versioning 110 | end 111 | 112 | # why would we use this when mixlib-install has a current_version method? 113 | # well mixlib-version parses the manifest JSON, which might not be there. 114 | # ohai handles this better IMO 115 | # @return String with the version details 116 | # @return nil when product is not installed 117 | # 118 | def current_version 119 | case new_resource.product_name 120 | when 'chef', 'angrychef', 'cinc', 'angrycinc' 121 | node['chef_packages']['chef']['version'] 122 | when 'chefdk' 123 | versions = Mixlib::ShellOut.new('chef -v').run_command.stdout # cookstyle: disable ChefModernize/ShellOutHelper 124 | # There is a verbiage change in newer version of Chef Infra 125 | version = versions.match(/(ChefDK Version(.)*:)\s*([\d.]+)/i) || versions.match(/(Chef Development Kit Version(.)*:)\s*([\d.]+)/i) 126 | 127 | return version[-1].to_s.strip if version 128 | end 129 | end 130 | 131 | # the version we WANT TO INSTALL. If the user specifies a version in X.Y.X format 132 | # we use that without looking it up. If :latest or a non-X.Y.Z format version we 133 | # look it up with mixlib-install to determine the latest version matching the request 134 | # @return Mixlib::Versioning::Format::PartialSemVer 135 | def desired_version 136 | if new_resource.version.to_sym == :latest # we need to find what :latest really means 137 | version = Mixlib::Versioning.parse(mixlib_install.available_versions.last) 138 | Chef::Log.debug("User specified version of :latest. Looking up using mixlib-install. Value maps to #{version}.") 139 | elsif new_resource.download_url_override # probably in an air-gapped environment. 140 | version = Mixlib::Versioning.parse(new_resource.version) 141 | Chef::Log.debug("download_url_override specified. Using specified version of #{version}") 142 | elsif new_resource.version.split('.').count == 3 # X.Y.Z version format given 143 | Chef::Log.debug("User specified version of #{new_resource.version}. No need check this against Chef servers.") 144 | version = Mixlib::Versioning.parse(new_resource.version) 145 | else # lookup their shortened version to find the X.Y.Z version 146 | version = Mixlib::Versioning.parse(Array(mixlib_install.artifact_info).first.version) 147 | Chef::Log.debug("User specified version of #{new_resource.version}. Looking up using mixlib-install as this is not X.Y.Z format. Value maps to #{version}.") 148 | end 149 | version 150 | end 151 | 152 | # why wouldn't we use the built in update_available? method in mixlib-install? 153 | # well that would use current_version from mixlib-install and it has no 154 | # concept of preventing downgrades 155 | def update_necessary? 156 | load_mixlib_versioning 157 | cur_version = current_version 158 | des_version = desired_version 159 | if cur_version.nil? 160 | Chef::Log.debug("#{new_resource.product_name} is not installed. Proceeding with installing its #{des_version} version.") 161 | true 162 | else 163 | cur_version = Mixlib::Versioning.parse(current_version) 164 | Chef::Log.debug("The current #{new_resource.product_name} version is #{cur_version} and the desired version is #{des_version}") 165 | necessary = new_resource.prevent_downgrade ? (des_version > cur_version) : (des_version != cur_version) 166 | Chef::Log.debug("A #{chef_infra_product_name} upgrade #{necessary ? 'is' : "isn't"} necessary") 167 | necessary 168 | end 169 | end 170 | 171 | def eval_post_install_action 172 | return unless new_resource.post_install_action == 'exec' 173 | 174 | if Chef::Config[:interval] || Chef::Config[:client_fork] 175 | Chef::Log.warn "post_install_action \"exec\" not supported for #{chef_infra_product_name} running forked -- changing to \"kill\"." 176 | new_resource.post_install_action = 'kill' 177 | end 178 | 179 | return unless windows? 180 | 181 | Chef::Log.warn 'forcing "exec" to "kill" on windows.' 182 | new_resource.post_install_action = 'kill' 183 | end 184 | 185 | def run_post_install_action 186 | # make sure the passed action will actually work 187 | eval_post_install_action 188 | 189 | case new_resource.post_install_action 190 | when 'exec' 191 | if Chef::Config[:local_mode] 192 | Chef::Log.info 'Shutting down local-mode server.' 193 | if Chef::Application.respond_to?(:destroy_server_connectivity) 194 | Chef::Application.destroy_server_connectivity 195 | elsif defined?(Chef::LocalMode) && Chef::LocalMode.respond_to?(:destroy_server_connectivity) 196 | Chef::LocalMode.destroy_server_connectivity 197 | end 198 | end 199 | Chef::Log.warn "Replacing #{chef_infra_client_name} process with upgraded version and re-running." 200 | env = {} 201 | unless node['chef_client']['chef_license'].nil? 202 | env['CHEF_LICENSE'] = node['chef_client']['chef_license'] 203 | end 204 | Kernel.exec(env, new_resource.exec_command, *new_resource.exec_args) 205 | when 'kill' 206 | if Chef::Config[:client_fork] && Process.ppid != 1 && !windows? 207 | Chef::Log.warn "#{chef_infra_product_name} is running forked with a supervisor. Sending KILL to parent process!" 208 | Process.kill('KILL', Process.ppid) 209 | end 210 | Chef::Log.warn "New #{chef_infra_product_name} installed and client process exit is allowed and/or specified. Now forcing #{chef_infra_product_name} to exit. Disregard any failure messages." 211 | exit(213) 212 | else 213 | raise "Unexpected post_install_action behavior: #{new_resource.post_install_action}" 214 | end 215 | end 216 | 217 | def chef_infra_product_name 218 | if defined?(::ChefUtils::Dist::Infra::PRODUCT) 219 | ::ChefUtils::Dist::Infra::PRODUCT 220 | else 221 | 'Chef Infra Client' 222 | end 223 | end 224 | 225 | def chef_infra_short_name 226 | if defined?(::ChefUtils::Dist::Infra::SHORT) 227 | ::ChefUtils::Dist::Infra::SHORT 228 | else 229 | 'chef' 230 | end 231 | end 232 | 233 | def chef_infra_client_name 234 | if defined?(::ChefUtils::Dist::Infra::CLIENT) 235 | ::ChefUtils::Dist::Infra::CLIENT 236 | else 237 | 'chef-client' 238 | end 239 | end 240 | 241 | def legacy_conf_dir_name 242 | if defined?(::ChefUtils::Dist::Org::LEGACY_CONF_DIR) 243 | ::ChefUtils::Dist::Org::LEGACY_CONF_DIR 244 | else 245 | 'opscode' 246 | end 247 | end 248 | 249 | def scheduled_task_name 250 | "#{new_resource.product_name}_upgrade" 251 | end 252 | 253 | def windows_chef_org_dir 254 | ::File.join('C:', legacy_conf_dir_name) 255 | end 256 | 257 | def chef_install_dir 258 | node['chef_client_updater']['chef_install_path'] || (windows? ? ::File.join(windows_chef_org_dir, chef_infra_short_name) : ::File.join('/opt', chef_infra_short_name)) 259 | end 260 | 261 | def windows_upgrade_script 262 | ::File.join(windows_chef_org_dir, "#{new_resource.product_name}_upgrade.ps1") 263 | end 264 | 265 | def chef_backup_dir 266 | "#{chef_install_dir}.upgrade" 267 | end 268 | 269 | def chef_broken_dir 270 | "#{chef_install_dir}.broken" 271 | end 272 | 273 | def chef_upgrade_log 274 | ::File.join(windows_chef_org_dir, "#{new_resource.product_name}_upgrade.log") 275 | end 276 | 277 | def cron_job_name 278 | "#{new_resource.product_name}_client_updater" 279 | end 280 | 281 | # cleanup cruft from *prior* runs 282 | def cleanup 283 | if ::File.exist?(chef_backup_dir) 284 | converge_by("remove #{chef_backup_dir} from previous #{chef_infra_product_name} run") do 285 | FileUtils.rm_rf chef_backup_dir 286 | end 287 | end 288 | if ::File.exist?(chef_upgrade_log) 289 | converge_by("remove #{chef_upgrade_log} from previous #{chef_infra_product_name} run") do 290 | FileUtils.rm_rf chef_upgrade_log 291 | end 292 | end 293 | if ::File.exist?(chef_broken_dir) && new_resource.event_log_service_restart 294 | converge_by("remove #{chef_broken_dir} from previous #{chef_infra_product_name} run") do 295 | event_log_ps_code 296 | FileUtils.rm_rf chef_broken_dir 297 | end 298 | end 299 | # When running under init this cron job is created after an update 300 | cron cron_job_name do 301 | action :delete 302 | end unless platform_family?('windows') 303 | end 304 | 305 | def windows? 306 | platform_family?('windows') 307 | end 308 | 309 | def copy_opt_chef(src, dest) 310 | FileUtils.mkdir dest 311 | FileUtils.cp_r "#{src}/.", dest 312 | rescue 313 | nil 314 | end 315 | 316 | # windows does not like having running open files nuked behind it so we have to move the old file 317 | # out of the way. on both platforms we must clean up the old install to not leave behind any old 318 | # gem files. 319 | # 320 | def move_opt_chef(src, dest) 321 | converge_by("move all files under #{src} to #{dest}") do 322 | FileUtils.rm_rf dest 323 | raise "rm_rf of #{dest} failed" if ::File.exist?(dest) # detect mountpoints that were not deleted 324 | FileUtils.mv src, dest 325 | end 326 | rescue => e 327 | # this handles mountpoints 328 | converge_by("caught #{e}, falling back to copying and removing from #{src} to #{dest}") do 329 | begin 330 | FileUtils.rm_rf dest 331 | rescue 332 | nil 333 | end # mountpoints can throw EBUSY 334 | begin 335 | FileUtils.mkdir dest 336 | rescue 337 | nil 338 | end # mountpoints can throw EBUSY 339 | FileUtils.cp_r Dir.glob("#{src}/*"), dest 340 | FileUtils.rm_rf Dir.glob("#{src}/*") 341 | end 342 | end 343 | 344 | def upgrade_start_time 345 | shifted_time = Time.now + new_resource.upgrade_delay 346 | shifted_time.strftime('%H:%M') 347 | end 348 | 349 | def prepare_windows 350 | copy_opt_chef(chef_install_dir, chef_backup_dir) 351 | 352 | remote_file "#{Chef::Config[:file_cache_path]}/handle.zip" do 353 | source new_resource.handle_zip_download_url 354 | not_if { ::File.file?(node['chef_client_updater']['handle_exe_path']) } 355 | end.run_action(:create) 356 | 357 | Kernel.spawn("c:/windows/system32/schtasks.exe /F /RU SYSTEM /create /sc once /ST \"#{upgrade_start_time}\" /tn #{scheduled_task_name} /tr \"powershell.exe -ExecutionPolicy Bypass #{windows_upgrade_script} 2>&1 > #{chef_upgrade_log}\"") 358 | FileUtils.rm_rf "#{chef_install_dir}/bin/#{chef_infra_client_name}.bat" 359 | end 360 | 361 | def uninstall_ps_code 362 | <<-EOH 363 | function guid_from_regvalue($value) { 364 | $order = 7,6,5,4,3,2,1,0,11,10,9,8,15,14,13,12,17,16,19,18,21,20,23,22,25,24,27,26,29,28,31,30 365 | $dash_pos = 8,13,18,23 366 | 367 | $guid = "" 368 | $order | % { 369 | $letter = $value.Substring($_,1) 370 | $guid = "$guid$letter" 371 | if ($dash_pos -contains $guid.length) {$guid = "$guid-"} 372 | } 373 | return $guid 374 | } 375 | 376 | function installed_remove() { 377 | $installed_product = (get-item HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UpgradeCodes\\C58A706DAFDB80F438DEE2BCD4DCB65C).Property 378 | $msi_guid = guid_from_regvalue $installed_product 379 | 380 | Write-Output "Removing installed product {$msi_guid}" 381 | Start-Process msiexec.exe -Wait -ArgumentList "/x {$msi_guid} /q" 382 | } 383 | 384 | Write-Output "Attempting to uninstall product" 385 | installed_remove 386 | EOH 387 | end 388 | 389 | def wait_for_chef_client_or_reschedule_upgrade_task_function 390 | <<-EOH 391 | Function WaitForChefClientOrRescheduleUpgradeTask { 392 | <# Wait for running #{chef_infra_product_name} to finish up to n times. If it has not finished after maxcount tries, then reschedule the upgrade task inMinutes minutes in the future and exit. 393 | #> 394 | param( 395 | [Parameter(Mandatory=$false)] 396 | [Int]$maxcount = 5, 397 | [Parameter(Mandatory=$false)] 398 | [Int]$inMinutes = 10 399 | ) 400 | 401 | # Try maxcount times waiting for given process (#{chef_infra_client_name}) to finish before rescheduling the upgrade task inMinutes into the future 402 | $count = 0 403 | $status = (Get-WmiObject Win32_Process -Filter "name = 'ruby.exe'" | Select-Object CommandLine | select-string '#{legacy_conf_dir_name}').count 404 | while ($status -gt 0) { 405 | $count++ 406 | if ($count -gt $maxcount) { 407 | Write-Output "#{chef_infra_product_name} cannot be upgraded while in use. Rescheduling the upgrade in $inMinutes minutes..." 408 | RescheduleTask #{scheduled_task_name} $inMinutes 409 | exit 0 410 | } 411 | Write-Output "#{chef_infra_product_name} cannot be upgraded while in use - Attempt $count of $maxcount. Sleeping for 60 seconds and retrying..." 412 | Start-Sleep 60 413 | $status = (Get-WmiObject Win32_Process -Filter "name = 'ruby.exe'" | Select-Object CommandLine | select-string '#{legacy_conf_dir_name}').count 414 | } 415 | } 416 | EOH 417 | end 418 | 419 | def reschedule_task_function 420 | <<-EOH 421 | Function RescheduleTask { 422 | <# Reschedule a named scheduled task the given number of minutes in the future 423 | The named scheduled task is expected to have an existing one-time TimeTrigger (which #{scheduled_task_name} has) 424 | #> 425 | param( 426 | [Parameter(Mandatory=$true)] 427 | [String]$taskName, 428 | [Parameter(Mandatory=$true)] 429 | [Int]$minutes 430 | ) 431 | 432 | $newDateTime = ((Get-Date).AddMinutes($minutes)).ToString("yyyy-MM-ddTHH:mm:ss") 433 | try 434 | { 435 | $task = Get-ScheduledTask -TaskName $taskName -TaskPath '\\' 436 | 437 | # Multiple triggers or types can exist. If the first trigger is not a daily, we'll bail out. 438 | # This could be made more resilient, but the task is ours to not foul up. 439 | if (($task.Triggers[0].ToString() -eq "MSFT_TaskDailyTrigger") -or 440 | ($task.Triggers[0].ToString() -eq "MSFT_TaskTimeTrigger")) { 441 | $newTrigger = $task.Triggers[0].Clone() 442 | $newTrigger.StartBoundary = $newDateTime 443 | $task.Triggers[0].StartBoundary = $newDateTime 444 | } 445 | else { 446 | throw "Error rescheduling $taskname task trigger. Valid trigger not found." 447 | } 448 | # Assure this task will run even if the scheduled time is missed 449 | $newTaskSettings = New-ScheduledTaskSettingsSet -StartWhenAvailable 450 | # Updates the scheduled task with new setting and new trigger. 451 | $task = Set-ScheduledTask -TaskName $taskName -TaskPath '\\' -Settings $newTaskSettings -trigger $task.triggers 452 | } 453 | catch { 454 | $_.Exception.Message 455 | } 456 | } 457 | EOH 458 | end 459 | 460 | def open_handle_functions 461 | <<-EOH 462 | Function Get-OpenHandle { 463 | param( 464 | [Parameter(ValueFromPipelineByPropertyName=$true)] 465 | $Search 466 | ) 467 | $handleOutput = &#{node['chef_client_updater']['handle_exe_path']} -accepteula -nobanner -a -u $Search 468 | $handleOutput | foreach { 469 | if ($_ -match '^(?\\S*)\\s*pid: (?\\d*)\\s*type: (?\\S*)\\s*(?\\S*)\\s*(?\\S*):\\s*(?(\\\\\\\\)|([a-z]:).*)') { 470 | $matches | select @{n="User";e={$_.user}},@{n="Path";e={$_.file}},@{n="Handle";e={$_.handle}},@{n="Type";e={$_.type}},@{n="HandlePid";e={$_.pid}},@{n="Program";e={$_.program}} 471 | } 472 | } 473 | } 474 | 475 | Function Destroy-Handle { 476 | param( 477 | [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 478 | $Handle, 479 | [Parameter(Mandatory=$true,ValueFromPipeline=$true)] 480 | $HandlePid 481 | ) 482 | 483 | $handleOutput = &#{node['chef_client_updater']['handle_exe_path']} -accepteula -nobanner -c $Handle -p $HandlePid -y 484 | ' Destroyed handle {0} from pid {1}' -f $Handle, $HandlePid | echo 485 | } 486 | 487 | Function Destroy-OpenChefHandles { 488 | echo '[*] Destroying open Chef handles.' 489 | Get-OpenHandle -Search '#{legacy_conf_dir_name}' | foreach { 490 | ' [+] Destroying handle that {0} (pid: {1}) has on {2}' -f $_.Program, $_.HandlePid, $_.Path | echo 491 | Destroy-Handle -Handle $_.Handle -HandlePid $_.HandlePid 492 | } 493 | echo '[*] Completed destroying open Chef handles.' 494 | } 495 | EOH 496 | end 497 | 498 | # Restart EventLog Service & its dependent services to release lock of files. 499 | def event_log_ps_code 500 | powershell_script 'EventLog Restart' do 501 | code <<-EOH 502 | $windows_kernel_version = (Get-CimInstance -class Win32_OperatingSystem).Version 503 | if (-Not ($windows_kernel_version.Contains('6.0') -or $windows_kernel_version.Contains('6.1'))) { 504 | # Get Dependent Services for Eventlog that are running 505 | $depsvcsrunning = Get-Service -Name 'EventLog' | Select-Object -ExpandProperty DependentServices | 506 | Where-Object Status -eq 'Running' | Select-Object -ExpandProperty Name 507 | # Attempt to preemptively stop Dependent Services 508 | $depsvcsrunning | ForEach-Object { 509 | Stop-Service -Name "$_" -Force -ErrorAction SilentlyContinue 510 | } 511 | # Stop EventLog Service - First Politely, then Forcibly 512 | try { 513 | Stop-Service -Name 'EventLog' -Force -ErrorAction Stop 514 | } catch { 515 | $process='svchost.exe' 516 | $data = Get-CimInstance Win32_Process -Filter "name = '$process'" | Select-Object ProcessId, CommandLine | Where-Object {$_.CommandLine -Match "LocalServiceNetworkRestricted"} 517 | $data = $data.ProcessId 518 | Stop-Process -Id $data -Force 519 | Start-Sleep -Seconds 10 520 | } 521 | # Restart EventLog Service, if Not AutoStarted 522 | $evtlogstate = Get-Service -Name 'EventLog' 523 | if ($evtlogstate.Status -eq 'Stopped') { 524 | Start-Service -Name 'EventLog' 525 | } 526 | # Restart Dependent Services - if Stopped 527 | $depsvcsrunning | ForEach-Object { 528 | $svcstate = Get-Service -Name "$_" 529 | if ($svcstate.Status -eq 'Stopped') { 530 | Start-Service -Name "$_" -ErrorAction SilentlyContinue 531 | } 532 | } 533 | } 534 | EOH 535 | end 536 | end 537 | 538 | def execute_install_script(install_script) 539 | if windows? 540 | cur_version = current_version 541 | cur_version = Mixlib::Versioning.parse(cur_version) if cur_version 542 | uninstall_if_necessary = if !cur_version.nil? && desired_version < cur_version 543 | uninstall_ps_code 544 | else 545 | '' 546 | end 547 | 548 | post_action = if new_resource.post_install_action == 'exec' 549 | new_resource.exec_command 550 | else 551 | '' 552 | end 553 | 554 | license_provided = node['chef_client']['chef_license'] || '' 555 | enforce_license = if defined?(::ChefUtils::Dist::Org::ENFORCE_LICENSE) 556 | ::ChefUtils::Dist::Org::ENFORCE_LICENSE 557 | elsif new_resource.product_name == 'chef' && desired_version.major >= 15 558 | true 559 | else 560 | false 561 | end 562 | 563 | powershell_script "#{chef_infra_product_name} Upgrade Script" do 564 | code <<-EOH 565 | $command = { 566 | $timestamp = Get-Date 567 | Write-Output "Starting upgrade at $timestamp" 568 | 569 | Get-Service #{chef_infra_client_name} -ErrorAction SilentlyContinue | stop-service 570 | Get-Service push-jobs-client -ErrorAction SilentlyContinue | stop-service 571 | 572 | #{reschedule_task_function} 573 | #{wait_for_chef_client_or_reschedule_upgrade_task_function} 574 | 575 | WaitForChefClientOrRescheduleUpgradeTask 576 | 577 | if (!(Test-Path "#{node['chef_client_updater']['handle_exe_path']}")) { 578 | Add-Type -AssemblyName System.IO.Compression.FileSystem 579 | [System.IO.Compression.ZipFile]::ExtractToDirectory("#{Chef::Config[:file_cache_path]}/handle.zip", "#{Chef::Config[:file_cache_path]}") 580 | } 581 | 582 | #{open_handle_functions} 583 | 584 | if (Test-Path "#{node['chef_client_updater']['handle_exe_path']}") { 585 | Destroy-OpenChefHandles 586 | } 587 | 588 | Remove-Item "#{chef_install_dir}" -Recurse -Force 589 | 590 | if (test-path "#{chef_install_dir}") { 591 | Write-Output "Removing #{chef_install_dir} did not completely succeed." 592 | Write-Output "It is likely now in a bad state, not even usable as a backup." 593 | Write-Output "Attempting to move #{chef_install_dir} to #{chef_broken_dir}" 594 | Move-Item "#{chef_install_dir}" "#{chef_broken_dir}" 595 | } 596 | 597 | if (test-path "#{chef_install_dir}") { 598 | Write-Output "#{chef_install_dir} still exists, upgrade will be aborted. Exiting (3)..." 599 | exit 3 600 | } 601 | 602 | #{uninstall_if_necessary} 603 | 604 | Write-Output "Running product install script..." 605 | try { 606 | #{install_script} 607 | } 608 | catch { 609 | Write-Output "An error occurred while trying to install product" 610 | Write-Output $_ 611 | 612 | # Might need more testing about different ways the installation could fail 613 | Move-Item "#{chef_backup_dir}" "#{chef_install_dir}" 614 | 615 | exit 100 616 | } 617 | Write-Output "Install script finished" 618 | 619 | Remove-Item "#{windows_upgrade_script}" 620 | c:/windows/system32/schtasks.exe /delete /f /tn '#{scheduled_task_name}' 621 | 622 | $enforceLicense = $#{enforce_license.to_s} 623 | 624 | if ($enforceLicense -eq $true) { 625 | 626 | SET CHEF_LICENSE '#{license_provided}' 627 | 628 | #{chef_install_dir}/embedded/bin/ruby.exe -e " 629 | begin 630 | require 'chef/VERSION' 631 | require 'license_acceptance/acceptor' 632 | acceptor = LicenseAcceptance::Acceptor.new(provided: '#{license_provided}') 633 | if acceptor.license_required?('chef', Chef::VERSION) 634 | license_id = acceptor.id_from_mixlib('chef') 635 | acceptor.check_and_persist(license_id, Chef::VERSION) 636 | end 637 | rescue LoadError 638 | puts 'License acceptance might not be needed !' 639 | end 640 | " 641 | } 642 | 643 | #{post_action} 644 | 645 | Get-Service push-jobs-client -ErrorAction SilentlyContinue | start-service 646 | 647 | $timestamp = Get-Date 648 | Write-Output "Finished upgrade at $timestamp" 649 | } 650 | 651 | $http_proxy = $env:http_proxy 652 | $no_proxy = $env:no_proxy 653 | $set_proxy = "`$env:http_proxy=`'$http_proxy`'" 654 | $set_no_proxy = "`$env:no_proxy=`'$no_proxy`'" 655 | 656 | Set-Content -Path "#{windows_upgrade_script}" -Value "$set_proxy", "$set_no_proxy" 657 | Add-Content "#{windows_upgrade_script}" "`n$command" 658 | 659 | EOH 660 | action :nothing 661 | end.run_action(:run) 662 | else 663 | upgrade_command = Mixlib::ShellOut.new(install_script, timeout: new_resource.install_timeout) 664 | upgrade_command.run_command 665 | if upgrade_command.exitstatus != 0 666 | raise "Error updating #{chef_infra_product_name}. exit code: #{upgrade_command.exitstatus}.\nSTDERR: #{upgrade_command.stderr}\nSTDOUT: #{upgrade_command.stdout}" 667 | end 668 | end 669 | end 670 | 671 | def license_acceptance! 672 | if node['chef_client']['chef_license'].nil? 673 | Chef::Log.debug 'No license acceptance configuration found, skipping.' 674 | return 675 | end 676 | 677 | license_acceptance = shell_out("#{chef_install_dir}/bin/chef-apply -e 'exit 0'", timeout: 60, environment: { 'CHEF_LICENSE' => "#{node['chef_client']['chef_license']}" }) 678 | 679 | unless license_acceptance.error? 680 | Chef::Log.debug 'Successfully accepted license.' 681 | return 682 | end 683 | 684 | msg = ['Something went wrong while accepting the license.'] 685 | unless license_acceptance.stdout.empty? 686 | msg << 'STDOUT:' 687 | msg << license_acceptance.stdout 688 | end 689 | unless license_acceptance.stderr.empty? 690 | msg << 'STDERR:' 691 | msg << license_acceptance.stderr 692 | end 693 | Chef::Log.warn msg.join("\n") 694 | end 695 | 696 | action :update do 697 | begin 698 | load_prerequisites! 699 | 700 | if update_necessary? 701 | converge_by "upgrade #{new_resource.product_name} #{current_version} to #{desired_version}" do 702 | # we have to get the script from mixlib-install.. 703 | install_script = mixlib_install.install_command 704 | # ...before we blow mixlib-install away 705 | platform_family?('windows') ? prepare_windows : move_opt_chef(chef_install_dir, chef_backup_dir) 706 | 707 | execute_install_script(install_script) 708 | end 709 | converge_by 'take post install action' do 710 | run_post_install_action 711 | end 712 | else 713 | cleanup 714 | end 715 | rescue SystemExit 716 | # sysvinit won't restart after we exit, potentially use cron to do so 717 | # either trust the chef-client cookbook's init scripts or the users choice 718 | if (node['chef_client'] && node['chef_client']['init_style'] == 'init') || node['chef_client_updater']['restart_chef_via_cron'] 719 | Chef::Log.warn "#{chef_infra_product_name} was upgraded, scheduling #{chef_infra_product_name} start via cron in 5 minutes" 720 | cron_time = Time.now + 300 721 | start_cmd = if platform_family?('aix') 722 | '/usr/bin/startsrc -s chef > /dev/console 2>&1' 723 | else 724 | "/etc/init.d/#{chef_infra_client_name} start" 725 | end 726 | 727 | license_acceptance! 728 | 729 | r = cron cron_job_name do 730 | hour cron_time.hour 731 | minute cron_time.min 732 | command start_cmd 733 | end 734 | 735 | r.run_action(:create) 736 | end 737 | 738 | raise 739 | rescue Exception => e # rubocop:disable Lint/RescueException 740 | if ::File.exist?(chef_backup_dir) 741 | Chef::Log.warn "#{chef_infra_product_name.upcase} UPGRADE ABORTED due to #{e}: rolling back to #{chef_backup_dir} copy" 742 | move_opt_chef(chef_backup_dir, chef_install_dir) unless platform_family?('windows') 743 | else 744 | Chef::Log.warn "NO #{chef_backup_dir} DIR TO ROLL BACK TO!" 745 | end 746 | raise 747 | end 748 | end 749 | --------------------------------------------------------------------------------