diff options
39 files changed, 691 insertions, 283 deletions
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 945fa0b503..9ea4b32227 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: ruby-version: 2.7 bundler-cache: true - uses: r7kamura/rubocop-problem-matchers-action@v1 # this shows the failures in the PR - - run: bundle exec rake style + - run: bundle exec chefstyle -c .rubocop.yml spellcheck: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff3a30e87..a2590274ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,31 @@ <!-- usage documentation: http://expeditor-docs.es.chef.io/configuration/changelog/ --> This changelog lists individual merged pull requests to Chef Infra Client and geared towards developers. For a list of significant changes per release see the [Chef Infra Client Release Notes](https://docs.chef.io/release_notes_client/). -<!-- latest_release 17.2.12 --> -## [v17.2.12](https://github.com/chef/chef/tree/v17.2.12) (2021-05-27) +<!-- latest_release 17.2.25 --> +## [v17.2.25](https://github.com/chef/chef/tree/v17.2.25) (2021-06-07) #### Merged Pull Requests -- Bump inspec-core-bin to 4.37.20 [#11632](https://github.com/chef/chef/pull/11632) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot])) +- Add ed25519 gem back to the omnibus install [#11664](https://github.com/chef/chef/pull/11664) ([tas50](https://github.com/tas50)) <!-- latest_release --> <!-- release_rollup since=17.1.35 --> ### Changes not yet released to stable #### Merged Pull Requests +- Add ed25519 gem back to the omnibus install [#11664](https://github.com/chef/chef/pull/11664) ([tas50](https://github.com/tas50)) <!-- 17.2.25 --> +- Cleanup the zypper_repository resource + support multiple GPG Keys [#11660](https://github.com/chef/chef/pull/11660) ([tas50](https://github.com/tas50)) <!-- 17.2.24 --> +- Cleanup windows_printer_port and allow updating the port [#11662](https://github.com/chef/chef/pull/11662) ([tas50](https://github.com/tas50)) <!-- 17.2.23 --> +- windows_printer: use powershell_exec to delete the printer instead of slower/double logging powershell_script [#11663](https://github.com/chef/chef/pull/11663) ([tas50](https://github.com/tas50)) <!-- 17.2.22 --> +- Updated the Windows Pagefile resource to use PowerShell over WMI, added a corresponding test file [#11636](https://github.com/chef/chef/pull/11636) ([johnmccrae](https://github.com/johnmccrae)) <!-- 17.2.21 --> +- Bump inspec-core-bin to 4.37.23 [#11655](https://github.com/chef/chef/pull/11655) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot])) <!-- 17.2.20 --> +- Add tests to verify knife command load & execution [#11653](https://github.com/chef/chef/pull/11653) ([marcparadise](https://github.com/marcparadise)) <!-- 17.2.19 --> +- Fix incorrect require_relative causing failures in `knife org user add` [#11649](https://github.com/chef/chef/pull/11649) ([marcparadise](https://github.com/marcparadise)) <!-- 17.2.18 --> +- Fix Chef::Handler specs and slow report behavior [#11648](https://github.com/chef/chef/pull/11648) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 17.2.17 --> +- Support recipes that and in .yaml as well as .yml [#11629](https://github.com/chef/chef/pull/11629) ([marcparadise](https://github.com/marcparadise)) <!-- 17.2.16 --> +- Make sure we load ohai in knife configure correctly [#11647](https://github.com/chef/chef/pull/11647) ([tas50](https://github.com/tas50)) <!-- 17.2.15 --> +- Bump all deps to the latest [#11643](https://github.com/chef/chef/pull/11643) ([tas50](https://github.com/tas50)) <!-- 17.2.14 --> +- Add a slow resources report to chef-client [#11642](https://github.com/chef/chef/pull/11642) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 17.2.13 --> +- Bump chef/chefstyle to 082bbaf73d76000724f8d8ae3ba7f89c9123ad3f [#11635](https://github.com/chef/chef/pull/11635) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot])) <!-- 17.2.12 --> - Bump inspec-core-bin to 4.37.20 [#11632](https://github.com/chef/chef/pull/11632) ([chef-expeditor[bot]](https://github.com/chef-expeditor[bot])) <!-- 17.2.12 --> - Bump the knife ffi dep [#11618](https://github.com/chef/chef/pull/11618) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 17.2.11 --> - inspec_waiver_file_entry: Autoload yaml and use dist file [#11552](https://github.com/chef/chef/pull/11552) ([tas50](https://github.com/tas50)) <!-- 17.2.10 --> @@ -22,6 +22,7 @@ group(:omnibus_package) do gem "rb-readline" gem "inspec-core-bin", "~> 4.24" # need to provide the binaries for inspec gem "chef-vault" + gem "ed25519", "~> 1.2" # to make it possible to install knife into chef. Remove this in Chef 18 end group(:omnibus_package, :pry) do diff --git a/Gemfile.lock b/Gemfile.lock index 5e1c46f696..87bf08f0ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,10 @@ GIT remote: https://github.com/chef/chefstyle.git - revision: aabbd0cd0b6ad646e55370277ee5af4eefed148a + revision: b257fe452318540c7016066beb845e0706917cb4 branch: master specs: - chefstyle (2.0.3) - rubocop (= 1.14.0) + chefstyle (2.0.4) + rubocop (= 1.15.0) GIT remote: https://github.com/chef/ohai.git @@ -35,10 +35,10 @@ GIT PATH remote: . specs: - chef (17.2.12) + chef (17.2.25) addressable - chef-config (= 17.2.12) - chef-utils (= 17.2.12) + chef-config (= 17.2.25) + chef-utils (= 17.2.25) chef-vault chef-zero (>= 14.0.11) diff-lcs (>= 1.2.4, < 1.4.0) @@ -62,10 +62,10 @@ PATH train-core (~> 3.2, >= 3.2.28) train-winrm (>= 0.2.5) uuidtools (>= 2.1.5, < 3.0) - chef (17.2.12-universal-mingw32) + chef (17.2.25-universal-mingw32) addressable - chef-config (= 17.2.12) - chef-utils (= 17.2.12) + chef-config (= 17.2.25) + chef-utils (= 17.2.25) chef-vault chef-zero (>= 14.0.11) diff-lcs (>= 1.2.4, < 1.4.0) @@ -104,15 +104,15 @@ PATH PATH remote: chef-bin specs: - chef-bin (17.2.12) - chef (= 17.2.12) + chef-bin (17.2.25) + chef (= 17.2.25) PATH remote: chef-config specs: - chef-config (17.2.12) + chef-config (17.2.25) addressable - chef-utils (= 17.2.12) + chef-utils (= 17.2.25) fuzzyurl mixlib-config (>= 2.2.12, < 4.0) mixlib-shellout (>= 2.0, < 4.0) @@ -121,7 +121,7 @@ PATH PATH remote: chef-utils specs: - chef-utils (17.2.12) + chef-utils (17.2.25) concurrent-ruby GEM @@ -152,11 +152,12 @@ GEM chef-zero (>= 14.0) net-ssh coderay (1.1.3) - concurrent-ruby (1.1.8) + concurrent-ruby (1.1.9) crack (0.4.5) rexml debug_inspector (1.1.0) diff-lcs (1.3) + ed25519 (1.2.4) erubi (1.10.0) erubis (2.7.0) faraday (1.4.2) @@ -194,7 +195,7 @@ GEM hashie (4.1.0) httpclient (2.8.3) iniparse (1.5.0) - inspec-core (4.37.20) + inspec-core (4.37.23) addressable (~> 2.4) chef-telemetry (~> 1.0, >= 1.0.8) faraday (>= 0.9.0, < 1.5) @@ -217,8 +218,8 @@ GEM train-core (~> 3.0) tty-prompt (~> 0.17) tty-table (~> 0.10) - inspec-core-bin (4.37.20) - inspec-core (= 4.37.20) + inspec-core-bin (4.37.23) + inspec-core (= 4.37.23) ipaddress (0.8.3) iso8601 (0.13.0) json (2.5.1) @@ -294,7 +295,7 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) rspec-support (3.10.2) - rubocop (1.14.0) + rubocop (1.15.0) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) @@ -303,7 +304,7 @@ GEM rubocop-ast (>= 1.5.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.6.0) + rubocop-ast (1.7.0) parser (>= 3.0.1.1) ruby-progressbar (1.11.0) ruby2_keywords (0.0.4) @@ -414,6 +415,7 @@ DEPENDENCIES chef-vault cheffish (>= 17) chefstyle! + ed25519 (~> 1.2) fauxhai-ng inspec-core-bin (~> 4.24) ohai! @@ -426,4 +428,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.2.4 + 2.2.19 @@ -1 +1 @@ -17.2.12
\ No newline at end of file +17.2.25
\ No newline at end of file diff --git a/chef-bin/lib/chef-bin/version.rb b/chef-bin/lib/chef-bin/version.rb index 1b68b3d488..aa1d8b99e3 100644 --- a/chef-bin/lib/chef-bin/version.rb +++ b/chef-bin/lib/chef-bin/version.rb @@ -21,7 +21,7 @@ module ChefBin CHEFBIN_ROOT = File.expand_path("..", __dir__) - VERSION = "17.2.12".freeze + VERSION = "17.2.25".freeze end # diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb index 3d73607b3b..b676cdffd1 100644 --- a/chef-config/lib/chef-config/version.rb +++ b/chef-config/lib/chef-config/version.rb @@ -15,5 +15,5 @@ module ChefConfig CHEFCONFIG_ROOT = File.expand_path("..", __dir__) - VERSION = "17.2.12".freeze + VERSION = "17.2.25".freeze end diff --git a/chef-utils/lib/chef-utils/version.rb b/chef-utils/lib/chef-utils/version.rb index aa1d0c4e80..e423f26bb7 100644 --- a/chef-utils/lib/chef-utils/version.rb +++ b/chef-utils/lib/chef-utils/version.rb @@ -16,5 +16,5 @@ module ChefUtils CHEFUTILS_ROOT = File.expand_path("..", __dir__) - VERSION = "17.2.12" + VERSION = "17.2.25" end diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/_windows_printer.rb b/kitchen-tests/cookbooks/end_to_end/recipes/_windows_printer.rb new file mode 100644 index 0000000000..9b7f5c91d8 --- /dev/null +++ b/kitchen-tests/cookbooks/end_to_end/recipes/_windows_printer.rb @@ -0,0 +1,24 @@ +# +# Cookbook:: end_to_end +# Recipe:: _windows_printer +# +# Copyright:: Copyright (c) Chef Software Inc. +# + +windows_printer_port "10.4.64.39" do + port_name "My awesome port" + snmp_enabled true + port_protocol 2 +end + +# change the port above +windows_printer_port "10.4.64.39" do + port_name "My awesome port" + snmp_enabled false + port_protocol 2 +end + +# delete a port that doesn't exist +windows_printer_port "10.4.64.37" do + action :delete +end diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/windows.rb b/kitchen-tests/cookbooks/end_to_end/recipes/windows.rb index e04396783b..f4cd74e3bf 100644 --- a/kitchen-tests/cookbooks/end_to_end/recipes/windows.rb +++ b/kitchen-tests/cookbooks/end_to_end/recipes/windows.rb @@ -176,3 +176,5 @@ end windows_certificate "c:/mordor/ca.cert.pem" do store_name "ROOT" end + +include_recipe "::_windows_printer" diff --git a/knife/lib/chef/knife/configure.rb b/knife/lib/chef/knife/configure.rb index 4a73b6875b..9c806b4af6 100644 --- a/knife/lib/chef/knife/configure.rb +++ b/knife/lib/chef/knife/configure.rb @@ -29,7 +29,6 @@ class Chef require "chef-config/path_helper" unless defined?(ChefConfig::PathHelper) require_relative "client_create" require_relative "user_create" - require "ohai" unless defined?(Ohai) Chef::Knife::ClientCreate.load_deps Chef::Knife::UserCreate.load_deps end @@ -131,6 +130,7 @@ class Chef # @return [String] our best guess at what the servername should be using Ohai data and falling back to localhost def guess_servername + require "ohai" unless defined?(Ohai::System) o = Ohai::System.new o.all_plugins(%w{ os hostname fqdn }) o[:fqdn] || o[:machinename] || o[:hostname] || "localhost" diff --git a/knife/lib/chef/knife/org_user_add.rb b/knife/lib/chef/knife/org_user_add.rb index cd0ea88d56..d1fb3a2a5d 100644 --- a/knife/lib/chef/knife/org_user_add.rb +++ b/knife/lib/chef/knife/org_user_add.rb @@ -29,7 +29,7 @@ class Chef description: "Add user to admin group" deps do - require_relative "../org" + require "chef/org" end def run diff --git a/knife/lib/chef/knife/version.rb b/knife/lib/chef/knife/version.rb index d73745a98f..03a77c9edf 100644 --- a/knife/lib/chef/knife/version.rb +++ b/knife/lib/chef/knife/version.rb @@ -17,7 +17,7 @@ class Chef class Knife KNIFE_ROOT = File.expand_path("../..", __dir__) - VERSION = "17.2.12".freeze + VERSION = "17.2.25".freeze end end diff --git a/knife/spec/integration/commands_spec.rb b/knife/spec/integration/commands_spec.rb new file mode 100644 index 0000000000..a9626c38fc --- /dev/null +++ b/knife/spec/integration/commands_spec.rb @@ -0,0 +1,55 @@ +# +# Author:: Marc Pardise (<marc@chef.io>) +# Copyright:: Copyright (c) Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "chef/knife" + +Chef::Knife.subcommand_loader.load_commands +commands = Chef::Knife::SubcommandLoader.generate_hash["_autogenerated_command_paths"]["plugins_paths"].keys + +# Directly execute each support knife command +context "Command Sanity Check: executing ", :workstation do + describe "bundle exec knife" do + commands.each do |command| + command_name = command.gsub("_", " ") + modified_command, expected_result = case command_name + when /knife/ + # because rspec is the actual executable running, the option parser error message + # is invalid from within the test. + next + when /config (use|get|list) profile.*/ + # hyphenated special cases + [command_name, /^USAGE: knife config #{$1}-profile.*/] + when /(role|node|env) (env )?run list(.*)/ + # underscored special cases... + env_part = $2.nil? ? "" : "env_" + ["#{$1} #{$2}run_list#{$3}", /^USAGE: knife #{$1} #{env_part}run_list#{$3}.*/] + else + [ command_name, /^USAGE: knife #{command_name}.*/] + end + + # By using bundle exec knife instead of directly loading the command class or using the knife() helper, + # we ensure that this is a valid end-to-end test. This operates on the assumption + # that we continue to require the command class to be fully loaded so that it can handle the parsing of + # its own options. + full_command = "#{modified_command} --invalid-option outputs usage for '#{modified_command}' to stdout" + it full_command do + result = `bundle exec knife #{full_command}` + expect(result).to match(expected_result) + end + end + end +end diff --git a/lib/chef/action_collection.rb b/lib/chef/action_collection.rb index 1ac47630a9..82a4ebb037 100644 --- a/lib/chef/action_collection.rb +++ b/lib/chef/action_collection.rb @@ -87,13 +87,11 @@ class Chef attr_reader :action_records attr_reader :pending_updates attr_reader :run_context - attr_reader :consumers attr_reader :events def initialize(events, run_context = nil, action_records = []) @action_records = action_records @pending_updates = [] - @consumers = [] @events = events @run_context = run_context end @@ -118,17 +116,17 @@ class Chef self.class.new(events, run_context, subrecords) end + def resources + action_records.map(&:new_resource) + end + # This hook gives us the run_context immediately after it is created so that we can wire up this object to it. # - # This also causes the action_collection_registration event to fire, all consumers that have not yet registered with the - # action_collection must register via this callback. This is the latest point before resources actually start to get - # evaluated. - # # (see EventDispatch::Base#) # def cookbook_compilation_start(run_context) run_context.action_collection = self - # fire the action_colleciton_registration hook after cookbook_compilation_start -- last chance for consumers to register + # this hook is now poorly named since it is just a callback that lets other consumers snag a reference to the action_collection run_context.events.enqueue(:action_collection_registration, self) @run_context = run_context end @@ -139,7 +137,7 @@ class Chef # @params object [Object] callers should call with `self` # def register(object) - consumers << object + Chef::Log.warn "the action collection no longer requires registration at #{caller[0]}" end # End of an unsuccessful converge used to fire off detect_unprocessed_resources. @@ -147,8 +145,6 @@ class Chef # (see EventDispatch::Base#) # def converge_failed(exception) - return if consumers.empty? - detect_unprocessed_resources end @@ -159,8 +155,6 @@ class Chef # (see EventDispatch::Base#) # def resource_action_start(new_resource, action, notification_type = nil, notifier = nil) - return if consumers.empty? - pending_updates << ActionRecord.new(new_resource, action, pending_updates.length) end @@ -170,8 +164,6 @@ class Chef # (see EventDispatch::Base#) # def resource_current_state_loaded(new_resource, action, current_resource) - return if consumers.empty? - current_record.current_resource = current_resource end @@ -181,8 +173,6 @@ class Chef # (see EventDispatch::Base#) # def resource_after_state_loaded(new_resource, action, after_resource) - return if consumers.empty? - current_record.after_resource = after_resource end @@ -191,8 +181,6 @@ class Chef # (see EventDispatch::Base#) # def resource_up_to_date(new_resource, action) - return if consumers.empty? - current_record.status = :up_to_date end @@ -201,8 +189,6 @@ class Chef # (see EventDispatch::Base#) # def resource_skipped(resource, action, conditional) - return if consumers.empty? - current_record.status = :skipped current_record.conditional = conditional end @@ -212,8 +198,6 @@ class Chef # (see EventDispatch::Base#) # def resource_updated(new_resource, action) - return if consumers.empty? - current_record.status = :updated end @@ -222,8 +206,6 @@ class Chef # (see EventDispatch::Base#) # def resource_failed(new_resource, action, exception) - return if consumers.empty? - current_record.status = :failed current_record.exception = exception current_record.error_description = Formatters::ErrorMapper.resource_failed(new_resource, action, exception).for_json @@ -234,8 +216,6 @@ class Chef # (see EventDispatch::Base#) # def resource_completed(new_resource) - return if consumers.empty? - current_record.elapsed_time = new_resource.elapsed_time # Verify if the resource has sensitive data and create a new blank resource with only diff --git a/lib/chef/application.rb b/lib/chef/application.rb index 117f498831..356c4a4a30 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -310,7 +310,7 @@ class Chef logger.info "Forking #{ChefUtils::Dist::Infra::PRODUCT} instance to converge..." pid = fork do # Want to allow forked processes to finish converging when - # TERM singal is received (exit gracefully) + # TERM signal is received (exit gracefully) trap("TERM") do logger.debug("SIGTERM received during converge," + " finishing converge to exit normally (send SIGINT to terminate immediately)") diff --git a/lib/chef/application/base.rb b/lib/chef/application/base.rb index 450fd7673b..b2f98d5f2f 100644 --- a/lib/chef/application/base.rb +++ b/lib/chef/application/base.rb @@ -297,6 +297,21 @@ class Chef::Application::Base < Chef::Application long: "--named-run-list NAMED_RUN_LIST", description: "Use a policyfile's named run list instead of the default run list." + option :slow_report, + long: "--[no-]slow-report [COUNT]", + description: "List the slowest resources at the end of the run (default: 10).", + boolean: true, + default: false, + proc: lambda { |argument| + if argument.nil? + true + elsif argument == false + false + else + Integer(argument) + end + } + IMMEDIATE_RUN_SIGNAL = "1".freeze RECONFIGURE_SIGNAL = "H".freeze diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 54d2a95ba3..3a312701d2 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -863,6 +863,12 @@ class Chef end def start_profiling + if Chef::Config[:slow_report] + require_relative "handler/slow_report" + + Chef::Config.report_handlers << Chef::Handler::SlowReport.new(Chef::Config[:slow_report]) + end + return unless Chef::Config[:profile_ruby] profiling_prereqs! diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 420532585a..6e4f13c291 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -138,11 +138,14 @@ class Chef end def recipe_yml_filenames_by_name - @recipe_ym_filenames_by_name ||= begin + @recipe_yml_filenames_by_name ||= begin name_map = yml_filenames_by_name(files_for("recipes")) - root_alias = cookbook_manifest.root_files.find { |record| record[:name] == "root_files/recipe.yml" } + root_alias = cookbook_manifest.root_files.find { |record| + record[:name] == "root_files/recipe.yml" || + record[:name] == "root_files/recipe.yaml" + } if root_alias - Chef::Log.error("Cookbook #{name} contains both recipe.yml and and recipes/default.yml, ignoring recipes/default.yml") if name_map["default"] + Chef::Log.error("Cookbook #{name} contains both recipe.yml and recipes/default.yml, ignoring recipes/default.yml") if name_map["default"] name_map["default"] = root_alias[:full_path] end name_map @@ -582,8 +585,27 @@ class Chef records.select { |record| record[:name] =~ /\.rb$/ }.inject({}) { |memo, record| memo[File.basename(record[:name], ".rb")] = record[:full_path]; memo } end + # Filters YAML files from the superset of provided files. + # Checks for duplicate basenames with differing extensions (eg yaml v yml) + # and raises error if any are detected. + # This prevents us from arbitrarily the ".yaml" or ".yml" version when both are present, + # because we don't know which is correct. + # This method runs in O(n^2) where N = number of yml files present. This number should be consistently + # low enough that there's no noticeable perf impact. def yml_filenames_by_name(records) - records.select { |record| record[:name] =~ /\.yml$/ }.inject({}) { |memo, record| memo[File.basename(record[:name], ".yml")] = record[:full_path]; memo } + yml_files = records.select { |record| record[:name] =~ /\.(y[a]?ml)$/ } + result = yml_files.inject({}) do |acc, record| + filename = record[:name] + base_dup_name = File.join(File.dirname(filename), File.basename(filename, File.extname(filename))) + yml_files.each do |other| + if other[:name] =~ /#{(File.extname(filename) == ".yml") ? "#{base_dup_name}.yaml" : "#{base_dup_name}.yml"}$/ + raise Chef::Exceptions::AmbiguousYAMLFile.new("Cookbook #{name}@#{version} contains ambiguous files: #{filename} and #{other[:name]}. Please update the cookbook to remove the incorrect file.") + end + end + acc[File.basename(record[:name], File.extname(record[:name]))] = record[:full_path] + acc + end + result end def file_vendor diff --git a/lib/chef/data_collector.rb b/lib/chef/data_collector.rb index 8d76f8a7b2..0e621f1492 100644 --- a/lib/chef/data_collector.rb +++ b/lib/chef/data_collector.rb @@ -104,7 +104,6 @@ class Chef # def action_collection_registration(action_collection) @action_collection = action_collection - action_collection.register(self) end # - Creates and writes our NodeUUID back to the node object diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb index f7b706cb2c..f9504967a9 100644 --- a/lib/chef/event_dispatch/base.rb +++ b/lib/chef/event_dispatch/base.rb @@ -221,7 +221,8 @@ class Chef # Called before convergence starts def converge_start(run_context); end - # Callback hook for handlers to register their interest in the action_collection + # Callback hook for handlers to grab a reference to the action_collection + # (sent before compiling cookbooks, consumers can also find it off the run_context.action_collection) def action_collection_registration(action_collection); end # Called when the converge phase is finished. diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index a0afd25208..ba34b22e61 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -174,6 +174,9 @@ class Chef class CannotDetermineWindowsInstallerType < Package; end class NoWindowsPackageSource < Package; end + # for example, if both recipes/default.yml, recipes/default.yaml are present + class AmbiguousYAMLFile < RuntimeError; end + # Can not create staging file during file deployment class FileContentStagingError < RuntimeError def initialize(errors) diff --git a/lib/chef/handler.rb b/lib/chef/handler.rb index 97562ea31b..173d4c4faa 100644 --- a/lib/chef/handler.rb +++ b/lib/chef/handler.rb @@ -55,6 +55,12 @@ class Chef # class Handler + # FIXME: Chef::Handler should probably inherit from EventDispatch::Base + # and should wire up to those events rather than the "notifications" system + # which is hanging off of Chef::Client. Those "notifications" could then be + # deprecated in favor of events, and this class could become decoupled from + # the Chef::Client object. + def self.handler_for(*args) if args.include?(:start) Chef::Config[:start_handlers] ||= [] @@ -207,17 +213,45 @@ class Chef # The Chef::Node for this client run def_delegator :@run_status, :node - ## - # :method: all_resources + # @return Array<Chef::Resource> all resources other than unprocessed # - # An Array containing all resources in the chef run's resource_collection - def_delegator :@run_status, :all_resources + def all_resources + @all_resources ||= action_collection&.filtered_collection(unprocessed: false)&.resources || [] + end - ## - # :method: updated_resources + # @return Array<Chef::Resource> all updated resources + # + def updated_resources + @updated_resources ||= action_collection&.filtered_collection(up_to_date: false, skipped: false, failed: false, unprocessed: false)&.resources || [] + end + + # @return Array<Chef::Resource> all up_to_date resources + # + def up_to_date_resources + @up_to_date_resources ||= action_collection&.filtered_collection(updated: false, skipped: false, failed: false, unprocessed: false)&.resources || [] + end + + # @return Array<Chef::Resource> all failed resources # - # An Array containing all resources that were updated during the chef run - def_delegator :@run_status, :updated_resources + def failed_resources + @failed_resources ||= action_collection&.filtered_collection(updated: false, up_to_date: false, skipped: false, unprocessed: false)&.resources || [] + end + + # @return Array<Chef::Resource> all skipped resources + # + def skipped_resources + @skipped_resources ||= action_collection&.filtered_collection(updated: false, up_to_date: false, failed: false, unprocessed: false)&.resources || [] + end + + # Unprocessed resources are those which are left over in the outer recipe context when a run fails. + # Sub-resources of unprocessed resourced are impossible to capture because they would require processing + # the outer resource. + # + # @return Array<Chef::Resource> all unprocessed resources + # + def unprocessed_resources + @unprocessed_resources ||= action_collection&.filtered_collection(updated: false, up_to_date: false, failed: false, skipped: false)&.resources || [] + end ## # :method: success? @@ -232,6 +266,10 @@ class Chef # Did the chef run fail? True if the chef run raised an uncaught exception def_delegator :@run_status, :failed? + def action_collection + @run_status.run_context.action_collection + end + # The main entry point for report handling. Subclasses should override this # method with their own report handling logic. def report; end diff --git a/lib/chef/handler/slow_report.rb b/lib/chef/handler/slow_report.rb new file mode 100644 index 0000000000..7bb472f4d5 --- /dev/null +++ b/lib/chef/handler/slow_report.rb @@ -0,0 +1,66 @@ +# +# Copyright:: Copyright (c) Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require_relative "../handler" +require "tty/table" unless defined?(TTY::Table) + +class Chef + class Handler + class SlowReport < ::Chef::Handler + attr_accessor :amount + + def initialize(amount) + @amount = Integer(amount) rescue nil + @amount ||= 10 + end + + def report + if count == 0 + puts "\nNo resources to profile\n\n" + return + end + + top = all_records.sort_by(&:elapsed_time).last(amount).reverse + data = top.map { |r| [ r.new_resource.to_s, r.elapsed_time, r.action, r.new_resource.cookbook_name, r.new_resource.recipe_name, stripped_source_line(r.new_resource) ] } + puts "\nTop #{count} slowest #{count == 1 ? "resource" : "resources"}:\n\n" + table = TTY::Table.new(%w{resource elapsed_time action cookbook recipe source}, data) + rendered = table.render do |renderer| + renderer.border do + mid "-" + mid_mid " " + end + end + puts rendered + puts "\n" + end + + def all_records + @all_records ||= action_collection&.filtered_collection(unprocessed: false) || [] + end + + def count + num = all_resources.count + num > amount ? amount : num + end + + def stripped_source_line(resource) + # strip the leading path off of the source line + resource.source_line.gsub(%r{.*/cookbooks/}, "").gsub(%r{.*/chef-[0-9\.]+/}, "") + end + end + end +end diff --git a/lib/chef/provider/support/zypper_repo.erb b/lib/chef/provider/support/zypper_repo.erb index 6d508fa77f..23e871e406 100644 --- a/lib/chef/provider/support/zypper_repo.erb +++ b/lib/chef/provider/support/zypper_repo.erb @@ -1,15 +1,17 @@ -# This file was generated by Chef +# This file was generated by Chef Infra # Do NOT modify this file by hand. [<%= @config.repo_name %>] <% %w{ type enabled autorefresh gpgcheck gpgkey baseurl mirrorlist path priority keeppackages mode refresh_cache }.each do |prop| -%> -<% next if @config.send(prop.to_sym).nil? -%> +<% next if @config.send(prop.to_sym).nil? || (@config.send(prop.to_sym).is_a?(Array) && @config.send(prop.to_sym).empty?) -%> <%= prop %>=<%= case @config.send(prop.to_sym) when TrueClass '1' when FalseClass '0' + when Array + @config.send(prop.to_sym).join("\n ") else @config.send(prop.to_sym) end %> diff --git a/lib/chef/provider/zypper_repository.rb b/lib/chef/provider/zypper_repository.rb index bbb25ee6cd..c1c863e20e 100644 --- a/lib/chef/provider/zypper_repository.rb +++ b/lib/chef/provider/zypper_repository.rb @@ -31,12 +31,12 @@ class Chef action :create do if new_resource.gpgautoimportkeys - install_gpg_key(new_resource.gpgkey) + install_gpg_keys(new_resource.gpgkey) else logger.debug("'gpgautoimportkeys' property is set to false. Skipping key import.") end - declare_resource(:template, "/etc/zypp/repos.d/#{escaped_repo_name}.repo") do + template "/etc/zypp/repos.d/#{escaped_repo_name}.repo" do if template_available?(new_resource.source) source new_resource.source else @@ -51,13 +51,13 @@ class Chef end action :delete do - declare_resource(:execute, "zypper --quiet --non-interactive removerepo #{escaped_repo_name}") do + execute "zypper --quiet --non-interactive removerepo #{escaped_repo_name}" do only_if "zypper --quiet lr #{escaped_repo_name}" end end action :refresh do - declare_resource(:execute, "zypper --quiet --non-interactive refresh --force #{escaped_repo_name}") do + execute "zypper --quiet --non-interactive refresh --force #{escaped_repo_name}" do only_if "zypper --quiet lr #{escaped_repo_name}" end end @@ -68,15 +68,7 @@ class Chef # zypper repos are allowed to have spaces in the names # @return [String] escaped repo string def escaped_repo_name - Shellwords.escape(new_resource.repo_name) - end - - # return the specified cookbook name or the cookbook containing the - # resource. - # - # @return [String] name of the cookbook - def cookbook_name - new_resource.cookbook || new_resource.cookbook_name + @escaped_repo_name ||= Shellwords.escape(new_resource.repo_name) end # determine if a template file is available in the current run @@ -84,7 +76,7 @@ class Chef # # @return [Boolean] template file exists or doesn't def template_available?(path) - !path.nil? && run_context.has_template_in_cookbook?(cookbook_name, path) + !path.nil? && run_context.has_template_in_cookbook?(new_resource.cookbook, path) end # determine if a cookbook file is available in the run @@ -92,7 +84,7 @@ class Chef # # @return [Boolean] cookbook file exists or doesn't def has_cookbook_file?(fn) - run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn) + run_context.has_cookbook_file_in_cookbook?(new_resource.cookbook, fn) end # Given the provided key URI determine what kind of chef resource we need @@ -158,27 +150,31 @@ class Chef short_key_id end - # install the provided gpg key - # @param [String] uri the uri of the local or remote gpg key - def install_gpg_key(uri) - unless uri - logger.debug("'gpgkey' property not provided or set to nil. Skipping key import.") + # install the provided gpg keys + # @param [String] uris the uri of the local or remote gpg key + def install_gpg_keys(uris) + if uris.empty? + logger.debug("'gpgkey' property not provided or set. Skipping gpg key import.") return end - cached_keyfile = ::File.join(Chef::Config[:file_cache_path], uri.split("/")[-1]) + # fetch each key to the cache dir either from the cookbook or by downloading it + # and then import the key + uris.each do |uri| + cached_keyfile = ::File.join(Chef::Config[:file_cache_path], uri.split("/")[-1]) - declare_resource(key_type(new_resource.gpgkey), cached_keyfile) do - source uri - mode "0644" - sensitive new_resource.sensitive - action :create - end + declare_resource(key_type(uri), cached_keyfile) do + source uri + mode "0644" + sensitive new_resource.sensitive + action :create + end - declare_resource(:execute, "import gpg key from #{new_resource.gpgkey}") do - command "/bin/rpm --import #{cached_keyfile}" - not_if { key_installed?(cached_keyfile) } - action :run + execute "import gpg key from #{uri}" do + command "/bin/rpm --import #{cached_keyfile}" + not_if { key_installed?(cached_keyfile) } + action :run + end end end end diff --git a/lib/chef/resource/windows_pagefile.rb b/lib/chef/resource/windows_pagefile.rb index a5f6052849..6fb63f2f3b 100644 --- a/lib/chef/resource/windows_pagefile.rb +++ b/lib/chef/resource/windows_pagefile.rb @@ -39,16 +39,26 @@ class Chef ```ruby windows_pagefile 'Delete the pagefile' do - path 'C:\pagefile.sys' + path 'C' action :delete end ``` + **Switch to system managed pagefiles**: + + ```ruby + windows_pagefile 'Change the pagefile to System Managed' do + path 'E:\' + system_managed true + action :set + end + ``` + **Create a pagefile with an initial and maximum size**: ```ruby - windows_pagefile 'create the pagefile' do - path 'C:\pagefile.sys' + windows_pagefile 'create the pagefile with these sizes' do + path 'f:\' initial_size 100 maximum_size 200 end @@ -64,8 +74,7 @@ class Chef description: "Configures whether the system manages the pagefile size." property :automatic_managed, [TrueClass, FalseClass], - description: "Enable automatic management of pagefile initial and maximum size. Setting this to true ignores `initial_size` and `maximum_size` properties.", - default: false + description: "Enable automatic management of pagefile initial and maximum size. Setting this to true ignores `initial_size` and `maximum_size` properties." property :initial_size, Integer, description: "Initial size of the pagefile in megabytes." @@ -74,22 +83,25 @@ class Chef description: "Maximum size of the pagefile in megabytes." action :set, description: "Configures the default pagefile, creating if it doesn't exist." do - pagefile = new_resource.path - initial_size = new_resource.initial_size - maximum_size = new_resource.maximum_size - system_managed = new_resource.system_managed automatic_managed = new_resource.automatic_managed if automatic_managed set_automatic_managed unless automatic_managed? - else + elsif automatic_managed == false unset_automatic_managed if automatic_managed? + else + pagefile = clarify_pagefile_name + initial_size = new_resource.initial_size + maximum_size = new_resource.maximum_size + system_managed = new_resource.system_managed - # Check that the resource is not just trying to unset automatic managed, if it is do nothing more - if (initial_size && maximum_size) || system_managed - validate_name - create(pagefile) unless exists?(pagefile) + # the method below is designed to raise an exception if the drive you are trying to create a pagefile for doesn't exist. + # PowerShell will happily let you create a pagefile called h:\pagefile.sys even though you don't have an H:\ drive. + + pagefile_drive_exist?(pagefile) + create(pagefile) unless exists?(pagefile) + if (initial_size && maximum_size) || system_managed if system_managed set_system_managed(pagefile) unless max_and_min_set?(pagefile, 0, 0) else @@ -102,20 +114,32 @@ class Chef end action :delete, description: "Deletes the specified pagefile." do - validate_name - delete(new_resource.path) if exists?(new_resource.path) + pagefile = clarify_pagefile_name + delete(pagefile) if exists?(pagefile) end action_class do private - # make sure the provided name property matches the appropriate format - # we do this here and not in the property itself because if automatic_managed - # is set then this validation is not necessary / doesn't make sense at all - def validate_name - return if /^.:.*.sys/.match?(new_resource.path) + # We are adding support for a number of possibilities for how users will express the drive and location they want the pagefile written to. + def clarify_pagefile_name + case new_resource.path + # user enters C, C:, C:\, C:\\ + when /^[a-zA-Z]/ + new_resource.path[0] + ":\\pagefile.sys" + # user enters C:\pagefile.sys OR c:\foo\bar\pagefile.sys as the path + when /^[a-zA-Z]:.*.sys/ + new_resource.path + else + raise "#{new_resource.path} does not match the format DRIVE:\\path\\pagefile.sys for pagefiles. Example: C:\\pagefile.sys" + end + end - raise "#{new_resource.path} does not match the format DRIVE:\\path\\file.sys for pagefiles. Example: C:\\pagefile.sys" + # raise an exception if the target drive location is invalid + def pagefile_drive_exist?(pagefile) + if ::Dir.exist?(pagefile[0] + ":\\") == false + raise "You are trying to create a pagefile on a drive that does not exist!" + end end # See if the pagefile exists @@ -124,9 +148,11 @@ class Chef # @return [Boolean] def exists?(pagefile) @exists ||= begin - logger.trace("Checking if #{pagefile} exists by running: wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list") - cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", returns: [0]) - cmd.stderr.empty? && (cmd.stdout =~ /SettingID=#{get_setting_id(pagefile)}/i) + logger.trace("Checking if #{pagefile} exists by running: Get-CimInstance Win32_PagefileSetting | Where-Object { $_.name -eq $($pagefile)} ") + cmd = "$page_file_name = '#{pagefile}';" + cmd << "$pagefile = Get-CimInstance Win32_PagefileSetting | Where-Object { $_.name -eq $($page_file_name)};" + cmd << "if ([string]::IsNullOrEmpty($pagefile)) { return $false } else { return $true }" + powershell_exec!(cmd).result end end @@ -137,11 +163,14 @@ class Chef # @param [String] max the minimum size of the pagefile # @return [Boolean] def max_and_min_set?(pagefile, min, max) - @max_and_min_set ||= begin - logger.trace("Checking if #{pagefile} min: #{min} and max #{max} are set") - cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", returns: [0]) - cmd.stderr.empty? && (cmd.stdout =~ /InitialSize=#{min}/i) && (cmd.stdout =~ /MaximumSize=#{max}/i) - end + logger.trace("Checking if #{pagefile} has max and initial disk size values set") + cmd = "$page_file = '#{pagefile}';" + cmd << "$driveLetter = $page_file.split(':')[0];" + cmd << "$page_file_settings = Get-CimInstance -ClassName Win32_PageFileSetting -Filter \"SettingID='pagefile.sys @ $($driveLetter):'\" -Property * -ErrorAction Stop;" + cmd << "if ($page_file_settings.InitialSize -eq #{min} -and $page_file_settings.MaximumSize -eq #{max})" + cmd << "{ return $true }" + cmd << "else { return $false }" + powershell_exec!(cmd).result end # create a pagefile @@ -149,9 +178,10 @@ class Chef # @param [String] pagefile path to the pagefile def create(pagefile) converge_by("create pagefile #{pagefile}") do - logger.trace("Running wmic.exe pagefileset create name=\"#{pagefile}\"") - cmd = shell_out("wmic.exe pagefileset create name=\"#{pagefile}\"") - check_for_errors(cmd.stderr) + logger.trace("Running New-CimInstance -ClassName Win32_PageFileSetting to create new pagefile : #{pagefile}") + powershell_exec! <<~ELM + New-CimInstance -ClassName Win32_PageFileSetting -Property @{Name = "#{pagefile}"} + ELM end end @@ -160,9 +190,13 @@ class Chef # @param [String] pagefile path to the pagefile def delete(pagefile) converge_by("remove pagefile #{pagefile}") do - logger.trace("Running wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete") - cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete") - check_for_errors(cmd.stderr) + logger.trace("Running Remove-CimInstance for pagefile : #{pagefile}") + powershell_exec! <<~EOL + $page_file = "#{pagefile}" + $driveLetter = $page_file.split(':')[0] + $PageFile = (Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveLetter):'" -ErrorAction Stop) + $null = ($PageFile | Remove-CimInstance -ErrorAction SilentlyContinue) + EOL end end @@ -172,26 +206,31 @@ class Chef def automatic_managed? @automatic_managed ||= begin logger.trace("Checking if pagefiles are automatically managed") - cmd = shell_out("wmic.exe computersystem where name=\"%computername%\" get AutomaticManagedPagefile /format:list") - cmd.stderr.empty? && (cmd.stdout =~ /AutomaticManagedPagefile=TRUE/i) + cmd = "$sys = Get-CimInstance Win32_ComputerSystem -Property *;" + cmd << "return $sys.AutomaticManagedPagefile" + powershell_exec!(cmd).result end end # turn on automatic management of all pagefiles by Windows def set_automatic_managed - converge_by("set pagefile to Automatic Managed") do - logger.trace("Running wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True") - cmd = shell_out("wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True") - check_for_errors(cmd.stderr) + converge_by("Set pagefile to Automatic Managed") do + logger.trace("Running Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$true} -PassThru") + powershell_exec! <<~EOH + $sys = Get-CimInstance Win32_ComputerSystem -Property * + Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$true} -PassThru + EOH end end # turn off automatic management of all pagefiles by Windows def unset_automatic_managed - converge_by("set pagefile to User Managed") do - logger.trace("Running wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False") - cmd = shell_out("wmic.exe computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False") - check_for_errors(cmd.stderr) + converge_by("Turn off Automatically Managed on pagefiles") do + logger.trace("Running Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$false} -PassThru") + powershell_exec! <<~EOH + $sys = Get-CimInstance Win32_ComputerSystem -Property * + Set-CimInstance -InputObject $sys -Property @{AutomaticManagedPagefile=$false} -PassThru + EOH end end @@ -202,9 +241,14 @@ class Chef # @param [String] max the minimum size of the pagefile def set_custom_size(pagefile, min, max) converge_by("set #{pagefile} to InitialSize=#{min} & MaximumSize=#{max}") do - logger.trace("Running wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}") - cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}", returns: [0]) - check_for_errors(cmd.stderr) + logger.trace("Set-CimInstance -Property @{InitialSize = #{min} MaximumSize = #{max}") + powershell_exec! <<~EOD + $page_file = "#{pagefile}" + $driveLetter = $page_file.split(':')[0] + Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveLetter):'" -ErrorAction Stop | Set-CimInstance -Property @{ + InitialSize = #{min} + MaximumSize = #{max}} + EOD end end @@ -213,21 +257,16 @@ class Chef # @param [String] pagefile path to the pagefile def set_system_managed(pagefile) converge_by("set #{pagefile} to System Managed") do - logger.trace("Running wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0") - cmd = shell_out("wmic.exe pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0", returns: [0]) - check_for_errors(cmd.stderr) + logger.trace("Running ") + powershell_exec! <<~EOM + $page_file = "#{pagefile}" + $driveLetter = $page_file.split(':')[0] + Get-CimInstance -ClassName Win32_PageFileSetting -Filter "SettingID='pagefile.sys @ $($driveLetter):'" -ErrorAction Stop | Set-CimInstance -Property @{ + InitialSize = 0 + MaximumSize = 0} + EOM end end - - def get_setting_id(pagefile) - split_path = pagefile.split("\\") - "#{split_path[1]} @ #{split_path[0]}" - end - - # raise if there's an error on stderr on a shellout - def check_for_errors(stderr) - raise stderr.chomp unless stderr.empty? - end end end end diff --git a/lib/chef/resource/windows_printer.rb b/lib/chef/resource/windows_printer.rb index c8d869b6b4..b1d6e29374 100644 --- a/lib/chef/resource/windows_printer.rb +++ b/lib/chef/resource/windows_printer.rb @@ -1,6 +1,7 @@ # # Author:: Doug Ireton (<doug@1strategy.com>) # Copyright:: 2012-2018, Nordstrom, Inc. +# Copyright:: Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -102,17 +103,15 @@ class Chef action :delete, description: "Delete an existing printer. Note that this resource does not delete the associated printer port." do if printer_exists? - converge_by("Delete #{@new_resource}") do - delete_printer + converge_by("Delete #{new_resource.device_id}") do + powershell_exec!("Remove-Printer -Name '#{new_resource.device_id}'") end else - Chef::Log.info "#{@current_resource} doesn't exist - can't delete." + Chef::Log.info "#{new_resource.device_id} doesn't exist - can't delete." end end action_class do - private - # does the printer exist # # @param [String] name the name of the printer @@ -132,7 +131,6 @@ class Chef declare_resource(:powershell_script, "Creating printer: #{new_resource.device_id}") do code <<-EOH - Set-WmiInstance -class Win32_Printer ` -EnableAllPrivileges ` -Argument @{ DeviceID = "#{new_resource.device_id}"; @@ -147,15 +145,6 @@ class Chef EOH end end - - def delete_printer - declare_resource(:powershell_script, "Deleting printer: #{new_resource.device_id}") do - code <<-EOH - $printer = Get-WMIObject -class Win32_Printer -EnableAllPrivileges -Filter "name = '#{new_resource.device_id}'" - $printer.Delete() - EOH - end - end end end end diff --git a/lib/chef/resource/windows_printer_port.rb b/lib/chef/resource/windows_printer_port.rb index ae58ed3c6c..b109528b5d 100644 --- a/lib/chef/resource/windows_printer_port.rb +++ b/lib/chef/resource/windows_printer_port.rb @@ -1,6 +1,7 @@ # # Author:: Doug Ireton <doug@1strategy.com> # Copyright:: 2012-2018, Nordstrom, Inc. +# Copyright:: Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -68,14 +69,17 @@ class Chef } property :port_name, String, - description: "The port name." + description: "The port name.", + default: lazy { |x| "IP_#{x.ipv4_address}" }, + default_description: "The resource block name or the ipv4_address prepended with IP_." property :port_number, Integer, - description: "The port number.", + description: "The TCP port number.", default: 9100 property :port_description, String, - description: "The description of the port." + desired_state: false, + deprecated: true property :snmp_enabled, [TrueClass, FalseClass], description: "Determines if SNMP is enabled on the port.", @@ -86,75 +90,58 @@ class Chef validation_message: "port_protocol must be either 1 for RAW or 2 for LPR!", default: 1, equal_to: [1, 2] - PORTS_REG_KEY = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\\'.freeze unless defined?(PORTS_REG_KEY) - - # @todo Set @current_resource port properties from registry load_current_value do |new_resource| - name new_resource.name - ipv4_address new_resource.ipv4_address - port_name new_resource.port_name || "IP_#{new_resource.ipv4_address}" - end - - action :create, description: "Create the printer port, if one doesn't already exist." do - if port_exists? - Chef::Log.info "#{@new_resource} already exists - nothing to do." - else - converge_by("Create #{@new_resource}") do - create_printer_port - end - end - end + port_data = powershell_exec(%Q{Get-WmiObject -Class Win32_TCPIPPrinterPort -Filter "Name='#{new_resource.port_name}'"}).result - action :delete, description: "Delete an existing printer port." do - if port_exists? - converge_by("Delete #{@new_resource}") do - delete_printer_port - end + if port_data.empty? + current_value_does_not_exist! else - Chef::Log.info "#{@current_resource} doesn't exist - can't delete." + ipv4_address port_data["HostAddress"] + port_name port_data["Name"] + snmp_enabled port_data["SNMPEnabled"] + port_protocol port_data["Protocol"] + port_number port_data["PortNumber"] end end - action_class do - private - - def port_exists? - name = new_resource.port_name || "IP_#{new_resource.ipv4_address}" - port_reg_key = PORTS_REG_KEY + name - - logger.trace "Checking to see if this reg key exists: '#{port_reg_key}'" - registry_key_exists?(port_reg_key) - end - - def create_printer_port - port_name = new_resource.port_name || "IP_#{new_resource.ipv4_address}" - - # create the printer port using PowerShell - declare_resource(:powershell_script, "Creating printer port #{new_resource.port_name}") do - code <<-EOH - - Set-WmiInstance -class Win32_TCPIPPrinterPort ` - -EnableAllPrivileges ` - -Argument @{ HostAddress = "#{new_resource.ipv4_address}"; - Name = "#{port_name}"; - Description = "#{new_resource.port_description}"; - PortNumber = "#{new_resource.port_number}"; - Protocol = "#{new_resource.port_protocol}"; - SNMPEnabled = "$#{new_resource.snmp_enabled}"; - } + action :create, description: "Create or update the printer port." do + converge_if_changed do + if current_resource + # update the printer port using PowerShell + powershell_exec! <<-EOH + Get-WmiObject Win32_TCPIPPrinterPort -EnableAllPrivileges -filter "Name='#{new_resource.port_name}'" | + ForEach-Object{ + $_.HostAddress='#{new_resource.ipv4_address}' + $_.PortNumber='#{new_resource.port_number}' + $_.Protocol='#{new_resource.port_protocol}' + $_.SNMPEnabled='$#{new_resource.snmp_enabled}' + $_.Put() + } + EOH + else + # create the printer port using PowerShell + powershell_exec! <<-EOH + Set-WmiInstance -class Win32_TCPIPPrinterPort ` + -EnableAllPrivileges ` + -Argument @{ HostAddress = "#{new_resource.ipv4_address}"; + Name = "#{new_resource.port_name}"; + PortNumber = "#{new_resource.port_number}"; + Protocol = "#{new_resource.port_protocol}"; + SNMPEnabled = "$#{new_resource.snmp_enabled}"; + } EOH end - end - def delete_printer_port - port_name = new_resource.port_name || "IP_#{new_resource.ipv4_address}" + end + end - declare_resource(:powershell_script, "Deleting printer port: #{new_resource.port_name}") do - code <<-EOH - $port = Get-WMIObject -class Win32_TCPIPPrinterPort -EnableAllPrivileges -Filter "name = '#{port_name}'" - $port.Delete() - EOH + action :delete, description: "Delete an existing printer port." do + if current_resource + converge_by("Delete #{new_resource.port_name}") do + powershell_exec!("Remove-PrinterPort -Name #{new_resource.port_name}") end + else + Chef::Log.info "#{new_resource.port_name} doesn't exist - can't delete." end end end diff --git a/lib/chef/resource/zypper_repository.rb b/lib/chef/resource/zypper_repository.rb index 89ed7f1aef..228329d2ea 100644 --- a/lib/chef/resource/zypper_repository.rb +++ b/lib/chef/resource/zypper_repository.rb @@ -24,7 +24,7 @@ class Chef unified_mode true provides(:zypper_repository) { true } - provides(:zypper_repo) { true } + provides(:zypper_repo) { true } # legacy cookbook compatibility description "Use the **zypper_repository** resource to create Zypper package repositories on SUSE Enterprise Linux and openSUSE systems. This resource maintains full compatibility with the **zypper_repository** resource in the existing **zypper** cookbook." introduced "13.3" @@ -34,11 +34,27 @@ class Chef ```ruby zypper_repository 'apache' do baseurl 'http://download.opensuse.org/repositories/Apache' - path '/openSUSE_Leap_15.0' + path '/openSUSE_Leap_15.2' type 'rpm-md' priority '100' end ``` + + **Remove the repo named 'apache'**: + + ```ruby + zypper_repository 'apache' do + action :delete + end + ``` + + **Refresh the repo named 'apache'**: + + ```ruby + zypper_repository 'apache' do + action :refresh + end + ``` DOC property :repo_name, String, @@ -66,8 +82,10 @@ class Chef description: "Determines whether or not to perform a GPG signature check on the repository.", default: true - property :gpgkey, String, - description: "The location of the repository key to be imported." + property :gpgkey, [String, Array], + description: "The location of the repository key(s) to be imported.", + coerce: proc { |v| Array(v) }, + default: [] property :baseurl, String, description: "The base URL for the Zypper repository, such as `http://download.opensuse.org`." @@ -95,10 +113,12 @@ class Chef default: true property :source, String, - description: "The name of the template for the repository file. Only necessary if you're not using the built in template." + description: "The name of the template for the repository file. Only necessary if you're using a custom template for the repository file." property :cookbook, String, - description: "The cookbook to source the repository template file from. Only necessary if you're not using the built in template.", + description: "The cookbook to source the repository template file from. Only necessary if you're using a custom template for the repository file.", + default: lazy { cookbook_name }, + default_description: "The cookbook containing the resource", desired_state: false property :gpgautoimportkeys, [TrueClass, FalseClass], diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb index 4051ac2f49..c6a7be133f 100644 --- a/lib/chef/resource_reporter.rb +++ b/lib/chef/resource_reporter.rb @@ -135,7 +135,6 @@ class Chef def action_collection_registration(action_collection) @action_collection = action_collection - action_collection.register(self) if reporting_enabled? end def post_reporting_data diff --git a/lib/chef/version.rb b/lib/chef/version.rb index 42549e5eff..94aa23c20f 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -23,7 +23,7 @@ require_relative "version_string" class Chef CHEF_ROOT = File.expand_path("..", __dir__) - VERSION = Chef::VersionString.new("17.2.12") + VERSION = Chef::VersionString.new("17.2.25") end # diff --git a/omnibus/Gemfile.lock b/omnibus/Gemfile.lock index b1d660d223..32b2dd0d44 100644 --- a/omnibus/Gemfile.lock +++ b/omnibus/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/chef/omnibus-software.git - revision: a03d3ba3409d3a35c929d1192ead7a7215dac5b2 + revision: 606edf8624241ad8b79200edabda4d788cb61882 branch: master specs: omnibus-software (4.0.0) @@ -32,7 +32,7 @@ GEM artifactory (3.0.15) awesome_print (1.9.2) aws-eventstream (1.1.1) - aws-partitions (1.461.0) + aws-partitions (1.465.0) aws-sdk-core (3.114.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) @@ -41,7 +41,7 @@ GEM aws-sdk-kms (1.43.0) aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.95.0) + aws-sdk-s3 (1.95.1) aws-sdk-core (~> 3, >= 3.112.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) @@ -177,20 +177,24 @@ GEM ed25519 (1.2.4) erubi (1.10.0) erubis (2.7.0) - faraday (1.4.1) + faraday (1.4.2) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) faraday-net_http (~> 1.0) faraday-net_http_persistent (~> 1.1) multipart-post (>= 1.2, < 3) ruby2_keywords (>= 0.0.4) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-net_http (1.0.1) faraday-net_http_persistent (1.1.0) faraday_middleware (1.0.0) faraday (~> 1.0) - ffi (1.15.0) - ffi (1.15.0-x64-mingw32) - ffi (1.15.0-x86-mingw32) + ffi (1.15.1) + ffi (1.15.1-x64-mingw32) + ffi (1.15.1-x86-mingw32) ffi-libarchive (1.0.17) ffi (~> 1.0) ffi-win32-extensions (1.0.4) @@ -206,7 +210,7 @@ GEM highline (2.0.3) httpclient (2.8.3) iniparse (1.5.0) - inspec-core (4.37.17) + inspec-core (4.37.20) addressable (~> 2.4) chef-telemetry (~> 1.0, >= 1.0.8) faraday (>= 0.9.0, < 1.5) @@ -375,7 +379,7 @@ GEM toml-rb (2.0.1) citrus (~> 3.0, > 3.0) tomlrb (1.3.0) - train-core (3.7.0) + train-core (3.7.2) addressable (~> 2.5) ffi (!= 1.13.0) json (>= 1.8, < 3.0) diff --git a/spec/functional/resource/windows_pagefile_spec.rb b/spec/functional/resource/windows_pagefile_spec.rb new file mode 100644 index 0000000000..fd44c01973 --- /dev/null +++ b/spec/functional/resource/windows_pagefile_spec.rb @@ -0,0 +1,98 @@ +# Author: John McCrae (john.mccrae@progress.com) +# Copyright:: Copyright (c) Chef Software Inc. +# License: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "spec_helper" +require "chef/mixin/powershell_exec" + +describe Chef::Resource::WindowsPagefile, :windows_only do + include Chef::Mixin::PowershellExec + + let(:c_path) { "c:\\" } + let(:e_path) { "e:\pagefile.sys" } + + let(:run_context) do + node = Chef::Node.new + node.consume_external_attrs(OHAI_SYSTEM.data, {}) # node[:languages][:powershell][:version] + node.automatic["os"] = "windows" + node.automatic["platform"] = "windows" + node.automatic["platform_version"] = "6.1" + node.automatic["kernel"][:machine] = :x86_64 # Only 64-bit architecture is supported + empty_events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, empty_events) + end + + subject do + new_resource = Chef::Resource::WindowsPagefile.new("pagefile", run_context) + new_resource + end + + describe "Setting Up Pagefile Management" do + context "Disable Automatic Management" do + it "Disables Automatic Management" do + subject.path c_path + subject.automatic_managed false + subject.run_action(:set) + expect(subject).to be_updated_by_last_action + end + + it "Enable Automatic Management " do + subject.path c_path + subject.automatic_managed true + subject.run_action(:set) + expect(subject).to be_updated_by_last_action + end + end + end + + describe "Creating a new Pagefile" do + context "Create new pagefile" do + it "Creates a new pagefile on a different drive that doesn't exist" do + subject.path e_path + expect { subject.run_action(:set) }.to raise_error(RuntimeError) + end + end + + context "Update a pagefile" do + it "Changes a pagefile to use custom sizes" do + subject.path c_path + subject.initial_size 20000 + subject.maximum_size 80000 + subject.run_action(:set) + expect(subject).to be_updated_by_last_action + end + end + end + + describe "Deleting a Pagefile and Resetting to Automatically Managed" do + context "delete the pagefile on disk" do + it "deletes the pagefile located at the given path" do + subject.path c_path + subject.run_action(:delete) + expect(subject).to be_updated_by_last_action + end + end + + context "Re-enable automatic management of pagefiles" do + it "Enable Automatic Management " do + subject.path c_path + subject.automatic_managed true + subject.run_action(:set) + expect(subject).to be_updated_by_last_action + end + end + end +end diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb index 01345e32e7..4a964f74de 100644 --- a/spec/unit/cookbook_version_spec.rb +++ b/spec/unit/cookbook_version_spec.rb @@ -41,7 +41,59 @@ describe Chef::CookbookVersion do it "has empty metadata" do expect(cookbook_version.metadata).to eq(Chef::Cookbook::Metadata.new) end + end + + describe "#recipe_yml_filenames_by_name" do + let(:cookbook_version) { Chef::CookbookVersion.new("mycb", "/tmp/mycb") } + + def files_for_recipe(extension) + [ + { name: "recipes/default.#{extension}", full_path: "/home/user/repo/cookbooks/test/recipes/default.#{extension}" }, + { name: "recipes/other.#{extension}", full_path: "/home/user/repo/cookbooks/test/recipes/other.#{extension}" }, + ] + end + context "and YAML files present include both a recipes/default.yml and a recipes/default.yaml" do + before(:each) do + allow(cookbook_version).to receive(:files_for).with("recipes").and_return( + [ + { name: "recipes/default.yml", full_path: "/home/user/repo/cookbooks/test/recipes/default.yml" }, + { name: "recipes/default.yaml", full_path: "/home/user/repo/cookbooks/test/recipes/default.yaml" }, + ] + ) + end + it "because both are valid and we can't pick, it raises an error that contains the info needed to fix the problem" do + expect { cookbook_version.recipe_yml_filenames_by_name } + .to raise_error(Chef::Exceptions::AmbiguousYAMLFile, /.*default.yml.*default.yaml.*update the cookbook to remove/) + end + end + + %w{yml yaml}.each do |extension| + + context "and YAML files are present including a recipes/default.#{extension}" do + before(:each) do + allow(cookbook_version).to receive(:files_for).with("recipes").and_return(files_for_recipe(extension)) + end + + context "and manifest does not include a root_files/recipe.#{extension}" do + it "returns all YAML recipes with a correct default of default.#{extension}" do + expect(cookbook_version.recipe_yml_filenames_by_name).to eq({ "default" => "/home/user/repo/cookbooks/test/recipes/default.#{extension}", + "other" => "/home/user/repo/cookbooks/test/recipes/other.#{extension}" }) + end + end + + context "and manifest also includes a root_files/recipe.#{extension}" do + let(:root_files) { [{ name: "root_files/recipe.#{extension}", full_path: "/home/user/repo/cookbooks/test/recipe.#{extension}" } ] } + before(:each) do + allow(cookbook_version.cookbook_manifest).to receive(:root_files).and_return(root_files) + end + it "returns all YAML recipes with a correct default of recipe.#{extension}" do + expect(cookbook_version.recipe_yml_filenames_by_name).to eq({ "default" => "/home/user/repo/cookbooks/test/recipe.#{extension}", + "other" => "/home/user/repo/cookbooks/test/recipes/other.#{extension}" }) + end + end + end + end end describe "with a cookbook directory named tatft" do diff --git a/spec/unit/handler_spec.rb b/spec/unit/handler_spec.rb index a9820e8a70..ee30113d1c 100644 --- a/spec/unit/handler_spec.rb +++ b/spec/unit/handler_spec.rb @@ -38,6 +38,8 @@ describe Chef::Handler do @run_context = Chef::RunContext.new(@node, {}, @events) @all_resources = [Chef::Resource::Cat.new("lolz"), Chef::Resource::ZenMaster.new("tzu")] @all_resources.first.updated_by_last_action true + @handler.instance_variable_set(:@all_resources, @all_resources) + @handler.instance_variable_set(:@updated_resources, [@all_resources.first]) @run_context.resource_collection.all_resources.replace(@all_resources) @run_status.run_context = @run_context @start_time = Time.now @@ -119,6 +121,8 @@ describe Chef::Handler do @run_context = Chef::RunContext.new(@node, {}, @events) @all_resources = [Chef::Resource::Cat.new("foo"), Chef::Resource::ZenMaster.new("moo")] @all_resources.first.updated_by_last_action true + @handler.instance_variable_set(:@all_resources, @all_resources) + @handler.instance_variable_set(:@updated_resources, [@all_resources.first]) @run_context.resource_collection.all_resources.replace(@all_resources) @run_status.run_context = @run_context @start_time = Time.now @@ -169,17 +173,19 @@ describe Chef::Handler do # and this would test the start handler describe "when running a start handler" do before do + @handler.instance_variable_set(:@all_resources, []) + @handler.instance_variable_set(:@updated_resources, []) @start_time = Time.now allow(Time).to receive(:now).and_return(@start_time) @run_status.start_clock end it "should not have all resources" do - expect(@handler.all_resources).to be_falsey + expect(@handler.all_resources).to be_empty end it "should not have updated resources" do - expect(@handler.updated_resources).to be_falsey + expect(@handler.updated_resources).to be_empty end it "has a shortcut for the start time" do diff --git a/spec/unit/provider/zypper_repository_spec.rb b/spec/unit/provider/zypper_repository_spec.rb index 950022080a..2cf4ed99c8 100644 --- a/spec/unit/provider/zypper_repository_spec.rb +++ b/spec/unit/provider/zypper_repository_spec.rb @@ -107,13 +107,6 @@ describe Chef::Provider::ZypperRepository do end end - describe "#cookbook_name" do - it "returns 'test' when the cookbook property is set" do - new_resource.cookbook("test") - expect(provider.cookbook_name).to eq("test") - end - end - describe "#key_type" do it "returns :remote_file with an http URL" do expect(provider.key_type("https://www.chef.io/key")).to eq(:remote_file) @@ -167,10 +160,10 @@ describe Chef::Provider::ZypperRepository do end end - describe "#install_gpg_key" do - it "skips installing the key if a nil value for key is passed" do + describe "#install_gpg_keys" do + it "skips installing the key if an empty array for key URL is passed" do expect(logger).to receive(:debug) - provider.install_gpg_key(nil) + provider.install_gpg_keys([]) end end end diff --git a/spec/unit/resource/windows_pagefile_spec.rb b/spec/unit/resource/windows_pagefile_spec.rb index 9f12a74ca8..e11b096fc0 100644 --- a/spec/unit/resource/windows_pagefile_spec.rb +++ b/spec/unit/resource/windows_pagefile_spec.rb @@ -18,14 +18,14 @@ require "spec_helper" describe Chef::Resource::WindowsPagefile do - let(:resource) { Chef::Resource::WindowsPagefile.new("C:\\pagefile.sys") } + let(:resource) { Chef::Resource::WindowsPagefile.new("c:\\pagefile.sys") } it "sets resource name as :windows_pagefile" do expect(resource.resource_name).to eql(:windows_pagefile) end it "the path property is the name_property" do - expect(resource.path).to eql("C:\\pagefile.sys") + expect(resource.path).to eql("c:\\pagefile.sys") end it "sets the default action as :set" do @@ -38,12 +38,7 @@ describe Chef::Resource::WindowsPagefile do end it "coerces forward slashes in the path property to back slashes" do - resource.path "C:/pagefile.sys" - expect(resource.path).to eql("C:\\pagefile.sys") + resource.path "c:/pagefile.sys" + expect(resource.path).to eql("c:\\pagefile.sys") end - - it "automatic_managed property defaults to false" do - expect(resource.automatic_managed).to eql(false) - end - end diff --git a/spec/unit/resource/zypper_repository_spec.rb b/spec/unit/resource/zypper_repository_spec.rb index b1b9cdf874..18ce0e47b3 100644 --- a/spec/unit/resource/zypper_repository_spec.rb +++ b/spec/unit/resource/zypper_repository_spec.rb @@ -85,7 +85,7 @@ describe Chef::Resource::ZypperRepository do it "accepts the legacy 'key' property" do resource.key "foo" - expect(resource.gpgkey).to eql("foo") + expect(resource.gpgkey).to eql(["foo"]) end it "accepts the legacy 'uri' property" do |