diff options
57 files changed, 2048 insertions, 526 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 074b4fdaff..ae45e5e667 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,26 @@ <!-- usage documentation: http://expeditor-docs.es.chef.io/configuration/changelog/ --> -<!-- latest_release 14.0.50 --> -## [v14.0.50](https://github.com/chef/chef/tree/v14.0.50) (2018-02-09) +<!-- latest_release 14.0.61 --> +## [v14.0.61](https://github.com/chef/chef/tree/v14.0.61) (2018-02-19) #### Merged Pull Requests -- Use the license_scout that comes with Omnibus gem [#6839](https://github.com/chef/chef/pull/6839) ([tduffield](https://github.com/tduffield)) +- Add hostname resource from chef_hostname cookbook [#6795](https://github.com/chef/chef/pull/6795) ([tas50](https://github.com/tas50)) <!-- latest_release --> <!-- release_rollup since=13.7.16 --> ### Changes since 13.7.16 release #### Merged Pull Requests +- Add hostname resource from chef_hostname cookbook [#6795](https://github.com/chef/chef/pull/6795) ([tas50](https://github.com/tas50)) <!-- 14.0.61 --> +- registry_key: Add sensitive property support for suppressing output (fixes #5695) [#6496](https://github.com/chef/chef/pull/6496) ([shoekstra](https://github.com/shoekstra)) <!-- 14.0.60 --> +- Use the updated inspec gem - 1.51.18 [#6845](https://github.com/chef/chef/pull/6845) ([tas50](https://github.com/tas50)) <!-- 14.0.59 --> +- add Chef::NodeMap#delete_class API [#6846](https://github.com/chef/chef/pull/6846) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 14.0.58 --> +- only run windows env specs on windows [#6850](https://github.com/chef/chef/pull/6850) ([thommay](https://github.com/thommay)) <!-- 14.0.57 --> +- RemoteFile: unlink tempfile when using cache control shows unchanged [#6822](https://github.com/chef/chef/pull/6822) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 14.0.56 --> +- [MSYS-726] Allow setting environment variables at the user level [#6612](https://github.com/chef/chef/pull/6612) ([harikesh-kolekar](https://github.com/harikesh-kolekar)) <!-- 14.0.55 --> +- Fix issue #2351, chef-client doesn't make /etc/chef if the directory … [#6429](https://github.com/chef/chef/pull/6429) ([jseely](https://github.com/jseely)) <!-- 14.0.54 --> +- invites_sort_fail: Clean the invites array before sorting it [#6463](https://github.com/chef/chef/pull/6463) ([MarkGibbons](https://github.com/MarkGibbons)) <!-- 14.0.53 --> +- Implement resource enhancement RFCs [#6818](https://github.com/chef/chef/pull/6818) ([thommay](https://github.com/thommay)) <!-- 14.0.52 --> +- add additional systemd_unit actions [#6835](https://github.com/chef/chef/pull/6835) ([nathwill](https://github.com/nathwill)) <!-- 14.0.51 --> - Use the license_scout that comes with Omnibus gem [#6839](https://github.com/chef/chef/pull/6839) ([tduffield](https://github.com/tduffield)) <!-- 14.0.50 --> - Simplify powershell_out calls in powershell_package [#6837](https://github.com/chef/chef/pull/6837) ([Happycoil](https://github.com/Happycoil)) <!-- 14.0.49 --> - Don't rely on the Passwd Ohai plugin in resources [#6833](https://github.com/chef/chef/pull/6833) ([thommay](https://github.com/thommay)) <!-- 14.0.48 --> diff --git a/CHEF_MVPS.md b/CHEF_MVPS.md index dd46ea9174..a5abe07a56 100644 --- a/CHEF_MVPS.md +++ b/CHEF_MVPS.md @@ -2,7 +2,8 @@ Every release of Chef we pick someone from the community to name as the Most Valuable Player for that release. It could be someone who provided a big feature, reported a security vulnerability, or someone doing great things in the community that we want to highlight. -In addition to the Hall of Fame and MVP recipients, three individuals are awarded the distinction of "Awesome Community Chef" each year at ChefConf. +In addition to the Hall of Fame and MVP recipients, a number of individuals are awarded the distinction +of "Awesome Community Chef" each year at ChefConf. #### Hall of Fame @@ -91,7 +92,7 @@ After receiving three MVP awards, we add someone to the hall of fame. We want to #### Awesome Community Chefs -Each year at ChefConf, three individuals are awarded the Awesome Community Chef award. The Awesome Community Chef awards are a way for the community to recognize a few of the individuals who have made a dramatic impact and have helped further the cause. +Each year at ChefConf, a number of individuals are awarded the Awesome Community Chef award. The Awesome Community Chef awards are a way for the community to recognize a few of the individuals who have made a dramatic impact and have helped further the cause. * 2013 * [Bryan Berry](https://github.com/bryanwb) @@ -105,4 +106,14 @@ Each year at ChefConf, three individuals are awarded the Awesome Community Chef * 2015 * [Jon Cowie](https://github.com/jonlives) * [Noah Kantrowitz](https://github.com/coderanger) - * [Matt Wrock](https://github.com/mwrock)
\ No newline at end of file + * [Matt Wrock](https://github.com/mwrock) +* 2016 + * [Mike Fiedler](https://github.com/miketheman) + * [Doug Ireton](https://github.com/dougireton) + * [Stuart Preston](https://github.com/stuartpreston) + * [Seth Thomas](https://github.com/cheeseplus) +* 2017 + * [Ben Dang](https://github.com/bdangit) + * [Annie Hedgpeth](https://github.com/anniehedgpeth) + * [Sean O'Meara](https://github.com/someara) + * [Nell Shamrell-Harrington](https://github.com/nellshamrell)
\ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 1486a29e7a..c942c1f214 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,11 +8,11 @@ GIT GIT remote: https://github.com/chef/ohai.git - revision: 849a4caf9926a4e11f12f9175b6f4dcd8a9ccaf1 + revision: 034a8a2f2ebccfc50657b8a79d73deeca4c27b17 branch: master specs: - ohai (14.0.1) - chef-config (>= 12.5.0.alpha.1, < 15) + ohai (14.0.12) + chef-config (>= 12.8, < 15) ffi (~> 1.9) ffi-yajl (~> 2.2) ipaddress @@ -35,10 +35,10 @@ GIT PATH remote: . specs: - chef (14.0.50) + chef (14.0.61) addressable bundler (>= 1.10) - chef-config (= 14.0.50) + chef-config (= 14.0.61) chef-zero (>= 13.0) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) @@ -65,10 +65,10 @@ PATH specinfra (~> 2.10) syslog-logger (~> 1.6) uuidtools (~> 2.1.5) - chef (14.0.50-universal-mingw32) + chef (14.0.61-universal-mingw32) addressable bundler (>= 1.10) - chef-config (= 14.0.50) + chef-config (= 14.0.61) chef-zero (>= 13.0) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) @@ -110,7 +110,7 @@ PATH PATH remote: chef-config specs: - chef-config (14.0.50) + chef-config (14.0.61) addressable fuzzyurl mixlib-config (~> 2.0) @@ -123,12 +123,12 @@ GEM addressable (2.4.0) appbundler (0.10.0) mixlib-cli (~> 1.4) - ast (2.3.0) - backports (3.11.0) + ast (2.4.0) + backports (3.11.1) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) builder (3.2.3) - byebug (9.1.0) + byebug (10.0.0) chef-vault (3.3.0) chef-zero (13.1.0) ffi-yajl (~> 2.2) @@ -181,7 +181,7 @@ GEM htmlentities (4.3.4) httpclient (2.8.3) iniparse (1.4.4) - inspec (1.50.1) + inspec (1.51.18) addressable (~> 2.4) faraday (>= 0.9.0) hashie (~> 3.4) @@ -215,7 +215,7 @@ GEM mixlib-log mixlib-authentication (1.4.2) mixlib-cli (1.7.0) - mixlib-config (2.2.4) + mixlib-config (2.2.5) mixlib-log (1.7.1) mixlib-shellout (2.3.2) mixlib-shellout (2.3.2-universal-mingw32) @@ -250,8 +250,8 @@ GEM pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) - pry-byebug (3.5.1) - byebug (~> 9.1) + pry-byebug (3.6.0) + byebug (~> 10.0) pry (~> 0.10) pry-remote (0.1.8) pry (~> 0.9) @@ -1 +1 @@ -14.0.50
\ No newline at end of file +14.0.61
\ No newline at end of file diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb index f5c4062739..cc34f28910 100644 --- a/chef-config/lib/chef-config/version.rb +++ b/chef-config/lib/chef-config/version.rb @@ -21,7 +21,7 @@ module ChefConfig CHEFCONFIG_ROOT = File.expand_path("../..", __FILE__) - VERSION = "14.0.50" + VERSION = "14.0.61" end # diff --git a/lib/chef/api_client/registration.rb b/lib/chef/api_client/registration.rb index e8ab0149e8..27e1f18c17 100644 --- a/lib/chef/api_client/registration.rb +++ b/lib/chef/api_client/registration.rb @@ -19,6 +19,7 @@ require "chef/config" require "chef/server_api" require "chef/exceptions" +require "fileutils" class Chef class ApiClient @@ -69,8 +70,15 @@ class Chef end def assert_destination_writable! - if (File.exists?(destination) && !File.writable?(destination)) || !File.writable?(File.dirname(destination)) - abs_path = File.expand_path(destination) + abs_path = File.expand_path(destination) + if !File.exists?(File.dirname(abs_path)) + begin + FileUtils.mkdir_p(File.dirname(abs_path)) + rescue Errno::EACCES + raise Chef::Exceptions::CannotWritePrivateKey, "I can't create the configuration directory at #{File.dirname(abs_path)} - check permissions?" + end + end + if (File.exists?(abs_path) && !File.writable?(abs_path)) || !File.writable?(File.dirname(abs_path)) raise Chef::Exceptions::CannotWritePrivateKey, "I can't write your private key to #{abs_path} - check permissions?" end end diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb index 0c8f12f1be..4f248625b3 100644 --- a/lib/chef/chef_fs/chef_fs_data_store.rb +++ b/lib/chef/chef_fs/chef_fs_data_store.rb @@ -307,11 +307,13 @@ class Chef # GET /cookbooks/NAME/VERSION or /cookbook_artifacts/NAME/IDENTIFIER elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 3 - with_entry(path) do |entry| + with_entry([path[0]]) do |entry| cookbook_type = path[0] + cookbook_entry = entry.children.select { |child| child.chef_object.full_name == "#{path[1]}-#{path[2]}" }[0] + raise ChefZero::DataStore::DataNotFoundError.new(path) if cookbook_entry.nil? result = nil begin - result = Chef::CookbookManifest.new(entry.chef_object, policy_mode: cookbook_type == "cookbook_artifacts").to_hash + result = Chef::CookbookManifest.new(cookbook_entry.chef_object, policy_mode: cookbook_type == "cookbook_artifacts").to_hash rescue Chef::ChefFS::FileSystem::NotFoundError => e raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end @@ -320,12 +322,7 @@ class Chef if value.is_a?(Array) value.each do |file| if file.is_a?(Hash) && file.has_key?("checksum") - relative = ["file_store", "repo", cookbook_type] - if chef_fs.versioned_cookbooks || cookbook_type == "cookbook_artifacts" - relative << "#{path[1]}-#{path[2]}" - else - relative << path[1] - end + relative = ["file_store", "repo", cookbook_type, cookbook_entry.name ] relative += file[:path].split("/") file["url"] = ChefZero::RestBase.build_uri(request.base_uri, relative) end @@ -519,14 +516,7 @@ class Chef elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 1 with_entry(path) do |entry| begin - if path[0] == "cookbook_artifacts" - entry.children.map { |child| child.name.rpartition("-")[0] }.uniq - elsif chef_fs.versioned_cookbooks - # /cookbooks/name-version -> /cookbooks/name - entry.children.map { |child| split_name_version(child.name)[0] }.uniq - else - entry.children.map { |child| child.name } - end + entry.children.map { |child| child.chef_object.name.to_s }.uniq rescue Chef::ChefFS::FileSystem::NotFoundError # If the cookbooks dir doesn't exist, we have no cookbooks (not 404) [] @@ -534,22 +524,15 @@ class Chef end elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 2 - if chef_fs.versioned_cookbooks || path[0] == "cookbook_artifacts" - result = with_entry([ path[0] ]) do |entry| - # list /cookbooks/name = filter /cookbooks/name-version down to name - entry.children.map { |child| split_name_version(child.name) }. - select { |name, version| name == path[1] }. - map { |name, version| version } - end - if result.empty? - raise ChefZero::DataStore::DataNotFoundError.new(path) - end - result - else - # list /cookbooks/name = <single version> - version = get_single_cookbook_version(path) - [version] + result = with_entry([ path[0] ]) do |entry| + cookbooks = entry.children.map { |child| child.chef_object } + cookbooks.select { |cookbook| cookbook.name.to_s == path[1] }. + map { |cookbook| cookbook.version } end + if result.empty? + raise ChefZero::DataStore::DataNotFoundError.new(path) + end + result else result = with_entry(path) do |entry| diff --git a/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb b/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb index c5a5f873c5..7ae08823b8 100644 --- a/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb +++ b/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb @@ -5,7 +5,7 @@ class Chef module DataHandler class OrganizationInvitesDataHandler < DataHandlerBase def normalize(invites, entry) - invites.map { |invite| invite.is_a?(Hash) ? invite["username"] : invite }.sort.uniq + invites.map { |invite| invite.is_a?(Hash) ? invite["username"] : invite }.compact.sort.uniq end def minimize(invites, entry) diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 5b470f574e..1ed71d2a55 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -45,7 +45,7 @@ class Chef class SigInt < RuntimeError; end class SigTerm < RuntimeError; end class Cron < RuntimeError; end - class Env < RuntimeError; end + class WindowsEnv < RuntimeError; end class Exec < RuntimeError; end class Execute < RuntimeError; end class ErlCall < RuntimeError; end diff --git a/lib/chef/knife/configure.rb b/lib/chef/knife/configure.rb index 10ae62b6c9..b277a14a3e 100644 --- a/lib/chef/knife/configure.rb +++ b/lib/chef/knife/configure.rb @@ -131,9 +131,7 @@ EOH def guess_servername o = Ohai::System.new - o.load_plugins - o.require_plugin "os" - o.require_plugin "hostname" + o.all_plugins(%w{ os hostname fqdn }) o[:fqdn] || o[:machinename] || o[:hostname] || "localhost" end diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index 8e13425f82..885ebc4faa 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -576,8 +576,13 @@ class Chef configure_user configure_password @password = config[:ssh_password] if config[:ssh_password] - configure_ssh_identity_file - configure_ssh_gateway_identity + + # If a password was not given, check for SSH identity file. + if !@password + configure_ssh_identity_file + configure_ssh_gateway_identity + end + configure_gateway configure_session diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb index d90e38b916..c955dd3b12 100644 --- a/lib/chef/mixin/params_validate.rb +++ b/lib/chef/mixin/params_validate.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2008-2017, Chef Software Inc. +# Copyright:: Copyright 2008-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,6 +35,8 @@ class Chef # map options are: # # @param opts [Hash<Symbol,Object>] Validation opts. + # @option opts [String] :validation_message A custom message to return + # should validation fail. # @option opts [Object,Array] :is An object, or list of # objects, that must match the value using Ruby's `===` operator # (`opts[:is].any? { |v| v === value }`). (See #_pv_is.) @@ -91,16 +93,20 @@ class Chef raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash) raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash) + @validation_message ||= {} + map.each do |key, validation| unless key.kind_of?(Symbol) || key.kind_of?(String) raise ArgumentError, "Validation map keys must be symbols or strings!" end + case validation when true _pv_required(opts, key) when false true when Hash + @validation_message[key] = validation.delete(:validation_message) if validation.has_key?(:validation_message) validation.each do |check, carg| check_method = "_pv_#{check}" if respond_to?(check_method, true) @@ -129,6 +135,10 @@ class Chef validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false) end + def _validation_message(key, default) + @validation_message.has_key?(key) ? @validation_message[key] : default + end + # Return the value of a parameter, or nil if it doesn't exist. def _pv_opts_lookup(opts, key) if opts.has_key?(key.to_s) @@ -145,7 +155,7 @@ class Chef if is_required return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?) return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?) - raise Exceptions::ValidationFailed, "Required argument #{key.inspect} is missing!" + raise Exceptions::ValidationFailed, _validation_message(key, "Required argument #{key.inspect} is missing!") end true end @@ -168,7 +178,7 @@ class Chef to_be.each do |tb| return true if value == tb end - raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}." + raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}.") end end @@ -187,7 +197,7 @@ class Chef to_be.each do |tb| return true if value.kind_of?(tb) end - raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}." + raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}.") end end @@ -202,7 +212,7 @@ class Chef unless value.nil? Array(method_name_list).each do |method_name| unless value.respond_to?(method_name) - raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!" + raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key} must have a #{method_name} method!") end end end @@ -234,7 +244,7 @@ class Chef if value.respond_to?(predicate_method) if value.send(predicate_method) - raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}" + raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key} cannot be #{predicate_method_base_name}") end end end @@ -294,7 +304,7 @@ class Chef Array(regex).flatten.each do |r| return true if r.match(value.to_s) end - raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}" + raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}") end end @@ -316,7 +326,7 @@ class Chef if !value.nil? callbacks.each do |message, zeproc| unless zeproc.call(value) - raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!" + raise Exceptions::ValidationFailed, _validation_message(key, "Option #{key}'s value #{value} #{message}!") end end end @@ -428,7 +438,7 @@ class Chef unless errors.empty? message << " Errors:\n#{errors.map { |m| "- #{m}" }.join("\n")}" end - raise Exceptions::ValidationFailed, message + raise Exceptions::ValidationFailed, _validation_message(key, message) end end diff --git a/lib/chef/mixin/properties.rb b/lib/chef/mixin/properties.rb index 8ff2cc4501..6b95b87063 100644 --- a/lib/chef/mixin/properties.rb +++ b/lib/chef/mixin/properties.rb @@ -75,6 +75,8 @@ class Chef # will return if the user does not set one. If this is `lazy`, it will # be run in the context of the instance (and able to access other # properties). + # @option options [String] :description A description of the property. + # @option options [String] :introduced The release that introduced this property # @option options [Boolean] :desired_state `true` if this property is # part of desired state. Defaults to `true`. # @option options [Boolean] :identity `true` if this property @@ -301,6 +303,17 @@ class Chef raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property property.reset(self) end + + # + # The description of the property + # + # @param name [Symbol] The name of the property. + # @return [String] The description of the property. + def property_description(name) + property = self.class.properties[name.to_sym] + raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property + property.description + end end end end diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb index ecd5c9df8f..0406b3c1d6 100644 --- a/lib/chef/node_map.rb +++ b/lib/chef/node_map.rb @@ -118,6 +118,32 @@ class Chef end.map { |matcher| matcher[:klass] } end + # Remove a class from all its matchers in the node_map, will remove mappings completely if its the last matcher left + # + # Note that this leaks the internal structure out a bit, but the main consumer of this (poise/halite) cares only about + # the keys in the returned Hash. + # + # @param klass [Class] the class to seek and destroy + # + # @return [Hash] deleted entries in the same format as the @map + def delete_class(klass) + raise "please use a Class type for the klass argument" unless klass.is_a?(Class) + deleted = {} + map.each do |key, matchers| + deleted_matchers = [] + matchers.delete_if do |matcher| + # because matcher[:klass] may be a string (which needs to die), coerce both to strings to compare somewhat canonically + if matcher[:klass].to_s == klass.to_s + deleted_matchers << matcher + true + end + end + deleted[key] = deleted_matchers unless deleted_matchers.empty? + map.delete(key) if matchers.empty? + end + deleted + end + # Seriously, don't use this, it's nearly certain to change on you # @return remaining # @api private diff --git a/lib/chef/property.rb b/lib/chef/property.rb index 9d0957dcdf..be54c8bfa1 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -60,10 +60,12 @@ class Chef # options). # @option options [Symbol] :name The name of this property. # @option options [Class] :declared_in The class this property comes from. + # @option options [String] :description A description of the property. # @option options [Symbol] :instance_variable_name The instance variable # tied to this property. Must include a leading `@`. Defaults to `@<name>`. # `nil` means the property is opaque and not tied to a specific instance # variable. + # @option options [String] :introduced The release that introduced this property # @option options [Boolean] :desired_state `true` if this property is part of desired # state. Defaults to `true`. # @option options [Boolean] :identity `true` if this property is part of object @@ -158,6 +160,24 @@ class Chef end # + # A description of this property. + # + # @return [String] + # + def description + options[:description] + end + + # + # When this property was introduced + # + # @return [String] + # + def introduced + options[:introduced] + end + + # # The instance variable associated with this property. # # Defaults to `@<name>` @@ -252,7 +272,7 @@ class Chef # def validation_options @validation_options ||= options.reject do |k, v| - [:declared_in, :name, :instance_variable_name, :desired_state, :identity, :default, :name_property, :coerce, :required, :nillable, :sensitive].include?(k) + [:declared_in, :name, :instance_variable_name, :desired_state, :identity, :default, :name_property, :coerce, :required, :nillable, :sensitive, :description, :introduced].include?(k) end end diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 327bf52a13..7cb2301772 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -197,6 +197,20 @@ class Chef @requirements ||= ResourceRequirements.new(@new_resource, run_context) end + def description(description = "NOT_PASSED") + if description != "NOT_PASSED" + @description = description + end + @description + end + + def introduced(introduced = "NOT_PASSED") + if introduced != "NOT_PASSED" + @introduced = introduced + end + @introduced + end + def converge_by(descriptions, &block) converge_actions.add_action(descriptions, &block) end diff --git a/lib/chef/provider/env/windows.rb b/lib/chef/provider/env/windows.rb deleted file mode 100644 index e813025c81..0000000000 --- a/lib/chef/provider/env/windows.rb +++ /dev/null @@ -1,76 +0,0 @@ -# -# Author:: Doug MacEachern (<dougm@vmware.com>) -# Copyright:: Copyright 2010-2016, VMware, 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/mixin/windows_env_helper" - -class Chef - class Provider - class Env - class Windows < Chef::Provider::Env - include Chef::Mixin::WindowsEnvHelper - - provides :env, os: "windows" - - def whyrun_supported? - false - end - - def create_env - obj = env_obj(@new_resource.key_name) - unless obj - obj = WIN32OLE.connect("winmgmts://").get("Win32_Environment").spawninstance_ - obj.name = @new_resource.key_name - obj.username = "<System>" - end - obj.variablevalue = @new_resource.value - obj.put_ - value = @new_resource.value - value = expand_path(value) if @new_resource.key_name.casecmp("PATH") == 0 - ENV[@new_resource.key_name] = value - broadcast_env_change - end - - def delete_env - obj = env_obj(@new_resource.key_name) - if obj - obj.delete_ - broadcast_env_change - end - if ENV[@new_resource.key_name] - ENV.delete(@new_resource.key_name) - end - end - - def env_value(key_name) - obj = env_obj(key_name) - obj ? obj.variablevalue : ENV[key_name] - end - - def env_obj(key_name) - wmi = WmiLite::Wmi.new - # Note that by design this query is case insensitive with regard to key_name - environment_variables = wmi.query("select * from Win32_Environment where name = '#{key_name}'") - if environment_variables && environment_variables.length > 0 - environment_variables[0].wmi_ole_object - end - end - - end - end - end -end diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb index f196154986..a4a0465e11 100644 --- a/lib/chef/provider/registry_key.rb +++ b/lib/chef/provider/registry_key.rb @@ -126,12 +126,18 @@ class Chef value[:data] = value[:data].to_i end unless current_value[:type] == value[:type] && current_value[:data] == value[:data] - converge_by("set value #{value}") do + converge_by_value = value + converge_by_value[:data] = "*sensitive value suppressed*" if new_resource.sensitive + + converge_by("set value #{converge_by_value}") do registry.set_value(new_resource.key, value) end end else - converge_by("set value #{value}") do + converge_by_value = value + converge_by_value[:data] = "*sensitive value suppressed*" if new_resource.sensitive + + converge_by("set value #{converge_by_value}") do registry.set_value(new_resource.key, value) end end @@ -146,7 +152,10 @@ class Chef end new_resource.unscrubbed_values.each do |value| unless @name_hash.has_key?(value[:name].downcase) - converge_by("create value #{value}") do + converge_by_value = value + converge_by_value[:data] = "*sensitive value suppressed*" if new_resource.sensitive + + converge_by("create value #{converge_by_value}") do registry.set_value(new_resource.key, value) end end @@ -157,7 +166,10 @@ class Chef if registry.key_exists?(new_resource.key) new_resource.unscrubbed_values.each do |value| if @name_hash.has_key?(value[:name].downcase) - converge_by("delete value #{value}") do + converge_by_value = value + converge_by_value[:data] = "*sensitive value suppressed*" if new_resource.sensitive + + converge_by("delete value #{converge_by_value}") do registry.delete_value(new_resource.key, value) end end diff --git a/lib/chef/provider/remote_file/http.rb b/lib/chef/provider/remote_file/http.rb index 4732253e5b..8dfa84ee2a 100644 --- a/lib/chef/provider/remote_file/http.rb +++ b/lib/chef/provider/remote_file/http.rb @@ -61,17 +61,22 @@ class Chef def fetch http = Chef::HTTP::Simple.new(uri, http_client_opts) - tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile + orig_tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile if want_progress? - tempfile = http.streaming_request_with_progress(uri, headers, tempfile) do |size, total| + tempfile = http.streaming_request_with_progress(uri, headers, orig_tempfile) do |size, total| events.resource_update_progress(new_resource, size, total, progress_interval) end else - tempfile = http.streaming_request(uri, headers, tempfile) + tempfile = http.streaming_request(uri, headers, orig_tempfile) end if tempfile update_cache_control_data(tempfile, http.last_response) tempfile.close + else + # cache_control shows the file is unchanged, so we got back nil from the streaming_request above, and it is + # now our responsibility to unlink the tempfile we created + orig_tempfile.close + orig_tempfile.unlink end tempfile end diff --git a/lib/chef/provider/systemd_unit.rb b/lib/chef/provider/systemd_unit.rb index dcef93bfde..420438775c 100644 --- a/lib/chef/provider/systemd_unit.rb +++ b/lib/chef/provider/systemd_unit.rb @@ -1,6 +1,6 @@ # # Author:: Nathan Williams (<nath.e.will@gmail.com>) -# Copyright:: Copyright 2016, Nathan Williams +# Copyright:: Copyright 2016-2018, Nathan Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -74,6 +74,18 @@ class Chef end end + def action_preset + converge_by("restoring enable/disable preset configuration for unit: #{new_resource.unit_name}") do + systemctl_execute!(:preset, new_resource.unit_name) + end + end + + def action_revert + converge_by("reverting to vendor version of unit: #{new_resource.unit_name}") do + systemctl_execute!(:revert, new_resource.unit_name) + end + end + def action_enable if current_resource.static Chef::Log.debug("#{new_resource.unit_name} is a static unit, enabling is a NOP.") @@ -98,6 +110,12 @@ class Chef end end + def action_reenable + converge_by("reenabling unit: #{new_resource.unit_name}") do + systemctl_execute!(:reenable, new_resource.unit_name) + end + end + def action_mask unless current_resource.masked converge_by("masking unit: #{new_resource.unit_name}") do diff --git a/lib/chef/provider/env.rb b/lib/chef/provider/windows_env.rb index 490fa31146..b5b06666f3 100644 --- a/lib/chef/provider/env.rb +++ b/lib/chef/provider/windows_env.rb @@ -17,14 +17,17 @@ # require "chef/provider" -require "chef/resource/env" +require "chef/resource/windows_env" +require "chef/mixin/windows_env_helper" class Chef class Provider - class Env < Chef::Provider + class WindowsEnv < Chef::Provider + include Chef::Mixin::WindowsEnvHelper attr_accessor :key_exists - provides :env, os: "!windows" + provides :env, os: "windows" + provides :windows_env, os: "windows" def whyrun_supported? false @@ -36,7 +39,7 @@ class Chef end def load_current_resource - @current_resource = Chef::Resource::Env.new(new_resource.name) + @current_resource = Chef::Resource::WindowsEnv.new(new_resource.name) current_resource.key_name(new_resource.key_name) if env_key_exists(new_resource.key_name) @@ -49,10 +52,6 @@ class Chef current_resource end - def env_value(key_name) - raise Chef::Exceptions::Env, "#{self} provider does not implement env_value!" - end - def env_key_exists(key_name) env_value(key_name) ? true : false end @@ -123,7 +122,7 @@ class Chef end def action_delete - if @key_exists && !delete_element + if ( ENV[new_resource.key_name] || @key_exists ) && !delete_element delete_env Chef::Log.info("#{new_resource} deleted") new_resource.updated_by_last_action(true) @@ -138,16 +137,34 @@ class Chef new_resource.updated_by_last_action(true) end else - raise Chef::Exceptions::Env, "Cannot modify #{new_resource} - key does not exist!" + raise Chef::Exceptions::WindowsEnv, "Cannot modify #{new_resource} - key does not exist!" end end def create_env - raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :#{new_resource.action}" + obj = env_obj(@new_resource.key_name) + unless obj + obj = WIN32OLE.connect("winmgmts://").get("Win32_Environment").spawninstance_ + obj.name = @new_resource.key_name + obj.username = new_resource.user + end + obj.variablevalue = @new_resource.value + obj.put_ + value = @new_resource.value + value = expand_path(value) if @new_resource.key_name.casecmp("PATH") == 0 + ENV[@new_resource.key_name] = value + broadcast_env_change end def delete_env - raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :delete" + obj = env_obj(@new_resource.key_name) + if obj + obj.delete_ + broadcast_env_change + end + if ENV[@new_resource.key_name] + ENV.delete(@new_resource.key_name) + end end def modify_env @@ -166,6 +183,25 @@ class Chef def new_values @new_values ||= new_resource.value.split(new_resource.delim) end + + def env_value(key_name) + obj = env_obj(key_name) + obj.variablevalue if obj + end + + def env_obj(key_name) + return @env_obj if @env_obj + wmi = WmiLite::Wmi.new + # Note that by design this query is case insensitive with regard to key_name + environment_variables = wmi.query("select * from Win32_Environment where name = '#{key_name}'") + if environment_variables && environment_variables.length > 0 + environment_variables.each do |env| + @env_obj = env.wmi_ole_object + return @env_obj if @env_obj.username.split('\\').last.casecmp(new_resource.user) == 0 + end + end + @env_obj = nil + end end end end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index 507203fd28..8b07e1b405 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -27,7 +27,6 @@ require "chef/provider/cron/aix" require "chef/provider/directory" require "chef/provider/dsc_script" require "chef/provider/dsc_resource" -require "chef/provider/env" require "chef/provider/execute" require "chef/provider/file" require "chef/provider/git" @@ -56,13 +55,12 @@ require "chef/provider/systemd_unit" require "chef/provider/template" require "chef/provider/user" require "chef/provider/whyrun_safe_ruby_block" +require "chef/provider/windows_env" require "chef/provider/yum_repository" require "chef/provider/windows_task" require "chef/provider/zypper_repository" require "chef/provider/windows_path" -require "chef/provider/env/windows" - require "chef/provider/package/apt" require "chef/provider/package/chocolatey" require "chef/provider/package/dpkg" diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index ac1146a647..77e735bbce 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -1174,8 +1174,8 @@ class Chef # Internal Resource Interface (for Chef) # - FORBIDDEN_IVARS = [:@run_context, :@not_if, :@only_if, :@enclosing_provider] - HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@not_if, :@only_if, :@elapsed_time, :@enclosing_provider] + FORBIDDEN_IVARS = [:@run_context, :@not_if, :@only_if, :@enclosing_provider, :@description, :@introduced, :@examples, :@validation_message] + HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@not_if, :@only_if, :@elapsed_time, :@enclosing_provider, :@description, :@introduced, :@examples, :@validation_message] include Chef::Mixin::ConvertToClassName extend Chef::Mixin::ConvertToClassName @@ -1374,6 +1374,27 @@ class Chef end end + def self.description(description = "NOT_PASSED") + if description != "NOT_PASSED" + @description = description + end + @description + end + + def self.introduced(introduced = "NOT_PASSED") + if introduced != "NOT_PASSED" + @introduced = introduced + end + @introduced + end + + def self.examples(examples = "NOT_PASSED") + if examples != "NOT_PASSED" + @examples = examples + end + @examples + end + # # The cookbook in which this Resource was defined (if any). # diff --git a/lib/chef/resource/hostname.rb b/lib/chef/resource/hostname.rb new file mode 100644 index 0000000000..16986c09a3 --- /dev/null +++ b/lib/chef/resource/hostname.rb @@ -0,0 +1,252 @@ +class Chef + class Resource + # Sets the hostname and updates /etc/hosts on *nix systems + # @since 14.0.0 + class Hostname < Chef::Resource + provides :hostname + resource_name :hostname + + description "Sets the systems hostname, ensures that reboot will preserve the hostname, and re-runs the ohai plugin so the hostname will be available in subsequent cookbooks." + introduced "14.0" + + property :hostname, + String, + description: "The hostname if different than the resource's name", + name_property: true + + property :compile_time, + [ TrueClass, FalseClass ], + description: "Should the resource run at compile time or not.", + default: true + + property :ipaddress, + String, + description: "The ip address to use when configuring the hosts file", + default: lazy { node["ipaddress"] } + + property :aliases, + [ Array, nil ], + description: "An array of hostname aliases to use when configuring the hosts file", + default: nil + + property :windows_reboot, + [ TrueClass, FalseClass ], + description: "Should Windows nodes be rebooted upon changing the name so it can take effect", + default: true + + action_class do + def append_replacing_matching_lines(path, regex, string) + text = IO.read(path).split("\n") + text.reject! { |s| s =~ regex } + text += [ string ] + file path do + content text.join("\n") + "\n" + owner "root" + group node["root_group"] + mode "0644" + not_if { IO.read(path).split("\n").include?(string) } + end + end + + # read in the xml file used by Ec2ConfigService and update the Ec2SetComputerName + # setting to disable updating the computer name so we don't revert our change on reboot + # @return [String] + def updated_ec2_config_xml + begin + require "rexml/document" + config_file = 'C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml' + config = REXML::Document.new(::File.read(config_file)) + # find an element named State with a sibling element whose value is Ec2SetComputerName + REXML::XPath.each(config, "//Plugin/State[../Name/text() = 'Ec2SetComputerName']") do |element| + element.text = "Disabled" + end + rescue + return "" + end + config.to_s + end + end + + action :set do + description "Sets the node's hostname" + + ohai "reload hostname" do + plugin "hostname" + action :nothing + end + + if node["platform_family"] != "windows" + # set the hostname via /bin/hostname + declare_resource(:execute, "set hostname to #{new_resource.hostname}") do + command "/bin/hostname #{new_resource.hostname}" + not_if { shell_out!("hostname").stdout.chomp == new_resource.hostname } + notifies :reload, "ohai[reload hostname]" + end + + # make sure node['fqdn'] resolves via /etc/hosts + unless new_resource.ipaddress.nil? + newline = "#{new_resource.ipaddress} #{new_resource.hostname}" + newline << " #{new_resource.aliases.join(" ")}" if new_resource.aliases && !new_resource.aliases.empty? + newline << " #{new_resource.hostname[/[^\.]*/]}" + r = append_replacing_matching_lines("/etc/hosts", /^#{new_resource.ipaddress}\s+|\s+#{new_resource.hostname}\s+/, newline) + r.atomic_update false if docker? + r.notifies :reload, "ohai[reload hostname]" + end + + # setup the hostname to perist on a reboot + case + when ::File.exist?("/usr/sbin/scutil") + # darwin + declare_resource(:execute, "set HostName via scutil") do + command "/usr/sbin/scutil --set HostName #{new_resource.hostname}" + not_if { shell_out!("/usr/sbin/scutil --get HostName").stdout.chomp == new_resource.hostname } + notifies :reload, "ohai[reload hostname]" + end + declare_resource(:execute, "set ComputerName via scutil") do + command "/usr/sbin/scutil --set ComputerName #{new_resource.hostname}" + not_if { shell_out!("/usr/sbin/scutil --get ComputerName").stdout.chomp == new_resource.hostname } + notifies :reload, "ohai[reload hostname]" + end + shortname = new_resource.hostname[/[^\.]*/] + declare_resource(:execute, "set LocalHostName via scutil") do + command "/usr/sbin/scutil --set LocalHostName #{shortname}" + not_if { shell_out!("/usr/sbin/scutil --get LocalHostName").stdout.chomp == shortname } + notifies :reload, "ohai[reload hostname]" + end + when node["os"] == "linux" + case + when ::File.exist?("/usr/bin/hostnamectl") && !docker? + # use hostnamectl whenever we find it on linux (as systemd takes over the world) + # this must come before other methods like /etc/hostname and /etc/sysconfig/network + declare_resource(:execute, "hostnamectl set-hostname #{new_resource.hostname}") do + notifies :reload, "ohai[reload hostname]" + not_if { shell_out!("hostnamectl status", { :returns => [0, 1] }).stdout =~ /Static hostname:\s+#{new_resource.hostname}/ } + end + when ::File.exist?("/etc/hostname") + # debian family uses /etc/hostname + # arch also uses /etc/hostname + # the "platform: iox_xr, platform_family: wrlinux, os: linux" platform also hits this + # the "platform: nexus, platform_family: wrlinux, os: linux" platform also hits this + # this is also fallback for any linux systemd host in a docker container (where /usr/bin/hostnamectl will fail) + declare_resource(:file, "/etc/hostname") do + atomic_update false if docker? + content "#{new_resource.hostname}\n" + owner "root" + group node["root_group"] + mode "0644" + end + when ::File.file?("/etc/sysconfig/network") + # older non-systemd RHEL/Fedora derived + append_replacing_matching_lines("/etc/sysconfig/network", /^HOSTNAME\s*=/, "HOSTNAME=#{new_resource.hostname}") + when ::File.exist?("/etc/HOSTNAME") + # SuSE/OpenSUSE uses /etc/HOSTNAME + declare_resource(:file, "/etc/HOSTNAME") do + content "#{new_resource.hostname}\n" + owner "root" + group node["root_group"] + mode "0644" + end + when ::File.exist?("/etc/conf.d/hostname") + # Gentoo + declare_resource(:file, "/etc/conf.d/hostname") do + content "hostname=\"#{new_resource.hostname}\"\n" + owner "root" + group node["root_group"] + mode "0644" + end + else + # This is a failsafe for all other linux distributions where we set the hostname + # via /etc/sysctl.conf on reboot. This may get into a fight with other cookbooks + # that manage sysctls on linux. + append_replacing_matching_lines("/etc/sysctl.conf", /^\s+kernel\.hostname\s+=/, "kernel.hostname=#{new_resource.hostname}") + end + when ::File.exist?("/etc/rc.conf") + # *BSD systems with /etc/rc.conf + /etc/myname + append_replacing_matching_lines("/etc/rc.conf", /^\s+hostname\s+=/, "hostname=#{new_resource.hostname}") + + declare_resource(:file, "/etc/myname") do + content "#{new_resource.hostname}\n" + owner "root" + group node["root_group"] + mode "0644" + end + when ::File.exist?("/etc/nodename") + # Solaris <= 5.10 systems prior to svccfg taking over this functionality (must come before svccfg handling) + declare_resource(:file, "/etc/nodename") do + content "#{new_resource.hostname}\n" + owner "root" + group node["root_group"] + mode "0644" + end + # Solaris also has /etc/inet/hosts (copypasta alert) + unless new_resource.ipaddress.nil? + newline = "#{new_resource.ipaddress} #{new_resource.hostname}" + newline << " #{new_resource.aliases.join(" ")}" if new_resource.aliases && !new_resource.aliases.empty? + newline << " #{new_resource.hostname[/[^\.]*/]}" + r = append_replacing_matching_lines("/etc/inet/hosts", /^#{new_resource.ipaddress}\s+|\s+#{new_resource.hostname}\s+/, newline) + r.notifies :reload, "ohai[reload hostname]" + end + when ::File.exist?("/usr/sbin/svccfg") + # Solaris >= 5.11 systems using svccfg (must come after /etc/nodename handling) + declare_resource(:execute, "svccfg -s system/identity:node setprop config/nodename=\'#{new_resource.hostname}\'") do + notifies :run, "execute[svcadm refresh]", :immediately + notifies :run, "execute[svcadm restart]", :immediately + not_if { shell_out!("svccfg -s system/identity:node listprop config/nodename").stdout.chomp =~ /config\/nodename\s+astring\s+#{new_resource.hostname}/ } + end + declare_resource(:execute, "svcadm refresh") do + command "svcadm refresh system/identity:node" + action :nothing + end + declare_resource(:execute, "svcadm restart") do + command "svcadm restart system/identity:node" + action :nothing + end + else + raise "Do not know how to set hostname on os #{node["os"]}, platform #{node["platform"]},"\ + "platform_version #{node["platform_version"]}, platform_family #{node["platform_family"]}" + end + + else # windows + + # suppress EC2 config service from setting our hostname + if ::File.exist?('C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml') + xml_contents = updated_ec2_config_xml + if xml_contents.empty? + Chef::Log.warn('Unable to properly parse and update C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml contents. Skipping file update.') + else + declare_resource(:file, 'C:\Program Files\Amazon\Ec2ConfigService\Settings\config.xml') do + content xml_contents + end + end + end + + # update via netdom + declare_resource(:powershell_script, "set hostname") do + code <<-EOH + $sysInfo = Get-WmiObject -Class Win32_ComputerSystem + $sysInfo.Rename("#{new_resource.hostname}") + EOH + notifies :request_reboot, "reboot[setting hostname]" + not_if { Socket.gethostbyname(Socket.gethostname).first == new_resource.hostname } + end + + # reboot because $windows + declare_resource(:reboot, "setting hostname") do + reason "chef setting hostname" + action :nothing + only_if { new_resource.windows_reboot } + end + end + end + + # this resource forces itself to run at compile_time + def after_created + if compile_time + Array(action).each do |action| + run_action(action) + end + end + end + end + end +end diff --git a/lib/chef/resource/rhsm_errata.rb b/lib/chef/resource/rhsm_errata.rb new file mode 100644 index 0000000000..56779909f5 --- /dev/null +++ b/lib/chef/resource/rhsm_errata.rb @@ -0,0 +1,45 @@ +# +# Copyright:: 2015-2018 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/resource" + +class Chef + class Resource + class RhsmErrata < Chef::Resource + resource_name :rhsm_errata + + description "A resource for installing packages associated with a given Red"\ + " Hat Subscription Manager Errata ID. This is helpful if packages"\ + " to mitigate a single vulnerability must be installed on your hosts." + introduced "14.0" + + property :errata_id, + String, + description: "An optional property for specifying the errata ID if not using the resource's name.", + name_property: true + + action :install do + description "Installs a package for a specific errata ID" + + execute "Install errata packages for #{new_resource.errata_id}" do + command "yum update --advisory #{new_resource.errata_id} -y" + action :run + end + end + end + end +end diff --git a/lib/chef/resource/rhsm_errata_level.rb b/lib/chef/resource/rhsm_errata_level.rb new file mode 100644 index 0000000000..3aa289ac2e --- /dev/null +++ b/lib/chef/resource/rhsm_errata_level.rb @@ -0,0 +1,53 @@ +# +# Copyright:: 2015-2018 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/resource" + +class Chef + class Resource + class RhsmErrataLevel < Chef::Resource + resource_name :rhsm_errata_level + + description "A resource for installing all packages of a specified errata level"\ + " from the Red Hat Subscript Manager. For example, you can ensure"\ + " that all packages associated with errata marked at a 'Critical'"\ + " security level are installed." + introduced "14.0" + + property :errata_level, + String, + coerce: proc { |x| x.downcase }, + equal_to: %w{critical moderate important low}, + description: "The errata level of packages to install.", + name_property: true + + action :install do + descripton "Install all packages of the specified errata level" + + yum_package "yum-plugin-security" do + action :install + only_if { node["platform_version"].to_i == 6 } + end + + execute "Install any #{new_resource.errata_level} errata" do + command "yum update --sec-severity=#{new_resource.errata_level.capitalize} -y" + action :run + end + end + end + end +end diff --git a/lib/chef/resource/rhsm_register.rb b/lib/chef/resource/rhsm_register.rb new file mode 100644 index 0000000000..47fe67d1cf --- /dev/null +++ b/lib/chef/resource/rhsm_register.rb @@ -0,0 +1,170 @@ +# +# Copyright:: 2015-2018 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/resource" +require "shellwords" + +class Chef + class Resource + class RhsmRegister < Chef::Resource + resource_name :rhsm_register + + description "A resource for registering a node with the Red Hat Subscription Manager"\ + " or a local Red Hat Satellite server." + introduced "14.0" + + property :activation_key, + [String, Array], + coerce: proc { |x| Array(x) }, + description: "A String or array of the activation keys to use when registering. You must also specify the organization property if using activation_key." + + property :satellite_host, + String, + description: "The FQDN of the Satellite host to register with. If not specified, the host will be registered with Red Hat's public RHSM service." + + property :organization, + String, + description: "The organization to use when registering, required when using an activation key" + + property :environment, + String, + description: "The environment to use when registering, required when using username and password" + + property :username, + String, + description: "The username to use when registering. Not applicable if using an activation key. If specified, password and environment are also required." + + property :password, + String, + description: "The password to use when registering. Not applicable if using an activation key. If specified, username and environment are also required." + + property :auto_attach, + [TrueClass, FalseClass], + description: "If true, RHSM will attempt to automatically attach the host to applicable subscriptions. It is generally better to use an activation key with the subscriptions pre-defined.", + default: false + + property :install_katello_agent, + [TrueClass, FalseClass], + description: "If true, the 'katello-agent' RPM will be installed.", + default: true + + property :force, + [TrueClass, FalseClass], + description: "If true, the system will be registered even if it is already registered. Normally, any register operations will fail if the machine is has already registered.", + default: false + + action :register do + description "Register the node with RHSM" + + package "subscription-manager" + + unless new_resource.satellite_host.nil? || registered_with_rhsm? + remote_file "#{Chef::Config[:file_cache_path]}/katello-package.rpm" do + source "http://#{new_resource.satellite_host}/pub/katello-ca-consumer-latest.noarch.rpm" + action :create + notifies :install, "yum_package[katello-ca-consumer-latest]", :immediately + not_if { katello_cert_rpm_installed? } + end + + yum_package "katello-ca-consumer-latest" do + options "--nogpgcheck" + source "#{Chef::Config[:file_cache_path]}/katello-package.rpm" + action :nothing + end + + file "#{Chef::Config[:file_cache_path]}/katello-package.rpm" do + action :delete + end + end + + execute "Register to RHSM" do + sensitive new_resource.sensitive + command register_command + action :run + not_if { registered_with_rhsm? } + end + + yum_package "katello-agent" do + action :install + only_if { new_resource.install_katello_agent && !new_resource.satellite_host.nil? } + end + end + + action :unregister do + description "Unregister the node from RHSM" + + execute "Unregister from RHSM" do + command "subscription-manager unregister" + action :run + only_if { registered_with_rhsm? } + notifies :run, "execute[Clean RHSM Config]", :immediately + end + + execute "Clean RHSM Config" do + command "subscription-manager clean" + action :nothing + end + end + + action_class do + def registered_with_rhsm? + cmd = Mixlib::ShellOut.new("subscription-manager status", env: { LANG: "en_US" }) + cmd.run_command + !cmd.stdout.match(/Overall Status: Unknown/) + end + + def katello_cert_rpm_installed? + cmd = Mixlib::ShellOut.new("rpm -qa | grep katello-ca-consumer") + cmd.run_command + !cmd.stdout.match(/katello-ca-consumer/).nil? + end + + def register_command + command = %w{subscription-manager register} + + unless new_resource.activation_key.empty? + raise "Unable to register - you must specify organization when using activation keys" if new_resource.organization.nil? + + command << new_resource.activation_key.map { |key| "--activationkey=#{Shellwords.shellescape(key)}" } + command << "--org=#{Shellwords.shellescape(new_resource.organization)}" + command << "--force" if new_resource.force + + return command.join(" ") + end + + if new_resource.username && new_resource.password + raise "Unable to register - you must specify environment when using username/password" if new_resource.environment.nil? && using_satellite_host? + + command << "--username=#{Shellwords.shellescape(new_resource.username)}" + command << "--password=#{Shellwords.shellescape(new_resource.password)}" + command << "--environment=#{Shellwords.shellescape(new_resource.environment)}" if using_satellite_host? + command << "--auto-attach" if new_resource.auto_attach + command << "--force" if new_resource.force + + return command.join(" ") + end + + raise "Unable to create register command - you must specify activation_key or username/password" + end + + def using_satellite_host? + !new_resource.satellite_host.nil? + end + end + end + end +end diff --git a/lib/chef/resource/rhsm_repo.rb b/lib/chef/resource/rhsm_repo.rb new file mode 100644 index 0000000000..aef4dd43d6 --- /dev/null +++ b/lib/chef/resource/rhsm_repo.rb @@ -0,0 +1,63 @@ +# +# Copyright:: 2015-2018 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/resource" + +class Chef + class Resource + class RhsmRepo < Chef::Resource + resource_name :rhsm_repo + + description "A resource for enabling and disabling Red Hat Subscription Manager"\ + " repositories that are made available via attached subscriptions." + introduced "14.0" + + property :repo_name, + String, + description: "An optional property for specifying the repository name if not using the resource's name.", + name_property: true + + action :enable do + description "Enable a RHSM repository" + + execute "Enable repository #{repo_name}" do + command "subscription-manager repos --enable=#{repo_name}" + action :run + not_if { repo_enabled?(repo_name) } + end + end + + action :disable do + description "Disable a RHSM repository" + + execute "Enable repository #{repo_name}" do + command "subscription-manager repos --disable=#{repo_name}" + action :run + only_if { repo_enabled?(repo_name) } + end + end + + action_class do + def repo_enabled?(repo) + cmd = Mixlib::ShellOut.new("subscription-manager repos --list-enabled", env: { LANG: "en_US" }) + cmd.run_command + !cmd.stdout.match(/Repo ID:\s+#{repo}$/).nil? + end + end + end + end +end diff --git a/lib/chef/resource/rhsm_subscription.rb b/lib/chef/resource/rhsm_subscription.rb new file mode 100644 index 0000000000..41dd398cd5 --- /dev/null +++ b/lib/chef/resource/rhsm_subscription.rb @@ -0,0 +1,96 @@ +# +# Copyright:: 2015-2018 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/resource" + +class Chef + class Resource + class RhsmSubscription < Chef::Resource + resource_name :rhsm_subscription + + description "A resource for adding additional Redhat Subscription Manager subscriptions"\ + " to your host. This can be used when a host's activation_key"\ + " does not attach all necessary subscriptions to your host." + introduced "14.0" + + property :pool_id, + String, + description: "An optional property for specifying the Pool ID if not using the resource's name.", + name_property: true + + action :attach do + description "Attach the node to a subscription pool" + + execute "Attach subscription pool #{new_resource.pool_id}" do + command "subscription-manager attach --pool=#{new_resource.pool_id}" + action :run + not_if { subscription_attached?(new_resource.pool_id) } + end + end + + action :remove do + description "Remove the node from a subscription pool" + + execute "Remove subscription pool #{new_resource.pool_id}" do + command "subscription-manager remove --serial=#{pool_serial(new_resource.pool_id)}" + action :run + only_if { subscription_attached?(new_resource.pool_id) } + end + end + + action_class do + def subscription_attached?(subscription) + cmd = Mixlib::ShellOut.new("subscription-manager list --consumed | grep #{subscription}", env: { LANG: "en_US" }) + cmd.run_command + !cmd.stdout.match(/Pool ID:\s+#{subscription}$/).nil? + end + + def serials_by_pool + serials = {} + pool = nil + serial = nil + + cmd = Mixlib::ShellOut.new("subscription-manager list --consumed", env: { LANG: "en_US" }) + cmd.run_command + cmd.stdout.lines.each do |line| + line.strip! + key, value = line.split(/:\s+/, 2) + next unless ["Pool ID", "Serial"].include?(key) + + if key == "Pool ID" + pool = value + elsif key == "Serial" + serial = value + end + + next unless pool && serial + + serials[pool] = serial + pool = nil + serial = nil + end + + serials + end + + def pool_serial(pool_id) + serials_by_pool[pool_id] + end + end + end + end +end diff --git a/lib/chef/resource/systemd_unit.rb b/lib/chef/resource/systemd_unit.rb index b08b26efa7..baf7e4cfa8 100644 --- a/lib/chef/resource/systemd_unit.rb +++ b/lib/chef/resource/systemd_unit.rb @@ -1,6 +1,6 @@ # # Author:: Nathan Williams (<nath.e.will@gmail.com>) -# Copyright:: Copyright 2016, Nathan Williams +# Copyright:: Copyright 2016-2018, Nathan Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,8 @@ class Chef default_action :nothing allowed_actions :create, :delete, - :enable, :disable, + :preset, :revert, + :enable, :disable, :reenable, :mask, :unmask, :start, :stop, :restart, :reload, diff --git a/lib/chef/resource/env.rb b/lib/chef/resource/windows_env.rb index 12133f4368..d25fe9326b 100644 --- a/lib/chef/resource/env.rb +++ b/lib/chef/resource/windows_env.rb @@ -21,8 +21,9 @@ class Chef class Resource # Use the env resource to manage environment keys in Microsoft Windows. After an environment key is set, Microsoft # Windows must be restarted before the environment key will be available to the Task Scheduler. - class Env < Chef::Resource - resource_name :env + class WindowsEnv < Chef::Resource + resource_name :windows_env + provides :windows_env, os: "windows" provides :env, os: "windows" default_action :create @@ -31,6 +32,7 @@ class Chef property :key_name, String, identity: true, name_property: true property :value, String, required: true property :delim, [ String, nil, false ], desired_state: false + property :user, String, default: "<System>" end end end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 9fee978432..d54e7815c4 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -33,7 +33,6 @@ require "chef/resource/dpkg_package" require "chef/resource/dnf_package" require "chef/resource/dsc_script" require "chef/resource/dsc_resource" -require "chef/resource/env" require "chef/resource/execute" require "chef/resource/file" require "chef/resource/freebsd_package" @@ -42,6 +41,7 @@ require "chef/resource/gem_package" require "chef/resource/git" require "chef/resource/group" require "chef/resource/http_request" +require "chef/resource/hostname" require "chef/resource/homebrew_package" require "chef/resource/ifconfig" require "chef/resource/ksh" @@ -68,6 +68,11 @@ require "chef/resource/reboot" require "chef/resource/registry_key" require "chef/resource/remote_directory" require "chef/resource/remote_file" +require "chef/resource/rhsm_errata_level" +require "chef/resource/rhsm_errata" +require "chef/resource/rhsm_register" +require "chef/resource/rhsm_repo" +require "chef/resource/rhsm_subscription" require "chef/resource/rpm_package" require "chef/resource/solaris_package" require "chef/resource/route" @@ -89,6 +94,7 @@ require "chef/resource/user/pw_user" require "chef/resource/user/solaris_user" require "chef/resource/user/windows_user" require "chef/resource/whyrun_safe_ruby_block" +require "chef/resource/windows_env" require "chef/resource/windows_package" require "chef/resource/yum_package" require "chef/resource/yum_repository" diff --git a/lib/chef/version.rb b/lib/chef/version.rb index 6af0290cbc..72efa3b085 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -23,7 +23,7 @@ require "chef/version_string" class Chef CHEF_ROOT = File.expand_path("../..", __FILE__) - VERSION = Chef::VersionString.new("14.0.50") + VERSION = Chef::VersionString.new("14.0.61") end # diff --git a/omnibus/Gemfile.lock b/omnibus/Gemfile.lock index 0a400af685..a36078d763 100644 --- a/omnibus/Gemfile.lock +++ b/omnibus/Gemfile.lock @@ -1,8 +1,8 @@ GIT remote: https://github.com/chef/omnibus - revision: 68c301587d8856c6ec4445a24ec49365bb22a314 + revision: 63e908114e460d47609869ece898c45d7250247b specs: - omnibus (5.6.8) + omnibus (5.6.9) aws-sdk (~> 2) chef-sugar (~> 3.3) cleanroom (~> 1.0) @@ -29,13 +29,13 @@ GEM addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) awesome_print (1.8.0) - aws-sdk (2.10.128) - aws-sdk-resources (= 2.10.128) - aws-sdk-core (2.10.128) + aws-sdk (2.10.129) + aws-sdk-resources (= 2.10.129) + aws-sdk-core (2.10.129) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-resources (2.10.128) - aws-sdk-core (= 2.10.128) + aws-sdk-resources (2.10.129) + aws-sdk-core (= 2.10.129) aws-sigv4 (1.0.2) berkshelf (4.3.5) addressable (~> 2.3, >= 2.3.4) @@ -91,6 +91,8 @@ GEM faraday (0.9.2) multipart-post (>= 1.2, < 3) ffi (1.9.21) + ffi (1.9.21-x64-mingw32) + ffi (1.9.21-x86-mingw32) ffi-yajl (2.3.1) libyajl2 (~> 1.2) fuzzyurl (0.9.0) @@ -100,6 +102,7 @@ GEM builder (>= 2.1.2) hashie (3.5.7) hitimes (1.2.6) + hitimes (1.2.6-x86-mingw32) httpclient (2.7.2) iostruct (0.0.4) ipaddress (0.8.3) @@ -122,13 +125,16 @@ GEM mixlib-log mixlib-authentication (1.4.2) mixlib-cli (1.7.0) - mixlib-config (2.2.4) - mixlib-install (3.9.0) + mixlib-config (2.2.5) + mixlib-install (3.9.3) mixlib-shellout mixlib-versioning thor mixlib-log (1.7.1) mixlib-shellout (2.3.2) + mixlib-shellout (2.3.2-universal-mingw32) + win32-process (~> 0.8.2) + wmi-lite (~> 1.0) mixlib-versioning (1.2.2) molinillo (0.4.5) multi_json (1.13.1) @@ -171,7 +177,7 @@ GEM pry-stack_explorer (0.4.9.2) binding_of_caller (>= 0.7) pry (>= 0.9.11) - public_suffix (3.0.1) + public_suffix (3.0.2) retryable (2.0.4) ridley (4.6.1) addressable @@ -221,6 +227,8 @@ GEM varia_model (0.4.1) buff-extensions (~> 1.0) hashie (>= 2.0.2, < 4.0.0) + win32-process (0.8.3) + ffi (>= 1.0.0) winrm (2.2.3) builder (>= 2.1.2) erubis (~> 2.7) @@ -243,6 +251,8 @@ GEM PLATFORMS ruby + x64-mingw32 + x86-mingw32 DEPENDENCIES berkshelf (~> 4.0) @@ -257,4 +267,4 @@ DEPENDENCIES winrm-fs (~> 1.0) BUNDLED WITH - 1.15.1 + 1.16.1 diff --git a/spec/functional/resource/env_spec.rb b/spec/functional/resource/env_spec.rb deleted file mode 100755 index 4b0ff70c0b..0000000000 --- a/spec/functional/resource/env_spec.rb +++ /dev/null @@ -1,192 +0,0 @@ -# -# Author:: Adam Edwards (<adamed@chef.io>) -# Copyright:: Copyright 2014-2016, 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" - -describe Chef::Resource::Env, :windows_only do - context "when running on Windows" do - let(:chef_env_test_lower_case) { "chefenvtest" } - let(:chef_env_test_mixed_case) { "chefENVtest" } - let(:env_dne_key) { "env_dne_key" } - let(:env_value1) { "value1" } - let(:env_value2) { "value2" } - - let(:env_value_expandable) { "%SystemRoot%" } - let(:test_run_context) do - node = Chef::Node.new - node.default["os"] = "windows" - node.default["platform"] = "windows" - node.default["platform_version"] = "6.1" - empty_events = Chef::EventDispatch::Dispatcher.new - Chef::RunContext.new(node, {}, empty_events) - end - let(:test_resource) do - Chef::Resource::Env.new("unknown", test_run_context) - end - - before(:each) do - resource_lower = Chef::Resource::Env.new(chef_env_test_lower_case, test_run_context) - resource_lower.run_action(:delete) - resource_mixed = Chef::Resource::Env.new(chef_env_test_mixed_case, test_run_context) - resource_mixed.run_action(:delete) - end - - context "when the create action is invoked" do - it "should create an environment variable for action create" do - expect(ENV[chef_env_test_lower_case]).to eq(nil) - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - end - - it "should modify an existing variable's value to a new value" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.value(env_value2) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value2) - end - - it "should modify an existing variable's value to a new value if the variable name case differs from the existing variable" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.key_name(chef_env_test_mixed_case) - test_resource.value(env_value2) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value2) - end - - it "should not expand environment variables if the variable is not PATH" do - expect(ENV[chef_env_test_lower_case]).to eq(nil) - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value_expandable) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value_expandable) - end - end - - context "when the modify action is invoked" do - it "should raise an exception for modify if the variable doesn't exist" do - expect(ENV[chef_env_test_lower_case]).to eq(nil) - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - expect { test_resource.run_action(:modify) }.to raise_error(Chef::Exceptions::Env) - end - - it "should modify an existing variable's value to a new value" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.value(env_value2) - test_resource.run_action(:modify) - expect(ENV[chef_env_test_lower_case]).to eq(env_value2) - end - - # This examlpe covers Chef Issue #1754 - it "should modify an existing variable's value to a new value if the variable name case differs from the existing variable" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.key_name(chef_env_test_mixed_case) - test_resource.value(env_value2) - test_resource.run_action(:modify) - expect(ENV[chef_env_test_lower_case]).to eq(env_value2) - end - - it "should not expand environment variables if the variable is not PATH" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.value(env_value_expandable) - test_resource.run_action(:modify) - expect(ENV[chef_env_test_lower_case]).to eq(env_value_expandable) - end - - context "when using PATH" do - let(:random_name) { Time.now.to_i } - let(:env_val) { "#{env_value_expandable}_#{random_name}" } - let!(:path_before) { test_resource.provider_for_action(test_resource.action).env_value("PATH") || "" } - let!(:env_path_before) { ENV["PATH"] } - - it "should expand PATH" do - expect(path_before).not_to include(env_val) - test_resource.key_name("PATH") - test_resource.value("#{path_before};#{env_val}") - test_resource.run_action(:create) - expect(ENV["PATH"]).not_to include(env_val) - expect(ENV["PATH"]).to include("#{random_name}") - end - - after(:each) do - # cleanup so we don't flood the path - test_resource.key_name("PATH") - test_resource.value(path_before) - test_resource.run_action(:create) - ENV["PATH"] = env_path_before - end - end - - end - - context "when the delete action is invoked" do - it "should delete an environment variable" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.run_action(:delete) - expect(ENV[chef_env_test_lower_case]).to eq(nil) - end - - it "should not raise an exception when a non-existent environment variable is deleted" do - expect(ENV[chef_env_test_lower_case]).to eq(nil) - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - expect { test_resource.run_action(:delete) }.not_to raise_error - expect(ENV[chef_env_test_lower_case]).to eq(nil) - end - - it "should delete an existing variable's value to a new value if the specified variable name case differs from the existing variable" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.key_name(chef_env_test_mixed_case) - test_resource.run_action(:delete) - expect(ENV[chef_env_test_lower_case]).to eq(nil) - expect(ENV[chef_env_test_mixed_case]).to eq(nil) - end - - it "should delete a value from the current process even if it is not in the registry" do - expect(ENV[env_dne_key]).to eq(nil) - ENV[env_dne_key] = env_value1 - test_resource.key_name(env_dne_key) - test_resource.run_action(:delete) - expect(ENV[env_dne_key]).to eq(nil) - end - end - end -end diff --git a/spec/functional/resource/windows_env_spec.rb b/spec/functional/resource/windows_env_spec.rb new file mode 100644 index 0000000000..a6c6b39970 --- /dev/null +++ b/spec/functional/resource/windows_env_spec.rb @@ -0,0 +1,285 @@ +# +# Author:: Adam Edwards (<adamed@chef.io>) +# Copyright:: Copyright 2014-2016, 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" + +describe Chef::Resource::WindowsEnv, :windows_only do + context "when running on Windows" do + let(:chef_env_test_lower_case) { "chefenvtest" } + let(:chef_env_test_mixed_case) { "chefENVtest" } + let(:chef_env_with_delim) { "chef_env_with_delim" } + let(:chef_env_delim) { ";" } + let(:chef_env_test_delim) { "#{value1};#{value2}" } + let(:env_dne_key) { "env_dne_key" } + let(:env_value1) { "value1" } + let(:env_value2) { "value2" } + let(:delim_value) { "#{env_value1};#{env_value2}" } + let(:env_user) { ENV["USERNAME"].upcase } + let(:default_env_user) { "<SYSTEM>" } + + let(:env_obj) do + wmi = WmiLite::Wmi.new + environment_variables = wmi.query("select * from Win32_Environment where name = '#{test_resource.key_name}'") + if environment_variables && environment_variables.length > 0 + environment_variables.each do |env| + env_obj = env.wmi_ole_object + return env_obj if env_obj.username.split('\\').last.casecmp(test_resource.user) == 0 + end + end + nil + end + + let(:env_value_expandable) { "%SystemRoot%" } + let(:test_run_context) do + node = Chef::Node.new + node.default["os"] = "windows" + node.default["platform"] = "windows" + node.default["platform_version"] = "6.1" + empty_events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, empty_events) + end + let(:test_resource) do + Chef::Resource::WindowsEnv.new("unknown", test_run_context) + end + + before(:each) do + resource_lower = Chef::Resource::WindowsEnv.new(chef_env_test_lower_case, test_run_context) + resource_lower.run_action(:delete) + resource_lower = Chef::Resource::WindowsEnv.new(chef_env_test_lower_case, test_run_context) + resource_lower.user(env_user) + resource_lower.run_action(:delete) + resource_mixed = Chef::Resource::WindowsEnv.new(chef_env_test_mixed_case, test_run_context) + resource_mixed.run_action(:delete) + resource_mixed = Chef::Resource::WindowsEnv.new(chef_env_test_mixed_case, test_run_context) + resource_lower.user(env_user) + resource_mixed.run_action(:delete) + end + + context "when the create action is invoked" do + it "should create an environment variable for action create" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.run_action(:create) + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + end + + it "should create an environment variable with default user System for action create" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.run_action(:create) + expect(env_obj.username.upcase).to eq(default_env_user) + end + + it "should create an environment variable with user for action create" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.user(env_user) + test_resource.run_action(:create) + expect(env_obj.username.split('\\').last.upcase).to eq(env_user) + end + + context "when env variable exist with same name" do + before(:each) do + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.run_action(:create) + end + it "should modify an existing variable's value to a new value" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.value(env_value2) + test_resource.run_action(:create) + expect(ENV[chef_env_test_lower_case]).to eq(env_value2) + end + + it "should not modify an existing variable's value to a new value if the users are different" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.value(env_value2) + test_resource.user(env_user) + test_resource.run_action(:create) + test_resource.key_name(chef_env_test_lower_case) + test_resource.user(default_env_user) + expect(env_obj.variablevalue).to eq(env_value1) + end + + it "should modify an existing variable's value to a new value if the variable name case differs from the existing variable" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.key_name(chef_env_test_mixed_case) + test_resource.value(env_value2) + test_resource.run_action(:create) + expect(ENV[chef_env_test_lower_case]).to eq(env_value2) + end + end + + it "should not expand environment variables if the variable is not PATH" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value_expandable) + test_resource.run_action(:create) + expect(ENV[chef_env_test_lower_case]).to eq(env_value_expandable) + end + end + + context "when the modify action is invoked" do + it "should raise an exception for modify if the variable doesn't exist" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + expect { test_resource.run_action(:modify) }.to raise_error(Chef::Exceptions::WindowsEnv) + end + + context "when env variable exist with same name" do + before(:each) do + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.run_action(:create) + end + + it "should modify an existing variable's value to a new value" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.value(env_value2) + test_resource.run_action(:modify) + expect(ENV[chef_env_test_lower_case]).to eq(env_value2) + end + + # This examlpe covers Chef Issue #1754 + it "should modify an existing variable's value to a new value if the variable name case differs from the existing variable" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.key_name(chef_env_test_mixed_case) + test_resource.value(env_value2) + test_resource.run_action(:modify) + expect(ENV[chef_env_test_lower_case]).to eq(env_value2) + end + + it "should not expand environment variables if the variable is not PATH" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.value(env_value_expandable) + test_resource.run_action(:modify) + expect(ENV[chef_env_test_lower_case]).to eq(env_value_expandable) + end + end + + context "when using PATH" do + let(:random_name) { Time.now.to_i } + let(:env_val) { "#{env_value_expandable}_#{random_name}" } + let!(:path_before) { test_resource.provider_for_action(test_resource.action).env_value("PATH") || "" } + let!(:env_path_before) { ENV["PATH"] } + + it "should expand PATH" do + expect(path_before).not_to include(env_val) + test_resource.key_name("PATH") + test_resource.value("#{path_before};#{env_val}") + test_resource.run_action(:create) + expect(ENV["PATH"]).not_to include(env_val) + expect(ENV["PATH"]).to include("#{random_name}") + end + + after(:each) do + # cleanup so we don't flood the path + test_resource.key_name("PATH") + test_resource.value(path_before) + test_resource.run_action(:create) + ENV["PATH"] = env_path_before + end + end + + end + + context "when the delete action is invoked" do + context "when env variable exist with same name" do + before(:each) do + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.run_action(:create) + end + + it "should delete a System environment variable" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.run_action(:delete) + expect(ENV[chef_env_test_lower_case]).to eq(nil) + end + + it "should not delete an System environment variable if user are passed" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.user(env_user) + test_resource.run_action(:delete) + test_resource.user(default_env_user) + expect(env_obj).not_to be_nil + end + end + + context "when env variable exist with same name" do + before(:each) do + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.user(env_user) + test_resource.run_action(:create) + end + + it "should delete a user environment variable" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.run_action(:delete) + expect(env_obj).to eq(nil) + end + + it "should not delete an user environment variable if user is not passed" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.user(default_env_user) + test_resource.run_action(:delete) + test_resource.user(env_user) + expect(env_obj).not_to be_nil + end + end + + context "when env variable exist with same name" do + before(:each) do + test_resource.key_name(chef_env_with_delim) + test_resource.delim(chef_env_delim) + test_resource.value(delim_value) + test_resource.run_action(:create) + end + + it "should not delete variable when a delim present" do + expect(ENV[chef_env_with_delim]).to eq(delim_value) + test_resource.value(env_value1) + test_resource.run_action(:delete) + expect(ENV[chef_env_with_delim]).to eq(env_value2) + end + end + + it "should not raise an exception when a non-existent environment variable is deleted" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + expect { test_resource.run_action(:delete) }.not_to raise_error + expect(ENV[chef_env_test_lower_case]).to eq(nil) + end + + it "should delete a value from the current process even if it is not in the registry" do + expect(ENV[env_dne_key]).to eq(nil) + ENV[env_dne_key] = env_value1 + test_resource.key_name(env_dne_key) + test_resource.run_action(:delete) + expect(ENV[env_dne_key]).to eq(nil) + end + + end + end +end diff --git a/spec/integration/knife/chef_fs_data_store_spec.rb b/spec/integration/knife/chef_fs_data_store_spec.rb index 02508b799d..ff674082b7 100644 --- a/spec/integration/knife/chef_fs_data_store_spec.rb +++ b/spec/integration/knife/chef_fs_data_store_spec.rb @@ -28,6 +28,7 @@ describe "ChefFSDataStore tests", :workstation do let(:cookbook_x_100_metadata_rb) { cb_metadata("x", "1.0.0") } let(:cookbook_z_100_metadata_rb) { cb_metadata("z", "1.0.0") } + let(:cookbook_y_102_metadata_rb) { cb_metadata("z", "1.0.2") } describe "with repo mode 'hosted_everything' (default)" do before do @@ -39,6 +40,8 @@ describe "ChefFSDataStore tests", :workstation do file "clients/x.json", {} file "cookbook_artifacts/x-111/metadata.rb", cookbook_x_100_metadata_rb file "cookbooks/x/metadata.rb", cookbook_x_100_metadata_rb + file "cookbooks/y/metadata.rb", cookbook_y_102_metadata_rb + file "cookbooks/z/metadata.rb", cookbook_z_100_metadata_rb file "data_bags/x/y.json", {} file "environments/x.json", {} file "nodes/x.json", {} @@ -64,6 +67,7 @@ describe "ChefFSDataStore tests", :workstation do /acls/cookbook_artifacts/x.json /acls/cookbooks/ /acls/cookbooks/x.json +/acls/cookbooks/z.json /acls/data_bags/ /acls/data_bags/x.json /acls/environments/ @@ -84,11 +88,13 @@ describe "ChefFSDataStore tests", :workstation do /containers/ /containers/x.json /cookbook_artifacts/ -/cookbook_artifacts/x-111/ -/cookbook_artifacts/x-111/metadata.rb +/cookbook_artifacts/x-1.0.0/ +/cookbook_artifacts/x-1.0.0/metadata.rb /cookbooks/ /cookbooks/x/ /cookbooks/x/metadata.rb +/cookbooks/z/ +/cookbooks/z/metadata.rb /data_bags/ /data_bags/x/ /data_bags/x/y.json @@ -111,6 +117,12 @@ EOM end end + context "LIST /TYPE/NAME" do + it "knife cookbook show -z z" do + knife("cookbook show -z z").should_succeed "z 1.0.2 1.0.0\n" + end + end + context "DELETE /TYPE/NAME" do it "knife delete -z /clients/x.json works" do knife("delete -z /clients/x.json").should_succeed "Deleted /clients/x.json\n" @@ -119,7 +131,7 @@ EOM it "knife delete -z -r /cookbooks/x works" do knife("delete -z -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" - knife("list -z -Rfp /cookbooks").should_succeed "" + knife("list -z -Rfp /cookbooks").should_succeed "/cookbooks/z/\n/cookbooks/z/metadata.rb\n" end it "knife delete -z -r /data_bags/x works" do @@ -194,7 +206,14 @@ EOM Uploading x [1.0.0] Uploaded 1 cookbook. EOM - knife("list --local -Rfp /cookbooks").should_succeed "/cookbooks/x/\n/cookbooks/x/metadata.rb\n" + knife("list --local -Rfp /cookbooks").should_succeed <<EOM +/cookbooks/x/ +/cookbooks/x/metadata.rb +/cookbooks/y/ +/cookbooks/y/metadata.rb +/cookbooks/z/ +/cookbooks/z/metadata.rb +EOM end it "knife raw -z -i empty.json -m PUT /data/x/y" do diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb index e952a5448a..c892b24f28 100644 --- a/spec/unit/exceptions_spec.rb +++ b/spec/unit/exceptions_spec.rb @@ -25,7 +25,7 @@ describe Chef::Exceptions do exception_to_super_class = { Chef::Exceptions::Application => RuntimeError, Chef::Exceptions::Cron => RuntimeError, - Chef::Exceptions::Env => RuntimeError, + Chef::Exceptions::WindowsEnv => RuntimeError, Chef::Exceptions::Exec => RuntimeError, Chef::Exceptions::FileNotFound => RuntimeError, Chef::Exceptions::Package => RuntimeError, diff --git a/spec/unit/knife/configure_spec.rb b/spec/unit/knife/configure_spec.rb index f1d3bd0745..27bb0d9a3f 100644 --- a/spec/unit/knife/configure_spec.rb +++ b/spec/unit/knife/configure_spec.rb @@ -26,8 +26,7 @@ describe Chef::Knife::Configure do let(:ohai) do o = {} - allow(o).to receive(:require_plugin) - allow(o).to receive(:load_plugins) + allow(o).to receive(:all_plugins).with(%w{ os hostname fqdn }) o[:fqdn] = fqdn o end diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb index 0cafb925c8..7bc8a27398 100644 --- a/spec/unit/mixin/params_validate_spec.rb +++ b/spec/unit/mixin/params_validate_spec.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob (<adam@chef.io>) -# Copyright:: Copyright 2008-2016, Chef Software Inc. +# Copyright:: Copyright 2008-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -342,6 +342,17 @@ describe Chef::Mixin::ParamsValidate do end.to raise_error(Chef::Exceptions::ValidationFailed) end + it "allows a custom validation message" do + expect do + @vo.validate({ :not_blank => "should pass" }, + { :not_blank => { :cannot_be => [ :nil, :empty ], validation_message: "my validation message" } }) + end.not_to raise_error + expect do + @vo.validate({ :not_blank => "" }, + { :not_blank => { :cannot_be => [ :nil, :empty ], validation_message: "my validation message" } }) + end.to raise_error(Chef::Exceptions::ValidationFailed, "my validation message") + end + it "should set and return a value, then return the same value" do value = "meow" expect(@vo.set_or_return(:test, value, {}).object_id).to eq(value.object_id) diff --git a/spec/unit/mixin/properties_spec.rb b/spec/unit/mixin/properties_spec.rb index 1af0bc7abd..ee0c252381 100644 --- a/spec/unit/mixin/properties_spec.rb +++ b/spec/unit/mixin/properties_spec.rb @@ -11,6 +11,7 @@ module ChefMixinPropertiesSpec property :a, "a", default: "a" property :ab, %w{a b}, default: "a" property :ac, %w{a c}, default: "a" + property :d, "d", description: "The d property", introduced: "14.0" end context "and a module B with properties b, ab and bc" do @@ -30,11 +31,20 @@ module ChefMixinPropertiesSpec end it "A.properties has a, ab, and ac with types 'a', ['a', 'b'], and ['b', 'c']" do - expect(A.properties.keys).to eq [ :a, :ab, :ac ] + expect(A.properties.keys).to eq [ :a, :ab, :ac, :d ] expect(A.properties[:a].validation_options[:is]).to eq "a" expect(A.properties[:ab].validation_options[:is]).to eq %w{a b} expect(A.properties[:ac].validation_options[:is]).to eq %w{a c} end + + it "A.properties can get the description of `d`" do + expect(A.properties[:d].description).to eq "The d property" + end + + it "A.properties can get the release that introduced `d`" do + expect(A.properties[:d].introduced).to eq "14.0" + end + it "B.properties has b, ab, and bc with types 'b', nil and ['b', 'c']" do expect(B.properties.keys).to eq [ :b, :ab, :bc ] expect(B.properties[:b].validation_options[:is]).to eq "b" @@ -42,7 +52,7 @@ module ChefMixinPropertiesSpec expect(B.properties[:bc].validation_options[:is]).to eq %w{b c} end it "C.properties has a, b, c, ac and bc with merged types" do - expect(C.properties.keys).to eq [ :a, :ab, :ac, :b, :bc, :c ] + expect(C.properties.keys).to eq [ :a, :ab, :ac, :d, :b, :bc, :c ] expect(C.properties[:a].validation_options[:is]).to eq "a" expect(C.properties[:b].validation_options[:is]).to eq "b" expect(C.properties[:c].validation_options[:is]).to eq "c" diff --git a/spec/unit/node_map_spec.rb b/spec/unit/node_map_spec.rb index 67bb741ec5..24f3bebe2a 100644 --- a/spec/unit/node_map_spec.rb +++ b/spec/unit/node_map_spec.rb @@ -19,6 +19,9 @@ require "spec_helper" require "chef/node_map" +class Foo; end +class Bar; end + describe Chef::NodeMap do let(:node_map) { Chef::NodeMap.new } @@ -120,8 +123,6 @@ describe Chef::NodeMap do end describe "ordering classes" do - class Foo; end - class Bar; end it "last writer wins when its reverse alphabetic order" do node_map.set(:thing, Foo) node_map.set(:thing, Bar) @@ -135,6 +136,30 @@ describe Chef::NodeMap do end end + describe "deleting classes" do + it "deletes a class and removes the mapping completely" do + node_map.set(:thing, Bar) + expect( node_map.delete_class(Bar) ).to eql({ :thing => [{ :klass => Bar }] }) + expect( node_map.get(node, :thing) ).to eql(nil) + end + + it "deletes a class and leaves the mapping that still has an entry" do + node_map.set(:thing, Bar) + node_map.set(:thing, Foo) + expect( node_map.delete_class(Bar) ).to eql({ :thing => [{ :klass => Bar }] }) + expect( node_map.get(node, :thing) ).to eql(Foo) + end + + it "handles deleting classes from multiple keys" do + node_map.set(:thing1, Bar) + node_map.set(:thing2, Bar) + node_map.set(:thing2, Foo) + expect( node_map.delete_class(Bar) ).to eql({ :thing1 => [{ :klass => Bar }], :thing2 => [{ :klass => Bar }] }) + expect( node_map.get(node, :thing1) ).to eql(nil) + expect( node_map.get(node, :thing2) ).to eql(Foo) + end + end + describe "with a block doing platform_version checks" do before do node_map.set(:thing, :foo, platform_family: "rhel") do |node| diff --git a/spec/unit/property/validation_spec.rb b/spec/unit/property/validation_spec.rb index 13afcdfbc2..882ea3353b 100644 --- a/spec/unit/property/validation_spec.rb +++ b/spec/unit/property/validation_spec.rb @@ -699,4 +699,13 @@ describe "Chef::Resource.property validation" do end end end + + context "custom validation messages" do + with_property ":x, String, validation_message: 'Must be a string, fool'" do + it "raise with the correct error message" do + expect { resource.x 1 }.to raise_error Chef::Exceptions::ValidationFailed, + "Must be a string, fool" + end + end + end end diff --git a/spec/unit/provider/env/windows_spec.rb b/spec/unit/provider/env/windows_spec.rb deleted file mode 100644 index 5ddc1d6f91..0000000000 --- a/spec/unit/provider/env/windows_spec.rb +++ /dev/null @@ -1,103 +0,0 @@ -# -# Author:: Sander van Harmelen <svanharmelen@schubergphilis.com> -# Copyright:: Copyright 2014-2016, 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" - -describe Chef::Provider::Env::Windows, :windows_only do - let(:node) { Chef::Node.new } - let(:events) { Chef::EventDispatch::Dispatcher.new } - let(:run_context) { Chef::RunContext.new(node, {}, events) } - - context "when environment variable is not PATH" do - let(:new_resource) do - new_resource = Chef::Resource::Env.new("CHEF_WINDOWS_ENV_TEST") - new_resource.value("foo") - new_resource - end - let(:provider) do - provider = Chef::Provider::Env::Windows.new(new_resource, run_context) - allow(provider).to receive(:env_obj).and_return(double("null object").as_null_object) - provider - end - - describe "action_create" do - before do - ENV.delete("CHEF_WINDOWS_ENV_TEST") - provider.key_exists = false - end - - it "should update the ruby ENV object when it creates the key" do - provider.action_create - expect(ENV["CHEF_WINDOWS_ENV_TEST"]).to eql("foo") - end - end - - describe "action_modify" do - before do - ENV["CHEF_WINDOWS_ENV_TEST"] = "foo" - end - - it "should update the ruby ENV object when it updates the value" do - expect(provider).to receive(:requires_modify_or_create?).and_return(true) - new_resource.value("foobar") - provider.action_modify - expect(ENV["CHEF_WINDOWS_ENV_TEST"]).to eql("foobar") - end - - describe "action_delete" do - before do - ENV["CHEF_WINDOWS_ENV_TEST"] = "foo" - end - - it "should update the ruby ENV object when it deletes the key" do - provider.action_delete - expect(ENV["CHEF_WINDOWS_ENV_TEST"]).to eql(nil) - end - end - end - end - - context "when environment is PATH" do - describe "for PATH" do - let(:system_root) { "%SystemRoot%" } - let(:system_root_value) { 'D:\Windows' } - let(:new_resource) do - new_resource = Chef::Resource::Env.new("PATH") - new_resource.value(system_root) - new_resource - end - let(:provider) do - provider = Chef::Provider::Env::Windows.new(new_resource, run_context) - allow(provider).to receive(:env_obj).and_return(double("null object").as_null_object) - provider - end - - before do - stub_const("ENV", { "PATH" => "" }) - end - - it "replaces Windows system variables" do - expect(provider).to receive(:requires_modify_or_create?).and_return(true) - expect(provider).to receive(:expand_path).with(system_root).and_return(system_root_value) - provider.action_modify - expect(ENV["PATH"]).to eql(system_root_value) - end - end - - end -end diff --git a/spec/unit/provider/remote_file/http_spec.rb b/spec/unit/provider/remote_file/http_spec.rb index ec9c0cf2e3..e1f74bd35a 100644 --- a/spec/unit/provider/remote_file/http_spec.rb +++ b/spec/unit/provider/remote_file/http_spec.rb @@ -185,14 +185,12 @@ describe Chef::Provider::RemoteFile::HTTP do expect(Chef::HTTP::Simple).to receive(:new).with(*expected_http_args).and_return(rest) end - describe "and the request does not return new content" do - - it "should return a nil tempfile for a 304 HTTPNotModifed" do - # Streaming request returns nil for 304 errors - expect(rest).to receive(:streaming_request).with(uri, {}, tempfile).and_return(nil) - expect(fetcher.fetch).to be_nil - end - + it "should clean up the tempfile, and return a nil when streaming_request returns nil" do + # Streaming request returns nil for a 304 not modified (etags / last-modified) + expect(rest).to receive(:streaming_request).with(uri, {}, tempfile).and_return(nil) + expect(tempfile).to receive(:close) + expect(tempfile).to receive(:unlink) + expect(fetcher.fetch).to be_nil end context "with progress reports" do diff --git a/spec/unit/provider/systemd_unit_spec.rb b/spec/unit/provider/systemd_unit_spec.rb index 8e19503814..15d5944992 100644 --- a/spec/unit/provider/systemd_unit_spec.rb +++ b/spec/unit/provider/systemd_unit_spec.rb @@ -1,6 +1,6 @@ # # Author:: Nathan Williams (<nath.e.will@gmail.com>) -# Copyright:: Copyright (c), Nathan Williams +# Copyright:: Copyright 2016-2018, Nathan Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -250,7 +250,7 @@ describe Chef::Provider::SystemdUnit do .and_return(systemctl_path) end - describe "creates/deletes the unit" do + describe "creates/deletes/presets/reverts the unit" do it "creates the unit file when it does not exist" do allow(provider).to receive(:manage_unit_file) .with(:create) @@ -345,6 +345,22 @@ describe Chef::Provider::SystemdUnit do expect(provider).to_not receive(:manage_unit_file) provider.action_delete end + + it "presets the unit" do + new_resource.user("joe") + expect(provider).to receive(:shell_out_with_systems_locale!) + .with("#{systemctl_path} --user preset #{unit_name_escaped}", user_cmd_opts) + .and_return(shell_out_success) + provider.action_preset + end + + it "reverts the unit" do + new_resource.user("joe") + expect(provider).to receive(:shell_out_with_systems_locale!) + .with("#{systemctl_path} --user revert #{unit_name_escaped}", user_cmd_opts) + .and_return(shell_out_success) + provider.action_revert + end end context "when no user is specified" do @@ -368,11 +384,33 @@ describe Chef::Provider::SystemdUnit do expect(provider).to_not receive(:manage_unit_file) provider.action_delete end + + it "presets the unit" do + expect(provider).to receive(:shell_out_with_systems_locale!) + .with("#{systemctl_path} --system preset #{unit_name_escaped}", {}) + .and_return(shell_out_success) + provider.action_preset + end + + it "reverts the unit" do + expect(provider).to receive(:shell_out_with_systems_locale!) + .with("#{systemctl_path} --system revert #{unit_name_escaped}", {}) + .and_return(shell_out_success) + provider.action_revert + end end end - describe "enables/disables the unit" do + describe "enables/disables/reenables the unit" do context "when a user is specified" do + it "reenables the unit" do + current_resource.user(user_name) + expect(provider).to receive(:shell_out_with_systems_locale!) + .with("#{systemctl_path} --user reenable #{unit_name_escaped}", user_cmd_opts) + .and_return(shell_out_success) + provider.action_reenable + end + it "enables the unit when it is disabled" do current_resource.user(user_name) current_resource.enabled(false) @@ -421,6 +459,13 @@ describe Chef::Provider::SystemdUnit do end context "when no user is specified" do + it "reenables the unit" do + expect(provider).to receive(:shell_out_with_systems_locale!) + .with("#{systemctl_path} --system reenable #{unit_name_escaped}", {}) + .and_return(shell_out_success) + provider.action_reenable + end + it "enables the unit when it is disabled" do current_resource.enabled(false) expect(provider).to receive(:shell_out_with_systems_locale!) diff --git a/spec/unit/provider/env_spec.rb b/spec/unit/provider/windows_env_spec.rb index fd52c5a8ed..a42a0509c5 100644 --- a/spec/unit/provider/env_spec.rb +++ b/spec/unit/provider/windows_env_spec.rb @@ -18,15 +18,16 @@ require "spec_helper" -describe Chef::Provider::Env do +describe Chef::Provider::WindowsEnv, :windows_only do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) - @new_resource = Chef::Resource::Env.new("FOO") + @new_resource = Chef::Resource::WindowsEnv.new("FOO") @new_resource.value("bar") - @provider = Chef::Provider::Env.new(@new_resource, @run_context) + @new_resource.user("<System>") + @provider = Chef::Provider::WindowsEnv.new(@new_resource, @run_context) end it "assumes the key_name exists by default" do @@ -47,12 +48,17 @@ describe Chef::Provider::Env do expect(@provider.new_resource.name).to eq("FOO") end + it "should create a current resource with the same user as the new resource" do + @provider.load_current_resource + expect(@provider.new_resource.user).to eq("<System>") + end + it "should set the key_name to the key name of the new resource" do @provider.load_current_resource expect(@provider.current_resource.key_name).to eq("FOO") end - it "should check if the key_name exists" do + it "should check if the key_name and user exist" do expect(@provider).to receive(:env_key_exists).with("FOO").and_return(true) @provider.load_current_resource expect(@provider.key_exists).to be_truthy @@ -65,7 +71,7 @@ describe Chef::Provider::Env do end it "should return the current resource" do - expect(@provider.load_current_resource).to be_a_kind_of(Chef::Resource::Env) + expect(@provider.load_current_resource).to be_a_kind_of(Chef::Resource::WindowsEnv) end end @@ -76,7 +82,7 @@ describe Chef::Provider::Env do allow(@provider).to receive(:modify_env).and_return(true) end - it "should call create_env if the key does not exist" do + it "should call create_env if the key does not exist with user" do expect(@provider).to receive(:create_env).and_return(true) @provider.action_create end @@ -92,7 +98,7 @@ describe Chef::Provider::Env do @provider.action_create end - it "should call modify_env if the key exists and values are not equal" do + it "should call modify_env if the key exists with provided user and values are not equal" do @provider.key_exists = true allow(@provider).to receive(:requires_modify_or_create?).and_return(true) expect(@provider).to receive(:modify_env).and_return(true) @@ -152,6 +158,12 @@ describe Chef::Provider::Env do @provider.action_modify end + it "should call modify_group if the key exists and users are not equal" do + expect(@provider).to receive(:requires_modify_or_create?).and_return(true) + expect(@provider).to receive(:modify_env).and_return(true) + @provider.action_modify + end + it "should set the new resources updated flag to true if modify_env is called" do allow(@provider).to receive(:requires_modify_or_create?).and_return(true) allow(@provider).to receive(:modify_env).and_return(true) @@ -159,25 +171,26 @@ describe Chef::Provider::Env do expect(@new_resource).to be_updated end - it "should not call modify_env if the key exists but the values are equal" do + it "should not call modify_env if the key exists with user but the values are equal" do expect(@provider).to receive(:requires_modify_or_create?).and_return(false) expect(@provider).not_to receive(:modify_env) @provider.action_modify end - it "should raise a Chef::Exceptions::Env if the key doesn't exist" do + it "should raise a Chef::Exceptions::WindowsEnv if the key doesn't exist" do @provider.key_exists = false - expect { @provider.action_modify }.to raise_error(Chef::Exceptions::Env) + expect { @provider.action_modify }.to raise_error(Chef::Exceptions::WindowsEnv) end end describe "delete_element" do before(:each) do - @current_resource = Chef::Resource::Env.new("FOO") + @current_resource = Chef::Resource::WindowsEnv.new("FOO") @new_resource.delim ";" @new_resource.value "C:/bar/bin" + @current_resource.user "<System>" @current_resource.value "C:/foo/bin;C:/bar/bin" @provider.current_resource = @current_resource end @@ -280,7 +293,7 @@ describe Chef::Provider::Env do allow(@provider).to receive(:create_env).and_return(true) @new_resource.delim ";" - @current_resource = Chef::Resource::Env.new("FOO") + @current_resource = Chef::Resource::WindowsEnv.new("FOO") @current_resource.value "C:/foo/bin" @provider.current_resource = @current_resource end @@ -307,4 +320,81 @@ describe Chef::Provider::Env do expect(@new_resource.value).to eq("C:/foo;C:/bar;C:/baz;C:/foo/bar") end end + + context "when environment variable is not PATH" do + let(:new_resource) do + new_resource = Chef::Resource::WindowsEnv.new("CHEF_WINDOWS_ENV_TEST") + new_resource.value("foo") + new_resource + end + let(:provider) do + provider = Chef::Provider::WindowsEnv.new(new_resource, run_context) + allow(provider).to receive(:env_obj).and_return(double("null object").as_null_object) + provider + end + + describe "action_create" do + before do + ENV.delete("CHEF_WINDOWS_ENV_TEST") + provider.key_exists = false + end + + it "should update the ruby ENV object when it creates the key" do + provider.action_create + expect(ENV["CHEF_WINDOWS_ENV_TEST"]).to eql("foo") + end + end + + describe "action_modify" do + before do + ENV["CHEF_WINDOWS_ENV_TEST"] = "foo" + end + + it "should update the ruby ENV object when it updates the value" do + expect(provider).to receive(:requires_modify_or_create?).and_return(true) + new_resource.value("foobar") + provider.action_modify + expect(ENV["CHEF_WINDOWS_ENV_TEST"]).to eql("foobar") + end + + describe "action_delete" do + before do + ENV["CHEF_WINDOWS_ENV_TEST"] = "foo" + end + + it "should update the ruby ENV object when it deletes the key" do + provider.action_delete + expect(ENV["CHEF_WINDOWS_ENV_TEST"]).to eql(nil) + end + end + end + end + + context "when environment is PATH" do + describe "for PATH" do + let(:system_root) { "%SystemRoot%" } + let(:system_root_value) { 'D:\Windows' } + let(:new_resource) do + new_resource = Chef::Resource::WindowsEnv.new("PATH") + new_resource.value(system_root) + new_resource + end + let(:provider) do + provider = Chef::Provider::WindowsEnv.new(new_resource, run_context) + allow(provider).to receive(:env_obj).and_return(double("null object").as_null_object) + provider + end + + before do + stub_const("ENV", { "PATH" => "" }) + end + + it "replaces Windows system variables" do + expect(provider).to receive(:requires_modify_or_create?).and_return(true) + expect(provider).to receive(:expand_path).with(system_root).and_return(system_root_value) + provider.action_modify + expect(ENV["PATH"]).to eql(system_root_value) + end + end + end end diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb index a331093055..ac641de43a 100644 --- a/spec/unit/provider_resolver_spec.rb +++ b/spec/unit/provider_resolver_spec.rb @@ -762,7 +762,7 @@ describe Chef::ProviderResolver do "windows" => { batch: [ Chef::Resource::Batch, Chef::Provider::Batch ], dsc_script: [ Chef::Resource::DscScript, Chef::Provider::DscScript ], - env: [ Chef::Resource::Env, Chef::Provider::Env::Windows ], + windows_env: [ Chef::Resource::WindowsEnv, Chef::Provider::WindowsEnv ], group: [ Chef::Resource::Group, Chef::Provider::Group::Windows ], mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Windows ], package: [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ], diff --git a/spec/unit/resource/hostname_spec.rb b/spec/unit/resource/hostname_spec.rb new file mode 100644 index 0000000000..33f944dbc9 --- /dev/null +++ b/spec/unit/resource/hostname_spec.rb @@ -0,0 +1,43 @@ +# +# Copyright:: Copyright 2018, 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" + +describe Chef::Resource::Hostname do + + let(:resource) { Chef::Resource::Hostname.new("foo") } + + it "has a resource name of :hostname" do + expect(resource.resource_name).to eql(:hostname) + end + + it "has a default action of set" do + expect(resource.action).to eql([:set]) + end + + it "runs at compile_time by default" do + expect(resource.compile_time).to eql(true) + end + + it "reboots windows nodes by default" do + expect(resource.windows_reboot).to eql(true) + end + + it "the hostname property is the name property" do + expect(resource.hostname).to eql("foo") + end +end diff --git a/spec/unit/resource/rhsm_errata_level_spec.rb b/spec/unit/resource/rhsm_errata_level_spec.rb new file mode 100644 index 0000000000..3284107e75 --- /dev/null +++ b/spec/unit/resource/rhsm_errata_level_spec.rb @@ -0,0 +1,46 @@ +# +# Copyright:: Copyright 2018, 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" + +describe Chef::Resource::RhsmErrataLevel do + + let(:resource) { Chef::Resource::RhsmErrataLevel.new("moderate") } + + it "has a resource name of :rhsm_errata_level" do + expect(resource.resource_name).to eql(:rhsm_errata_level) + end + + it "has a default action of install" do + expect(resource.action).to eql([:install]) + end + + it "the errata_level property is the name property" do + expect(resource.errata_level).to eql("moderate") + end + + it "coerces the errata_level to be lowercase" do + resource.errata_level "Important" + expect(resource.errata_level).to eql("important") + end + + it "raises an exception if invalid errata_level is passed" do + expect do + resource.errata_level "FOO" + end.to raise_error(Chef::Exceptions::ValidationFailed) + end +end diff --git a/spec/unit/resource/rhsm_errata_spec.rb b/spec/unit/resource/rhsm_errata_spec.rb new file mode 100644 index 0000000000..8da7269ca8 --- /dev/null +++ b/spec/unit/resource/rhsm_errata_spec.rb @@ -0,0 +1,35 @@ +# +# Copyright:: Copyright 2018, 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" + +describe Chef::Resource::RhsmErrata do + + let(:resource) { Chef::Resource::RhsmErrata.new("foo") } + + it "has a resource name of :rhsm_errata" do + expect(resource.resource_name).to eql(:rhsm_errata) + end + + it "has a default action of install" do + expect(resource.action).to eql([:install]) + end + + it "the errata_id property is the name property" do + expect(resource.errata_id).to eql("foo") + end +end diff --git a/spec/unit/resource/rhsm_register_spec.rb b/spec/unit/resource/rhsm_register_spec.rb new file mode 100644 index 0000000000..2e360b5708 --- /dev/null +++ b/spec/unit/resource/rhsm_register_spec.rb @@ -0,0 +1,199 @@ +# +# Copyright:: Copyright 2018, 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" + +describe Chef::Resource::RhsmRegister do + + let(:resource) { Chef::Resource::RhsmRegister.new("foo") } + let(:provider) { resource.provider_for_action(:register) } + + it "has a resource name of :rhsm_register" do + expect(resource.resource_name).to eql(:rhsm_register) + end + + it "has a default action of register" do + expect(resource.action).to eql([:register]) + end + + it "coerces activation_key to an array" do + resource.activation_key "foo" + expect(resource.activation_key).to eql(["foo"]) + end + + describe "#katello_cert_rpm_installed?" do + let(:cmd) { double("cmd") } + + before do + allow(Mixlib::ShellOut).to receive(:new).and_return(cmd) + allow(cmd).to receive(:run_command) + end + + context "when the output contains katello-ca-consumer" do + it "returns true" do + allow(cmd).to receive(:stdout).and_return("katello-ca-consumer-somehostname-1.0-1") + expect(provider.katello_cert_rpm_installed?).to eq(true) + end + end + + context "when the output does not contain katello-ca-consumer" do + it "returns false" do + allow(cmd).to receive(:stdout).and_return("katello-agent-but-not-the-ca") + expect(provider.katello_cert_rpm_installed?).to eq(false) + end + end + end + + describe "#register_command" do + before do + allow(provider).to receive(:activation_key).and_return([]) + allow(provider).to receive(:auto_attach) + end + + context "when activation keys exist" do + before do + allow(resource).to receive(:activation_key).and_return(%w{key1 key2}) + end + + context "when no org exists" do + it "raises an exception" do + allow(resource).to receive(:organization).and_return(nil) + expect { provider.register_command }.to raise_error(RuntimeError) + end + end + + context "when an org exists" do + it "returns a command containing the keys and org" do + allow(resource).to receive(:organization).and_return("myorg") + + expect(provider.register_command).to match("--activationkey=key1 --activationkey=key2 --org=myorg") + end + end + + context "when auto_attach is true" do + it "does not return a command with --auto-attach since it is not supported with activation keys" do + allow(resource).to receive(:organization).and_return("myorg") + allow(resource).to receive(:auto_attach).and_return(true) + + expect(provider.register_command).not_to match("--auto-attach") + end + end + end + + context "when username and password exist" do + before do + allow(resource).to receive(:username).and_return("myuser") + allow(resource).to receive(:password).and_return("mypass") + allow(resource).to receive(:environment) + allow(resource).to receive(:using_satellite_host?) + allow(resource).to receive(:activation_key).and_return([]) + end + + context "when auto_attach is true" do + it "returns a command containing --auto-attach" do + allow(resource).to receive(:auto_attach).and_return(true) + + expect(provider.register_command).to match("--auto-attach") + end + end + + context "when auto_attach is false" do + it "returns a command that does not contain --auto-attach" do + allow(resource).to receive(:auto_attach).and_return(false) + + expect(provider.register_command).not_to match("--auto-attach") + end + end + + context "when auto_attach is nil" do + it "returns a command that does not contain --auto-attach" do + allow(resource).to receive(:auto_attach).and_return(nil) + + expect(provider.register_command).not_to match("--auto-attach") + end + end + + context "when environment does not exist" do + context "when registering to a satellite server" do + it "raises an exception" do + allow(provider).to receive(:using_satellite_host?).and_return(true) + allow(resource).to receive(:environment).and_return(nil) + expect { provider.register_command }.to raise_error(RuntimeError) + end + end + + context "when registering to RHSM proper" do + before do + allow(provider).to receive(:using_satellite_host?).and_return(false) + allow(resource).to receive(:environment).and_return(nil) + end + + it "does not raise an exception" do + expect { provider.register_command }.not_to raise_error + end + + it "returns a command containing the username and password and no environment" do + allow(resource).to receive(:environment).and_return("myenv") + expect(provider.register_command).to match("--username=myuser --password=mypass") + expect(provider.register_command).not_to match("--environment") + end + end + end + + context "when an environment exists" do + it "returns a command containing the username, password, and environment" do + allow(provider).to receive(:using_satellite_host?).and_return(true) + allow(resource).to receive(:environment).and_return("myenv") + expect(provider.register_command).to match("--username=myuser --password=mypass --environment=myenv") + end + end + end + + context "when no activation keys, username, or password exist" do + it "raises an exception" do + allow(resource).to receive(:activation_key).and_return([]) + allow(resource).to receive(:username).and_return(nil) + allow(resource).to receive(:password).and_return(nil) + + expect { provider.register_command }.to raise_error(RuntimeError) + end + end + end + + describe "#registered_with_rhsm?" do + let(:cmd) { double("cmd") } + + before do + allow(Mixlib::ShellOut).to receive(:new).and_return(cmd) + allow(cmd).to receive(:run_command) + end + + context "when the status is Unknown" do + it "returns false" do + allow(cmd).to receive(:stdout).and_return("Overall Status: Unknown") + expect(provider.registered_with_rhsm?).to eq(false) + end + end + + context "when the status is anything else" do + it "returns true" do + allow(cmd).to receive(:stdout).and_return("Overall Status: Insufficient") + expect(provider.registered_with_rhsm?).to eq(true) + end + end + end +end diff --git a/spec/unit/resource/rhsm_repo_spec.rb b/spec/unit/resource/rhsm_repo_spec.rb new file mode 100644 index 0000000000..97606a03c8 --- /dev/null +++ b/spec/unit/resource/rhsm_repo_spec.rb @@ -0,0 +1,59 @@ +# +# Copyright:: Copyright 2018, 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" + +describe Chef::Resource::RhsmRepo do + + let(:resource) { Chef::Resource::RhsmRepo.new("foo") } + let(:provider) { resource.provider_for_action(:enable) } + + it "has a resource name of :rhsm_repo" do + expect(resource.resource_name).to eql(:rhsm_repo) + end + + it "has a default action of enable" do + expect(resource.action).to eql([:enable]) + end + + it "the repo_name property is the name property" do + expect(resource.repo_name).to eql("foo") + end + + describe "#repo_enabled?" do + let(:cmd) { double("cmd") } + let(:output) { "Repo ID: repo123" } + + before do + allow(Mixlib::ShellOut).to receive(:new).and_return(cmd) + allow(cmd).to receive(:run_command) + allow(cmd).to receive(:stdout).and_return(output) + end + + context "when the repo provided matches the output" do + it "returns true" do + expect(provider.repo_enabled?("repo123")).to eq(true) + end + end + + context "when the repo provided does not match the output" do + it "returns false" do + expect(provider.repo_enabled?("differentrepo")).to eq(false) + end + end + end +end diff --git a/spec/unit/resource/rhsm_subscription_spec.rb b/spec/unit/resource/rhsm_subscription_spec.rb new file mode 100644 index 0000000000..0160624f39 --- /dev/null +++ b/spec/unit/resource/rhsm_subscription_spec.rb @@ -0,0 +1,93 @@ +# +# Copyright:: Copyright 2018, 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" + +describe Chef::Resource::RhsmSubscription do + let(:resource) { Chef::Resource::RhsmSubscription.new("foo") } + let(:provider) { resource.provider_for_action(:attach) } + + it "has a resource name of :rhsm_subscription" do + expect(resource.resource_name).to eql(:rhsm_subscription) + end + + it "has a default action of attach" do + expect(resource.action).to eql([:attach]) + end + + it "the pool_id property is the name property" do + expect(resource.pool_id).to eql("foo") + end + + describe "#subscription_attached?" do + let(:cmd) { double("cmd") } + let(:output) { "Pool ID: pool123" } + + before do + allow(Mixlib::ShellOut).to receive(:new).and_return(cmd) + allow(cmd).to receive(:run_command) + allow(cmd).to receive(:stdout).and_return(output) + end + + context "when the pool provided matches the output" do + it "returns true" do + expect(provider.subscription_attached?("pool123")).to eq(true) + end + end + + context "when the pool provided does not match the output" do + it "returns false" do + expect(provider.subscription_attached?("differentpool")).to eq(false) + end + end + end + + describe "#serials_by_pool" do + let(:cmd) { double("cmd") } + let(:output) do + <<~EOL + Key1: value1 + Pool ID: pool1 + Serial: serial1 + Key2: value2 + + Key1: value1 + Pool ID: pool2 + Serial: serial2 + Key2: value2 +EOL + end + + it "parses the output correctly" do + allow(Mixlib::ShellOut).to receive(:new).and_return(cmd) + allow(cmd).to receive(:run_command) + allow(cmd).to receive(:stdout).and_return(output) + + expect(provider.serials_by_pool["pool1"]).to eq("serial1") + expect(provider.serials_by_pool["pool2"]).to eq("serial2") + end + end + + describe "#pool_serial" do + let(:serials) { { "pool1" => "serial1", "pool2" => "serial2" } } + + it "returns the serial for a given pool" do + allow(provider).to receive(:serials_by_pool).and_return(serials) + expect(provider.pool_serial("pool1")).to eq("serial1") + end + end +end diff --git a/spec/unit/resource/systemd_unit_spec.rb b/spec/unit/resource/systemd_unit_spec.rb index 15b792e6bf..9d156402a1 100644 --- a/spec/unit/resource/systemd_unit_spec.rb +++ b/spec/unit/resource/systemd_unit_spec.rb @@ -1,6 +1,6 @@ # # Author:: Nathan Williams (<nath.e.will@gmail.com>) -# Copyright:: Copyright 2016, Nathan Williams +# Copyright:: Copyright 2016-2018, Nathan Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,8 +46,11 @@ describe Chef::Resource::SystemdUnit do it "supports appropriate unit actions" do expect { resource.action :create }.not_to raise_error expect { resource.action :delete }.not_to raise_error + expect { resource.action :preset }.not_to raise_error + expect { resource.action :revert }.not_to raise_error expect { resource.action :enable }.not_to raise_error expect { resource.action :disable }.not_to raise_error + expect { resource.action :reenable }.not_to raise_error expect { resource.action :mask }.not_to raise_error expect { resource.action :unmask }.not_to raise_error expect { resource.action :start }.not_to raise_error diff --git a/spec/unit/resource/env_spec.rb b/spec/unit/resource/windows_env_spec.rb index 61c40e33e8..776e4f7cd5 100644 --- a/spec/unit/resource/env_spec.rb +++ b/spec/unit/resource/windows_env_spec.rb @@ -19,10 +19,16 @@ require "spec_helper" -describe Chef::Resource::Env do - let(:resource) { Chef::Resource::Env.new("FOO") } +describe Chef::Resource::WindowsEnv do - it "has a name property" do + let(:resource) { Chef::Resource::WindowsEnv.new("FOO") } + + it "creates a new Chef::Resource::WindowsEnv" do + expect(resource).to be_a_kind_of(Chef::Resource) + expect(resource).to be_a_kind_of(Chef::Resource::WindowsEnv) + end + + it "has a name" do expect(resource.name).to eql("FOO") end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index 934a613eee..fe853922a1 100644 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -387,6 +387,37 @@ describe Chef::Resource do end end + context "Documentation of resources" do + it "can have a description" do + c = Class.new(Chef::Resource) do + description "my description" + end + expect(c.description).to eq "my description" + end + + it "can say when it was introduced" do + c = Class.new(Chef::Resource) do + introduced "14.0" + end + expect(c.introduced).to eq "14.0" + end + + it "can have some examples" do + c = Class.new(Chef::Resource) do + examples <<-EOH +resource "foo" do + foo foo +end + EOH + end + expect(c.examples).to eq <<-EOH +resource "foo" do + foo foo +end + EOH + end + end + describe "self.resource_name" do context "When resource_name is not set" do it "and there are no provides lines, resource_name is nil" do |