diff options
25 files changed, 887 insertions, 129 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ae939cfae..79deb00ffa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,24 @@ <!-- usage documentation: http://expeditor-docs.es.chef.io/configuration/changelog/ --> -<!-- latest_release 15.0.13 --> -## [v15.0.13](https://github.com/chef/chef/tree/v15.0.13) (2018-10-29) +<!-- latest_release 15.0.22 --> +## [v15.0.22](https://github.com/chef/chef/tree/v15.0.22) (2018-10-31) #### Merged Pull Requests -- Refactor Cookbook loader logic now that we don't support merging [#7794](https://github.com/chef/chef/pull/7794) ([lamont-granquist](https://github.com/lamont-granquist)) +- Fix chef-apply crash for reboot [#7720](https://github.com/chef/chef/pull/7720) ([dheerajd-msys](https://github.com/dheerajd-msys)) <!-- latest_release --> <!-- release_rollup --> ### Changes since latest stable release #### Merged Pull Requests +- Fix chef-apply crash for reboot [#7720](https://github.com/chef/chef/pull/7720) ([dheerajd-msys](https://github.com/dheerajd-msys)) <!-- 15.0.22 --> +- Remove preview resource from windows_certificate & windows_share [#7818](https://github.com/chef/chef/pull/7818) ([tas50](https://github.com/tas50)) <!-- 15.0.21 --> +- Bump win32-certstore to 0.1.11 [#7823](https://github.com/chef/chef/pull/7823) ([tas50](https://github.com/tas50)) <!-- 15.0.20 --> +- More cookbook loader cleanup and documentation [#7820](https://github.com/chef/chef/pull/7820) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 15.0.19 --> +- Fix testing / installing on SLES 15 [#7819](https://github.com/chef/chef/pull/7819) ([tas50](https://github.com/tas50)) <!-- 15.0.18 --> +- Update win32-certstore to include a license [#7822](https://github.com/chef/chef/pull/7822) ([tas50](https://github.com/tas50)) <!-- 15.0.17 --> +- Add windows_certificate and windows_share resources [#7731](https://github.com/chef/chef/pull/7731) ([tas50](https://github.com/tas50)) <!-- 15.0.16 --> +- Remove unused route resource properties [#7240](https://github.com/chef/chef/pull/7240) ([tas50](https://github.com/tas50)) <!-- 15.0.15 --> +- Fix inspector to properly handle defaults that are symbols [#7813](https://github.com/chef/chef/pull/7813) ([tas50](https://github.com/tas50)) <!-- 15.0.14 --> - Refactor Cookbook loader logic now that we don't support merging [#7794](https://github.com/chef/chef/pull/7794) ([lamont-granquist](https://github.com/lamont-granquist)) <!-- 15.0.13 --> - Remove deprecated support for FreeBSD pkg provider [#7789](https://github.com/chef/chef/pull/7789) ([tas50](https://github.com/tas50)) <!-- 15.0.12 --> - Multiple fixes to dmg_package [#7802](https://github.com/chef/chef/pull/7802) ([tas50](https://github.com/tas50)) <!-- 15.0.11 --> diff --git a/Gemfile.lock b/Gemfile.lock index 0e6d5f2222..01fca20d37 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,10 +8,10 @@ GIT GIT remote: https://github.com/chef/ohai.git - revision: 3759aeff5ff3c999fd99ffef44f6d00ad3fd0135 + revision: 5236aff74b8f2a2e9244cff4d325ccb4f73b3d69 branch: master specs: - ohai (15.0.2) + ohai (15.0.4) chef-config (>= 12.8, < 16) ffi (~> 1.9) ffi-yajl (~> 2.2) @@ -27,10 +27,10 @@ GIT PATH remote: . specs: - chef (15.0.13) + chef (15.0.22) addressable bundler (>= 1.10) - chef-config (= 15.0.13) + chef-config (= 15.0.22) chef-zero (>= 13.0) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) @@ -57,10 +57,10 @@ PATH specinfra (~> 2.10) syslog-logger (~> 1.6) uuidtools (~> 2.1.5) - chef (15.0.13-universal-mingw32) + chef (15.0.22-universal-mingw32) addressable bundler (>= 1.10) - chef-config (= 15.0.13) + chef-config (= 15.0.22) chef-zero (>= 13.0) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) @@ -89,6 +89,7 @@ PATH syslog-logger (~> 1.6) uuidtools (~> 2.1.5) win32-api (~> 1.5.3) + win32-certstore (>= 0.1.8) win32-dir (~> 0.5.0) win32-event (~> 0.6.1) win32-eventlog (= 0.6.3) @@ -103,7 +104,7 @@ PATH PATH remote: chef-config specs: - chef-config (15.0.13) + chef-config (15.0.22) addressable fuzzyurl mixlib-config (>= 2.2.12, < 3.0) @@ -225,7 +226,7 @@ GEM octokit (4.13.0) sawyer (~> 0.8.0, >= 0.5.3) parallel (1.12.1) - parser (2.5.1.2) + parser (2.5.3.0) ast (~> 2.4.0) parslet (1.8.2) plist (3.4.0) @@ -330,6 +331,9 @@ GEM hashdiff websocket (1.2.8) win32-api (1.5.3-universal-mingw32) + win32-certstore (0.1.11) + ffi + mixlib-shellout win32-dir (0.5.1) ffi (>= 1.0.0) win32-event (0.6.3) @@ -1 +1 @@ -15.0.13
\ No newline at end of file +15.0.22
\ 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 02f115eec6..389c03b056 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 = "15.0.13".freeze + VERSION = "15.0.22".freeze end # diff --git a/chef-universal-mingw32.gemspec b/chef-universal-mingw32.gemspec index f663153159..7e1a9d1551 100644 --- a/chef-universal-mingw32.gemspec +++ b/chef-universal-mingw32.gemspec @@ -16,6 +16,7 @@ gemspec.add_dependency "windows-api", "~> 0.4.4" gemspec.add_dependency "wmi-lite", "~> 1.0" gemspec.add_dependency "win32-taskscheduler", "~> 2.0" gemspec.add_dependency "iso8601", "~> 0.12.1" +gemspec.add_dependency "win32-certstore", ">= 0.1.8" gemspec.extensions << "ext/win32-eventlog/Rakefile" gemspec.files += Dir.glob("{distro,ext}/**/*") diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb index 7325d89e1e..5b697703d7 100644 --- a/lib/chef/application/apply.rb +++ b/lib/chef/application/apply.rb @@ -191,10 +191,12 @@ class Chef::Application::Apply < Chef::Application recipe, run_context = get_recipe_and_run_context recipe.instance_eval(@recipe_text, @recipe_filename, 1) runner = Chef::Runner.new(run_context) - begin - runner.converge - ensure - @recipe_fh.close + catch(:end_client_run_early) do + begin + runner.converge + ensure + @recipe_fh.close + end end Chef::Platform::Rebooter.reboot_if_needed!(runner) end diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb index c864c30505..96bc4aa303 100644 --- a/lib/chef/cookbook/cookbook_version_loader.rb +++ b/lib/chef/cookbook/cookbook_version_loader.rb @@ -7,12 +7,21 @@ require "find" class Chef class Cookbook + # This class is only used drectly from the Chef::CookbookLoader and from chef-fs, + # so it only affects legacy-mode chef-client runs and knife. It is not used by + # server or zolo/zero modes. + # + # This seems to be mostly a glorified factory method for creating CookbookVersion + # objects now, with creating Metadata objects bolted onto the side? It used + # to be also responsible for the merging of multiple objects when creating + # shadowed/merged cookbook versions from multiple sources. It also handles + # Chefignore files. + # class CookbookVersionLoader UPLOADED_COOKBOOK_VERSION_FILE = ".uploaded-cookbook-version.json".freeze attr_reader :cookbook_settings - attr_reader :cookbook_paths attr_reader :frozen attr_reader :uploaded_cookbook_version_file @@ -25,13 +34,11 @@ class Chef def initialize(path, chefignore = nil) @cookbook_path = File.expand_path( path ) # cookbook_path from which this was loaded - # We keep a list of all cookbook paths that have been merged in - @cookbook_paths = [ cookbook_path ] @inferred_cookbook_name = File.basename( path ) @chefignore = chefignore @metadata = nil - @relative_path = /#{Regexp.escape(@cookbook_path)}\/(.+)$/ + @relative_path = /#{Regexp.escape(cookbook_path)}\/(.+)$/ @metadata_loaded = false @cookbook_settings = { all_files: {}, @@ -68,53 +75,22 @@ class Chef if empty? Chef::Log.warn "Found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping." end - @cookbook_settings + cookbook_settings end alias :load_cookbooks :load - def metadata_filenames - return @metadata_filenames unless @metadata_filenames.empty? - if File.exists?(File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)) - @uploaded_cookbook_version_file = File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE) - end - - if File.exists?(File.join(cookbook_path, "metadata.json")) - @metadata_filenames << File.join(cookbook_path, "metadata.json") - elsif File.exists?(File.join(cookbook_path, "metadata.rb")) - @metadata_filenames << File.join(cookbook_path, "metadata.rb") - elsif @uploaded_cookbook_version_file - @metadata_filenames << @uploaded_cookbook_version_file - end - - # Set frozen based on .uploaded-cookbook-version.json - set_frozen - @metadata_filenames - end - def cookbook_version return nil if empty? - Chef::CookbookVersion.new(cookbook_name, *cookbook_paths).tap do |c| + Chef::CookbookVersion.new(cookbook_name, cookbook_path).tap do |c| c.all_files = cookbook_settings[:all_files].values c.metadata = metadata - c.freeze_version if @frozen + c.freeze_version if frozen end end - def cookbook_name - # The `name` attribute is now required in metadata, so - # inferred_cookbook_name generally should not be used. Per CHEF-2923, - # we have to not raise errors in cookbook metadata immediately, so that - # users can still `knife cookbook upload some-cookbook` when an - # unrelated cookbook has an error in its metadata. This situation - # could prevent us from reading the `name` attribute from the metadata - # entirely, but the name is used as a hash key in CookbookLoader, so we - # fall back to the inferred name here. - (metadata.name || @inferred_cookbook_name).to_sym - end - # Generates the Cookbook::Metadata object def metadata return @metadata unless @metadata.nil? @@ -125,7 +101,7 @@ class Chef case metadata_file when /\.rb$/ apply_ruby_metadata(metadata_file) - when @uploaded_cookbook_version_file + when uploaded_cookbook_version_file apply_json_cookbook_version_metadata(metadata_file) when /\.json$/ apply_json_metadata(metadata_file) @@ -146,13 +122,46 @@ class Chef @metadata end + def cookbook_name + # The `name` attribute is now required in metadata, so + # inferred_cookbook_name generally should not be used. Per CHEF-2923, + # we have to not raise errors in cookbook metadata immediately, so that + # users can still `knife cookbook upload some-cookbook` when an + # unrelated cookbook has an error in its metadata. This situation + # could prevent us from reading the `name` attribute from the metadata + # entirely, but the name is used as a hash key in CookbookLoader, so we + # fall back to the inferred name here. + (metadata.name || inferred_cookbook_name).to_sym + end + + private + + def metadata_filenames + return @metadata_filenames unless @metadata_filenames.empty? + if File.exists?(File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)) + @uploaded_cookbook_version_file = File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE) + end + + if File.exists?(File.join(cookbook_path, "metadata.json")) + @metadata_filenames << File.join(cookbook_path, "metadata.json") + elsif File.exists?(File.join(cookbook_path, "metadata.rb")) + @metadata_filenames << File.join(cookbook_path, "metadata.rb") + elsif uploaded_cookbook_version_file + @metadata_filenames << uploaded_cookbook_version_file + end + + # Set frozen based on .uploaded-cookbook-version.json + set_frozen + @metadata_filenames + end + def raise_metadata_error! - raise @metadata_error unless @metadata_error.nil? + raise metadata_error unless metadata_error.nil? # Metadata won't be valid if the cookbook is empty. If the cookbook is # actually empty, a metadata error here would be misleading, so don't # raise it (if called by #load!, a different error is raised). if !empty? && !metadata.valid? - message = "Cookbook loaded at path(s) [#{@cookbook_paths.join(', ')}] has invalid metadata: #{metadata.errors.join('; ')}" + message = "Cookbook loaded at path [#{cookbook_path}] has invalid metadata: #{metadata.errors.join('; ')}" raise Exceptions::MetadataNotValid, message end false @@ -162,18 +171,6 @@ class Chef cookbook_settings.values.all? { |files_hash| files_hash.empty? } && metadata_filenames.size == 0 end - def merge!(other_cookbook_loader) - other_cookbook_settings = other_cookbook_loader.cookbook_settings - cookbook_settings.each do |file_type, file_list| - file_list.merge!(other_cookbook_settings[file_type]) - end - metadata_filenames.concat(other_cookbook_loader.metadata_filenames) - @cookbook_paths += other_cookbook_loader.cookbook_paths - @frozen = true if other_cookbook_loader.frozen - @metadata = nil # reset metadata so it gets reloaded and all metadata files applied. - self - end - def chefignore @chefignore ||= Chefignore.new(File.basename(cookbook_path)) end @@ -223,14 +220,14 @@ class Chef def apply_ruby_metadata(file) @metadata.from_file(file) rescue Chef::Exceptions::JSON::ParseError - Chef::Log.error("Error evaluating metadata.rb for #{@inferred_cookbook_name} in " + file) + Chef::Log.error("Error evaluating metadata.rb for #{inferred_cookbook_name} in " + file) raise end def apply_json_metadata(file) @metadata.from_json(IO.read(file)) rescue Chef::Exceptions::JSON::ParseError - Chef::Log.error("Couldn't parse cookbook metadata JSON for #{@inferred_cookbook_name} in " + file) + Chef::Log.error("Couldn't parse cookbook metadata JSON for #{inferred_cookbook_name} in " + file) raise end @@ -247,7 +244,7 @@ class Chef # metadata contains a name key. @metadata.name(data["cookbook_name"]) unless data["metadata"].key?("name") rescue Chef::Exceptions::JSON::ParseError - Chef::Log.error("Couldn't parse cookbook metadata JSON for #{@inferred_cookbook_name} in " + file) + Chef::Log.error("Couldn't parse cookbook metadata JSON for #{inferred_cookbook_name} in " + file) raise end @@ -257,7 +254,7 @@ class Chef data = Chef::JSONCompat.parse(IO.read(uploaded_cookbook_version_file)) @frozen = data["frozen?"] rescue Chef::Exceptions::JSON::ParseError - Chef::Log.error("Couldn't parse cookbook metadata JSON for #{@inferred_cookbook_name} in #{uploaded_cookbook_version_file}") + Chef::Log.error("Couldn't parse cookbook metadata JSON for #{inferred_cookbook_name} in #{uploaded_cookbook_version_file}") raise end end diff --git a/lib/chef/cookbook_loader.rb b/lib/chef/cookbook_loader.rb index ed6f9342df..1a7dec8b03 100644 --- a/lib/chef/cookbook_loader.rb +++ b/lib/chef/cookbook_loader.rb @@ -25,42 +25,72 @@ require "chef/cookbook_version" require "chef/cookbook/chefignore" require "chef/cookbook/metadata" -# -# CookbookLoader class loads the cookbooks lazily as read -# class Chef + # This class is used by knife, cheffs and legacy chef-solo modes. It is not used by the server mode + # of chef-client or zolo/zero modes. + # + # This class implements orchestration around producing a single cookbook_version for a cookbook or + # loading a Mash of all cookbook_versions, using the cookbook_version_loader class, and doing + # lazy-access and memoization to only load each cookbook once on demand. + # + # This implements a key-value style each which makes it appear to be a Hash of String => CookbookVersion + # pairs where the String is the cookbook name. The use of Enumerable combined with the Hash-style + # each is likely not entirely sane. + # + # This object is also passed and injected into the CookbookCollection object where it is converted + # to a Mash that looks almost exactly like the cookbook_by_name Mash in this object. + # class CookbookLoader - # FIXME: doc public api - attr_reader :cookbook_paths + # @return [Array<String>] the array of repo paths containing cookbook dirs + attr_reader :repo_paths + # XXX: this is highly questionable combined with the Hash-style each method include Enumerable + # @param repo_paths [Array<String>] the array of repo paths containing cookbook dirs def initialize(*repo_paths) @repo_paths = repo_paths.flatten.map { |p| File.expand_path(p) } - raise ArgumentError, "You must specify at least one cookbook repo path" if @repo_paths.empty? + raise ArgumentError, "You must specify at least one cookbook repo path" if repo_paths.empty? end + # The primary function of this class is to build this Mash mapping cookbook names as a string to + # the CookbookVersion objects for them. Callers must call "load_cookbooks" first. + # + # @return [Mash<String, Chef::CookbookVersion>] def cookbooks_by_name @cookbooks_by_name ||= Mash.new end + # This class also builds a mapping of cookbook names to their Metadata objects. Callers must call + # "load_cookbooks" first. + # + # @return [Mash<String, Chef::Cookbook::Metadata>] def metadata @metadata ||= Mash.new end + # Loads all cookbooks across all repo_paths + # + # @return [Mash<String, Chef::CookbookVersion>] the cookbooks_by_name Mash def load_cookbooks - cookbook_loaders.each_key do |cookbook_name| + cookbook_version_loaders.each_key do |cookbook_name| load_cookbook(cookbook_name) end cookbooks_by_name end + # Loads a single cookbook by its name. + # + # @param [String] + # @return [Chef::CookbookVersion] def load_cookbook(cookbook_name) - return nil unless cookbook_loaders.key?(cookbook_name) + unless cookbook_version_loaders.key?(cookbook_name) + raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook_name}; did you forget to add metadata to a cookbook? (https://docs.chef.io/config_rb_metadata.html)" + end return cookbooks_by_name[cookbook_name] if cookbooks_by_name.key?(cookbook_name) - loader = cookbook_loaders[cookbook_name] + loader = cookbook_version_loaders[cookbook_name] loader.load @@ -71,11 +101,7 @@ class Chef end def [](cookbook) - if cookbooks_by_name.key?(cookbook.to_sym) || load_cookbook(cookbook.to_sym) - cookbooks_by_name[cookbook.to_sym] - else - raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook}; did you forget to add metadata to a cookbook? (https://docs.chef.io/config_rb_metadata.html)" - end + load_cookbook(cookbook) end alias :fetch :[] @@ -113,6 +139,11 @@ class Chef private + # Helper method to lazily create and remember the chefignore object + # for a given repo_path. + # + # @param [String] repo_path the full path to the cookbook directory of the repo + # @return [Chef::Cookbook::Chefignore] the chefignore object for the repo_path def chefignore(repo_path) @chefignores ||= {} @chefignores[repo_path] ||= Cookbook::Chefignore.new(repo_path) @@ -126,14 +157,17 @@ class Chef def all_files_in_repo_paths @all_files_in_repo_paths ||= begin - @repo_paths.inject([]) do |all_children, repo_path| + repo_paths.inject([]) do |all_children, repo_path| all_children + Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(repo_path), "*")] end end end - def cookbook_loaders - @cookbook_loaders ||= + # This method creates a Mash of the CookbookVersionLoaders for each cookbook. + # + # @return [Mash<String, Cookbook::CookbookVersionLoader>] + def cookbook_version_loaders + @cookbook_version_loaders ||= begin mash = Mash.new all_directories_in_repo_paths.each do |cookbook_path| diff --git a/lib/chef/provider/route.rb b/lib/chef/provider/route.rb index 58a65edb81..b8af9e55b8 100644 --- a/lib/chef/provider/route.rb +++ b/lib/chef/provider/route.rb @@ -158,9 +158,9 @@ class Chef end def generate_config - conf = {} case node[:platform_family] when "rhel", "amazon", "fedora" + conf = {} # walk the collection run_context.resource_collection.each do |resource| next unless resource.is_a? Chef::Resource::Route diff --git a/lib/chef/resource/route.rb b/lib/chef/resource/route.rb index 8c14394ace..45a1266fd7 100644 --- a/lib/chef/resource/route.rb +++ b/lib/chef/resource/route.rb @@ -27,20 +27,30 @@ class Chef description "Use the route resource to manage the system routing table in a Linux environment." - property :target, String, identity: true, name_property: true - property :comment, [String, nil], introduced: "14.0" - property :metric, [Integer, nil] - property :netmask, [String, nil] - property :gateway, [String, nil] - property :device, [String, nil], desired_state: false # Has a partial default in the provider of eth0. - property :route_type, [:host, :net], default: :host, coerce: proc { |x| x.to_sym }, desired_state: false - - # I can find no evidence of these properties actually being used by Chef. NK 2017-04-11 - property :networking, [String, nil], desired_state: false - property :networking_ipv6, [String, nil], desired_state: false - property :hostname, [String, nil], desired_state: false - property :domainname, [String, nil], desired_state: false - property :domain, [String, nil], desired_state: false + property :target, String, + description: "The IP address of the target route.", + identity: true, name_property: true + + property :comment, [String, nil], + description: "Add a comment for the route.", + introduced: "14.0" + + property :metric, [Integer, nil], + description: "The route metric value." + + property :netmask, [String, nil], + description: "The decimal representation of the network mask. For example: 255.255.255.0." + + property :gateway, [String, nil], + description: "The gateway for the route." + + property :device, [String, nil], + description: "The network interface to which the route applies.", + desired_state: false # Has a partial default in the provider of eth0. + + property :route_type, [Symbol, String], + description: "", + equal_to: [:host, :net], default: :host, desired_state: false end end end diff --git a/lib/chef/resource/windows_certificate.rb b/lib/chef/resource/windows_certificate.rb new file mode 100644 index 0000000000..afb69703a8 --- /dev/null +++ b/lib/chef/resource/windows_certificate.rb @@ -0,0 +1,268 @@ +# +# Author:: Richard Lavey (richard.lavey@calastone.com) +# +# Copyright:: 2015-2017, Calastone Ltd. +# Copyright:: 2018, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 "win32-certstore" if Chef::Platform.windows? +require "openssl" + +class Chef + class Resource + class WindowsCertificate < Chef::Resource + resource_name :windows_certificate + + description "Use the windows_certificate resource to install a certificate into the Windows certificate store from a file. The resource grants read-only access to the private key for designated accounts. Due to current limitations in WinRM, installing certificates remotely may not work if the operation requires a user profile. Operations on the local machine store should still work." + introduced "14.7" + + property :source, String, + description: "The source file (for create and acl_add), thumbprint (for delete and acl_add) or subject (for delete).", + name_property: true + + property :pfx_password, String, + description: "The password to access the source if it is a pfx file." + + property :private_key_acl, Array, + description: "An array of 'domain\account' entries to be granted read-only access to the certificate's private key. Not idempotent." + + property :store_name, String, + description: "The certificate store to manipulate.", + default: "MY", equal_to: ["TRUSTEDPUBLISHER", "TrustedPublisher", "CLIENTAUTHISSUER", "REMOTE DESKTOP", "ROOT", "TRUSTEDDEVICES", "WEBHOSTING", "CA", "AUTHROOT", "TRUSTEDPEOPLE", "MY", "SMARTCARDROOT", "TRUST", "DISALLOWED"] + + property :user_store, [TrueClass, FalseClass], + description: "Use the user store of the local machine store if set to false.", + default: false + + property :cert_path, String, + description: "" + + # lazy used to set default value of sensitive to true if password is set + property :sensitive, [ TrueClass, FalseClass ], + description: "Ensure that sensitive resource data is not logged by the chef-client.", + default: lazy { |r| r.pfx_password ? true : false }, skip_docs: true + + action :create do + description "Creates or updates a certificate." + + add_cert(OpenSSL::X509::Certificate.new(raw_source)) + end + + # acl_add is a modify-if-exists operation : not idempotent + action :acl_add do + description "Adds read-only entries to a certificate's private key ACL." + + if ::File.exist?(new_resource.source) + hash = "$cert.GetCertHashString()" + code_script = cert_script(false) + guard_script = cert_script(false) + else + # make sure we have no spaces in the hash string + hash = "\"#{new_resource.source.gsub(/\s/, '')}\"" + code_script = "" + guard_script = "" + end + code_script << acl_script(hash) + guard_script << cert_exists_script(hash) + + powershell_script "setting the acls on #{new_resource.source} in #{cert_location}\\#{new_resource.store_name}" do + guard_interpreter :powershell_script + convert_boolean_return true + code code_script + only_if guard_script + sensitive if new_resource.sensitive + end + end + + action :delete do + description "Deletes a certificate." + + delete_cert + end + + action :fetch do + description "Fetches a certificate." + + cert_obj = fetch_cert + if cert_obj + show_or_store_cert(cert_obj) + else + Chef::Log.info("Certificate not found") + end + end + + action :verify do + description "" + + out = verify_cert + if !!out == out + out = out ? "Certificate is valid" : "Certificate not valid" + end + Chef::Log.info(out.to_s) + end + + action_class do + def add_cert(cert_obj) + store = ::Win32::Certstore.open(new_resource.store_name) + store.add(cert_obj) + end + + def delete_cert + store = ::Win32::Certstore.open(new_resource.store_name) + store.delete(new_resource.source) + end + + def fetch_cert + store = ::Win32::Certstore.open(new_resource.store_name) + store.get(new_resource.source) + end + + def verify_cert + store = ::Win32::Certstore.open(new_resource.store_name) + store.valid?(new_resource.source) + end + + def show_or_store_cert(cert_obj) + if new_resource.cert_path + export_cert(cert_obj, new_resource.cert_path) + if ::File.size(new_resource.cert_path) > 0 + Chef::Log.info("Certificate export in #{new_resource.cert_path}") + else + ::File.delete(new_resource.cert_path) + end + else + Chef::Log.info(cert_obj.display) + end + end + + def export_cert(cert_obj, cert_path) + out_file = ::File.new(cert_path, "w+") + case ::File.extname(cert_path) + when ".pem" + out_file.puts(cert_obj.to_pem) + when ".der" + out_file.puts(cert_obj.to_der) + when ".cer" + cert_out = powershell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CER").stdout + out_file.puts(cert_out) + when ".crt" + cert_out = powershell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CRT").stdout + out_file.puts(cert_out) + when ".pfx" + cert_out = powershell_out("openssl pkcs12 -export -nokeys -in #{cert_obj.to_pem} -outform PFX").stdout + out_file.puts(cert_out) + when ".p7b" + cert_out = powershell_out("openssl pkcs7 -export -nokeys -in #{cert_obj.to_pem} -outform P7B").stdout + out_file.puts(cert_out) + else + Chef::Log.info("Supported certificate format .pem, .der, .cer, .crt, .pfx and .p7b") + end + out_file.close + end + + def cert_location + @location ||= new_resource.user_store ? "CurrentUser" : "LocalMachine" + end + + def cert_script(persist) + cert_script = "$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2" + file = win_friendly_path(new_resource.source) + cert_script << " \"#{file}\"" + if ::File.extname(file.downcase) == ".pfx" + cert_script << ", \"#{new_resource.pfx_password}\"" + if persist && new_resource.user_store + cert_script << ", ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet)" + elsif persist + cert_script << ", ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeyset)" + end + end + cert_script << "\n" + end + + def cert_exists_script(hash) + <<-EOH + $hash = #{hash} + Test-Path "Cert:\\#{cert_location}\\#{new_resource.store_name}\\$hash" + EOH + end + + def within_store_script + inner_script = yield "$store" + <<-EOH + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "#{new_resource.store_name}", ([System.Security.Cryptography.X509Certificates.StoreLocation]::#{cert_location}) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + #{inner_script} + $store.Close() + EOH + end + + def acl_script(hash) + return "" if new_resource.private_key_acl.nil? || new_resource.private_key_acl.empty? + # this PS came from http://blogs.technet.com/b/operationsguy/archive/2010/11/29/provide-access-to-private-keys-commandline-vs-powershell.aspx + # and from https://msdn.microsoft.com/en-us/library/windows/desktop/bb204778(v=vs.85).aspx + set_acl_script = <<-EOH + $hash = #{hash} + $storeCert = Get-ChildItem "cert:\\#{cert_location}\\#{new_resource.store_name}\\$hash" + if ($storeCert -eq $null) { throw 'no key exists.' } + $keyname = $storeCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName + if ($keyname -eq $null) { throw 'no private key exists.' } + if ($storeCert.PrivateKey.CspKeyContainerInfo.MachineKeyStore) + { + $fullpath = "$Env:ProgramData\\Microsoft\\Crypto\\RSA\\MachineKeys\\$keyname" + } + else + { + $currentUser = New-Object System.Security.Principal.NTAccount($Env:UserDomain, $Env:UserName) + $userSID = $currentUser.Translate([System.Security.Principal.SecurityIdentifier]).Value + $fullpath = "$Env:ProgramData\\Microsoft\\Crypto\\RSA\\$userSID\\$keyname" + } + EOH + new_resource.private_key_acl.each do |name| + set_acl_script << "$uname='#{name}'; icacls $fullpath /grant $uname`:RX\n" + end + set_acl_script + end + + def raw_source + ext = ::File.extname(new_resource.source) + convert_pem(ext, new_resource.source) + end + + def convert_pem(ext, source) + out = case ext + when ".crt", ".der" + powershell_out("openssl x509 -text -inform DER -in #{source} -outform PEM").stdout + when ".cer" + powershell_out("openssl x509 -text -inform DER -in #{source} -outform PEM").stdout + when ".pfx" + powershell_out("openssl pkcs12 -in #{source} -nodes -passin pass:#{new_resource.pfx_password}").stdout + when ".p7b" + powershell_out("openssl pkcs7 -print_certs -in #{source} -outform PEM").stdout + end + out = ::File.read(source) if out.nil? || out.empty? + format_raw_out(out) + end + + def format_raw_out(out) + begin_cert = "-----BEGIN CERTIFICATE-----" + end_cert = "-----END CERTIFICATE-----" + begin_cert + out[/#{begin_cert}(.*?)#{end_cert}/m, 1] + end_cert + end + end + + end + end +end diff --git a/lib/chef/resource/windows_share.rb b/lib/chef/resource/windows_share.rb new file mode 100644 index 0000000000..b50c5ef2e3 --- /dev/null +++ b/lib/chef/resource/windows_share.rb @@ -0,0 +1,314 @@ +# +# Author:: Sölvi Páll Ásgeirsson (<solvip@gmail.com>) +# Author:: Richard Lavey (richard.lavey@calastone.com) +# Author:: Tim Smith (tsmith@chef.io) +# +# Copyright:: 2014-2017, Sölvi Páll Ásgeirsson. +# Copyright:: 2018, Chef Software, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 "chef/json_compat" + +class Chef + class Resource + class WindowsShare < Chef::Resource + resource_name :windows_share + + description "Use the windows_share resource to create, modify and remove Windows shares." + introduced "14.7" + + # Specifies a name for the SMB share. The name may be composed of any valid file name characters, but must be less than 80 characters long. The names pipe and mailslot are reserved for use by the computer. + property :share_name, String, + description: "The name to assign to the share.", + name_property: true + + # Specifies the path of the location of the folder to share. The path must be fully qualified. Relative paths or paths that contain wildcard characters are not permitted. + property :path, String, + description: "The path of the folder to share. Required when creating. If the share already exists on a different path then it is deleted and re-created." + + # Specifies an optional description of the SMB share. A description of the share is displayed by running the Get-SmbShare cmdlet. The description may not contain more than 256 characters. + property :description, String, + description: "The description to be applied to the share.", + default: "" + + # Specifies which accounts are granted full permission to access the share. Use a comma-separated list to specify multiple accounts. An account may not be specified more than once in the FullAccess, ChangeAccess, or ReadAccess parameter lists, but may be specified once in the FullAccess, ChangeAccess, or ReadAccess parameter list and once in the NoAccess parameter list. + property :full_users, Array, + description: "The users that should have 'Full control' permissions on the share in domain\\username format.", + default: [], coerce: proc { |u| u.sort } + + # Specifies which users are granted modify permission to access the share + property :change_users, Array, + description: "The users that should have 'modify' permission on the share in domain\\username format.", + default: [], coerce: proc { |u| u.sort } + + # Specifies which users are granted read permission to access the share. Multiple users can be specified by supplying a comma-separated list. + property :read_users, Array, + description: "The users that should have 'read' permission on the share in domain\\username format.", + default: [], coerce: proc { |u| u.sort } + + # Specifies the lifetime of the new SMB share. A temporary share does not persist beyond the next restart of the computer. By default, new SMB shares are persistent, and non-temporary. + property :temporary, [TrueClass, FalseClass], + description: "The lifetime of the new SMB share. A temporary share does not persist beyond the next restart of the computer.", + default: false + + # Specifies the scope name of the share. + property :scope_name, String, + description: "The scope name of the share.", + default: "*" + + # Specifies the continuous availability time-out for the share. + property :ca_timeout, Integer, + description: "The continuous availability time-out for the share.", + default: 0 + + # Indicates that the share is continuously available. + property :continuously_available, [TrueClass, FalseClass], + description: "Indicates that the share is continuously available.", + default: false + + # Specifies the caching mode of the offline files for the SMB share. + # property :caching_mode, String, equal_to: %w(None Manual Documents Programs BranchCache) + + # Specifies the maximum number of concurrently connected users that the new SMB share may accommodate. If this parameter is set to zero (0), then the number of users is unlimited. + property :concurrent_user_limit, Integer, + description: "The maximum number of concurrently connected users the share can accommodate.", + default: 0 + + # Indicates that the share is encrypted. + property :encrypt_data, [TrueClass, FalseClass], + description: "Indicates that the share is encrypted.", + default: false + + # Specifies which files and folders in the SMB share are visible to users. AccessBased: SMB does not the display the files and folders for a share to a user unless that user has rights to access the files and folders. By default, access-based enumeration is disabled for new SMB shares. Unrestricted: SMB displays files and folders to a user even when the user does not have permission to access the items. + # property :folder_enumeration_mode, String, equal_to: %(AccessBased Unrestricted) + + include Chef::Mixin::PowershellOut + + load_current_value do |desired| + # this command selects individual objects because EncryptData & CachingMode have underlying + # types that get converted to their Integer values by ConvertTo-Json & we need to make sure + # those get written out as strings + share_state_cmd = "Get-SmbShare -Name '#{desired.share_name}' | Select-Object Name,Path, Description, Temporary, CATimeout, ContinuouslyAvailable, ConcurrentUserLimit, EncryptData | ConvertTo-Json" + + Chef::Log.debug("Running '#{share_state_cmd}' to determine share state'") + ps_results = powershell_out(share_state_cmd) + + # detect a failure without raising and then set current_resource to nil + if ps_results.error? + Chef::Log.debug("Error fetching share state: #{ps_results.stderr}") + current_value_does_not_exist! + end + + Chef::Log.debug("The Get-SmbShare results were #{ps_results.stdout}") + results = Chef::JSONCompat.from_json(ps_results.stdout) + + path results["Path"] + description results["Description"] + temporary results["Temporary"] + ca_timeout results["CATimeout"] + continuously_available results["ContinuouslyAvailable"] + # caching_mode results['CachingMode'] + concurrent_user_limit results["ConcurrentUserLimit"] + encrypt_data results["EncryptData"] + # folder_enumeration_mode results['FolderEnumerationMode'] + + perm_state_cmd = %{Get-SmbShareAccess -Name "#{desired.share_name}" | Select-Object AccountName,AccessControlType,AccessRight | ConvertTo-Json} + + Chef::Log.debug("Running '#{perm_state_cmd}' to determine share permissions state'") + ps_perm_results = powershell_out(perm_state_cmd) + + # we raise here instead of warning like above because we'd only get here if the above Get-SmbShare + # command was successful and that continuing would leave us with 1/2 known state + raise "Could not determine #{desired.share_name} share permissions by running '#{perm_state_cmd}'" if ps_perm_results.error? + + Chef::Log.debug("The Get-SmbShareAccess results were #{ps_perm_results.stdout}") + + f_users, c_users, r_users = parse_permissions(ps_perm_results.stdout) + + full_users f_users + change_users c_users + read_users r_users + end + + def after_created + raise "The windows_share resource relies on PowerShell cmdlets not present in Windows releases prior to 8/2012. Cannot continue!" if node["platform_version"].to_f < 6.3 + end + +# given the string output of Get-SmbShareAccess parse out +# arrays of full access users, change users, and read only users + def parse_permissions(results_string) + json_results = Chef::JSONCompat.from_json(results_string) + json_results = [json_results] unless json_results.is_a?(Array) # single result is not an array + + f_users = [] + c_users = [] + r_users = [] + + json_results.each do |perm| + next unless perm["AccessControlType"] == 0 # allow + case perm["AccessRight"] + when 0 then f_users << stripped_account(perm["AccountName"]) # 0 full control + when 1 then c_users << stripped_account(perm["AccountName"]) # 1 == change + when 2 then r_users << stripped_account(perm["AccountName"]) # 2 == read + end + end + [f_users, c_users, r_users] + end + +# local names are returned from Get-SmbShareAccess in the full format MACHINE\\NAME +# but users of this resource would simply say NAME so we need to strip the values for comparison + def stripped_account(name) + name.slice!("#{node['hostname']}\\") + name + end + + action :create do + description "Create and modify Windows shares." + + # we do this here instead of requiring the property because :delete doesn't need path set + raise "No path property set" unless new_resource.path + + converge_if_changed do + # you can't actually change the path so you have to delete the old share first + delete_share if different_path? + + # powershell cmdlet for create is different than updates + if current_resource.nil? + Chef::Log.debug("The current resource is nil so we will create a new share") + create_share + else + Chef::Log.debug("The current resource was not nil so we will update an existing share") + update_share + end + + # creating the share does not set permissions so we need to update + update_permissions + end + end + + action :delete do + description "Delete an existing Windows share." + + if current_resource.nil? + Chef::Log.debug("#{new_resource.share_name} does not exist - nothing to do") + else + converge_by("delete #{new_resource.share_name}") do + delete_share + end + end + end + + action_class do + def different_path? + return false if current_resource.nil? # going from nil to something isn't different for our concerns + return false if current_resource.path == new_resource.path + true + end + + def delete_share + delete_command = "Remove-SmbShare -Name '#{new_resource.share_name}' -Force" + + Chef::Log.debug("Running '#{delete_command}' to remove the share") + powershell_out!(delete_command) + end + + def update_share + update_command = "Set-SmbShare -Name '#{new_resource.share_name}' -Description '#{new_resource.description}' -Force" + + Chef::Log.debug("Running '#{update_command}' to update the share") + powershell_out!(update_command) + end + + def create_share + raise "#{new_resource.path} is missing or not a directory. Shares cannot be created if the path doesn't first exist." unless ::File.directory? new_resource.path + + share_cmd = "New-SmbShare -Name '#{new_resource.share_name}' -Path '#{new_resource.path}' -Description '#{new_resource.description}' -ConcurrentUserLimit #{new_resource.concurrent_user_limit} -CATimeout #{new_resource.ca_timeout} -EncryptData:#{bool_string(new_resource.encrypt_data)} -ContinuouslyAvailable:#{bool_string(new_resource.continuously_available)}" + share_cmd << " -ScopeName #{new_resource.scope_name}" unless new_resource.scope_name == "*" # passing * causes the command to fail + share_cmd << " -Temporary:#{bool_string(new_resource.temporary)}" if new_resource.temporary # only set true + + Chef::Log.debug("Running '#{share_cmd}' to create the share") + powershell_out!(share_cmd) + end + + # determine what users in the current state don't exist in the desired state + # users/groups will have their permissions updated with the same command that + # sets it, but removes must be performed with Revoke-SmbShareAccess + def users_to_revoke + @users_to_revoke ||= begin + # if the resource doesn't exist then nothing needs to be revoked + if current_resource.nil? + [] + else # if it exists then calculate the current to new resource diffs + (current_resource.full_users + current_resource.change_users + current_resource.read_users) - (new_resource.full_users + new_resource.change_users + new_resource.read_users) + end + end + end + + # update existing permissions on a share + def update_permissions + # revoke any users that had something, but now has nothing + revoke_user_permissions(users_to_revoke) unless users_to_revoke.empty? + + # set permissions for each of the permission types + %w{full read change}.each do |perm_type| + # set permissions for a brand new share OR + # update permissions if the current state and desired state differ + next unless permissions_need_update?(perm_type) + grant_command = "Grant-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{new_resource.send("#{perm_type}_users").join('","')}\" -Force -AccessRight #{perm_type}" + + Chef::Log.debug("Running '#{grant_command}' to update the share permissions") + powershell_out!(grant_command) + end + end + + # determine if permissions need to be updated. + # Brand new share with no permissions defined: no + # Brand new share with permissions defined: yes + # Existing share with differing permissions: yes + # + # @param [String] type the permissions type (Full, Read, or Change) + def permissions_need_update?(type) + property_name = "#{type}_users" + + # brand new share, but nothing to set + return false if current_resource.nil? && new_resource.send(property_name).empty? + + # brand new share with new permissions to set + return true if current_resource.nil? && !new_resource.send(property_name).empty? + + # there's a difference between the current and desired state + return true unless (new_resource.send(property_name) - current_resource.send(property_name)).empty? + + # anything else + false + end + + def revoke_user_permissions(users) + revoke_command = "Revoke-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{users.join(',')}\" -Force" + Chef::Log.debug("Running '#{revoke_command}' to revoke share permissions") + powershell_out!(revoke_command) + end + + # convert True/False into "$True" & "$False" + def bool_string(bool) + # bool ? 1 : 0 + bool ? "$true" : "$false" + end + end + + end + end +end diff --git a/lib/chef/resource_inspector.rb b/lib/chef/resource_inspector.rb index 1a49f05b72..1888f9a8bc 100644 --- a/lib/chef/resource_inspector.rb +++ b/lib/chef/resource_inspector.rb @@ -31,7 +31,7 @@ module ResourceInspector # code for the resource ourselves and just no "lazy default" else - default + default.inspect # inspect properly returns symbols end end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 6324ce5b66..805d278cc6 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -130,6 +130,7 @@ require "chef/resource/powershell_package" require "chef/resource/msu_package" require "chef/resource/windows_ad_join" require "chef/resource/windows_auto_run" +require "chef/resource/windows_certificate" require "chef/resource/windows_feature" require "chef/resource/windows_feature_dism" require "chef/resource/windows_feature_powershell" @@ -138,6 +139,7 @@ require "chef/resource/windows_pagefile" require "chef/resource/windows_path" require "chef/resource/windows_printer" require "chef/resource/windows_printer_port" +require "chef/resource/windows_share" require "chef/resource/windows_shortcut" require "chef/resource/windows_task" require "chef/resource/windows_workgroup" diff --git a/lib/chef/version.rb b/lib/chef/version.rb index 71ad6abc47..d71d686335 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("15.0.13") + VERSION = Chef::VersionString.new("15.0.22") end # diff --git a/omnibus/Gemfile.lock b/omnibus/Gemfile.lock index 575ca1f61b..233be61ae0 100644 --- a/omnibus/Gemfile.lock +++ b/omnibus/Gemfile.lock @@ -32,8 +32,8 @@ GEM public_suffix (>= 2.0.2, < 4.0) awesome_print (1.8.0) aws-eventstream (1.0.1) - aws-partitions (1.106.0) - aws-sdk-core (3.35.0) + aws-partitions (1.107.0) + aws-sdk-core (3.36.0) aws-eventstream (~> 1.0) aws-partitions (~> 1.0) aws-sigv4 (~> 1.0) @@ -41,7 +41,7 @@ GEM aws-sdk-kms (1.11.0) aws-sdk-core (~> 3, >= 3.26.0) aws-sigv4 (~> 1.0) - aws-sdk-s3 (1.23.0) + aws-sdk-s3 (1.23.1) aws-sdk-core (~> 3, >= 3.26.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.0) @@ -177,7 +177,7 @@ GEM ipaddress (0.8.3) iso8601 (0.12.1) jmespath (1.4.0) - kitchen-vagrant (1.3.5) + kitchen-vagrant (1.3.6) test-kitchen (~> 1.4) libyajl2 (1.2.0) license_scout (1.0.16) @@ -189,7 +189,7 @@ GEM little-plugger (~> 1.1) multi_json (~> 1.10) method_source (0.9.0) - minitar (0.6.1) + minitar (0.7) mixlib-archive (0.4.18) mixlib-log mixlib-archive (0.4.18-universal-mingw32) @@ -378,4 +378,4 @@ DEPENDENCIES winrm-fs (~> 1.0) BUNDLED WITH - 1.16.6 + 1.17.1 diff --git a/omnibus/package-scripts/angrychef/postrm b/omnibus/package-scripts/angrychef/postrm index 247688074e..a153da7102 100755 --- a/omnibus/package-scripts/angrychef/postrm +++ b/omnibus/package-scripts/angrychef/postrm @@ -11,11 +11,19 @@ is_smartos() { uname -v | grep "^joyent" 2>&1 >/dev/null } -is_darwin() -{ +is_darwin() { uname -v | grep "^Darwin" 2>&1 >/dev/null } +is_suse() { + if [ -f /etc/os-release ]; then + . /etc/os-release + [ "$ID_LIKE" = "sles" ] || [ "$ID_LIKE" = "suse" ] + else + [ -f /etc/SuSE-release ] + fi +} + if is_smartos; then PREFIX="/opt/local" elif is_darwin; then @@ -33,7 +41,7 @@ cleanup_symlinks() { # Clean up binary symlinks if they exist # see: http://tickets.opscode.com/browse/CHEF-3022 -if [ ! -f /etc/redhat-release -a ! -f /etc/fedora-release -a ! -f /etc/system-release -a ! -f /etc/SuSE-release ]; then +if [ ! -f /etc/redhat-release -a ! -f /etc/fedora-release -a ! -f /etc/system-release -a ! is_suse ]; then # not a redhat-ish RPM-based system cleanup_symlinks elif [ "x$1" = "x0" ]; then diff --git a/omnibus/package-scripts/chef-fips/postrm b/omnibus/package-scripts/chef-fips/postrm index 247688074e..a153da7102 100755 --- a/omnibus/package-scripts/chef-fips/postrm +++ b/omnibus/package-scripts/chef-fips/postrm @@ -11,11 +11,19 @@ is_smartos() { uname -v | grep "^joyent" 2>&1 >/dev/null } -is_darwin() -{ +is_darwin() { uname -v | grep "^Darwin" 2>&1 >/dev/null } +is_suse() { + if [ -f /etc/os-release ]; then + . /etc/os-release + [ "$ID_LIKE" = "sles" ] || [ "$ID_LIKE" = "suse" ] + else + [ -f /etc/SuSE-release ] + fi +} + if is_smartos; then PREFIX="/opt/local" elif is_darwin; then @@ -33,7 +41,7 @@ cleanup_symlinks() { # Clean up binary symlinks if they exist # see: http://tickets.opscode.com/browse/CHEF-3022 -if [ ! -f /etc/redhat-release -a ! -f /etc/fedora-release -a ! -f /etc/system-release -a ! -f /etc/SuSE-release ]; then +if [ ! -f /etc/redhat-release -a ! -f /etc/fedora-release -a ! -f /etc/system-release -a ! is_suse ]; then # not a redhat-ish RPM-based system cleanup_symlinks elif [ "x$1" = "x0" ]; then diff --git a/omnibus/package-scripts/chef/postrm b/omnibus/package-scripts/chef/postrm index 247688074e..a153da7102 100755 --- a/omnibus/package-scripts/chef/postrm +++ b/omnibus/package-scripts/chef/postrm @@ -11,11 +11,19 @@ is_smartos() { uname -v | grep "^joyent" 2>&1 >/dev/null } -is_darwin() -{ +is_darwin() { uname -v | grep "^Darwin" 2>&1 >/dev/null } +is_suse() { + if [ -f /etc/os-release ]; then + . /etc/os-release + [ "$ID_LIKE" = "sles" ] || [ "$ID_LIKE" = "suse" ] + else + [ -f /etc/SuSE-release ] + fi +} + if is_smartos; then PREFIX="/opt/local" elif is_darwin; then @@ -33,7 +41,7 @@ cleanup_symlinks() { # Clean up binary symlinks if they exist # see: http://tickets.opscode.com/browse/CHEF-3022 -if [ ! -f /etc/redhat-release -a ! -f /etc/fedora-release -a ! -f /etc/system-release -a ! -f /etc/SuSE-release ]; then +if [ ! -f /etc/redhat-release -a ! -f /etc/fedora-release -a ! -f /etc/system-release -a ! is_suse ]; then # not a redhat-ish RPM-based system cleanup_symlinks elif [ "x$1" = "x0" ]; then diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb index 6ae052ba1d..5ae9c01722 100644 --- a/spec/support/platform_helpers.rb +++ b/spec/support/platform_helpers.rb @@ -220,7 +220,8 @@ def selinux_enabled? end def suse? - File.exists?("/etc/SuSE-release") + ::File.exists?("/etc/SuSE-release") || + ( ::File.exists?("/etc/os-release") && /sles|suse/.match?(File.read("/etc/os-release")) ) end def root? diff --git a/spec/unit/cookbook/cookbook_version_loader_spec.rb b/spec/unit/cookbook/cookbook_version_loader_spec.rb index 40a054abee..c38af059f5 100644 --- a/spec/unit/cookbook/cookbook_version_loader_spec.rb +++ b/spec/unit/cookbook/cookbook_version_loader_spec.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright 2014-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -173,7 +173,7 @@ describe Chef::Cookbook::CookbookVersionLoader do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "incomplete-metadata-chef-repo/incomplete-metadata") } let(:error_message) do - "Cookbook loaded at path(s) [#{cookbook_path}] has invalid metadata: The `name' attribute is required in cookbook metadata" + "Cookbook loaded at path [#{cookbook_path}] has invalid metadata: The `name' attribute is required in cookbook metadata" end it "raises an error when loading with #load!" do diff --git a/spec/unit/cookbook_loader_spec.rb b/spec/unit/cookbook_loader_spec.rb index 7c15cc9030..ddb4f00f35 100644 --- a/spec/unit/cookbook_loader_spec.rb +++ b/spec/unit/cookbook_loader_spec.rb @@ -196,7 +196,9 @@ describe Chef::CookbookLoader do end it "should not load the cookbook again when accessed" do - expect(cookbook_loader).not_to receive("load_cookbook") + cookbook_loader.send(:cookbook_version_loaders).each do |cbv_loader| + expect(cbv_loader).not_to receive(:load) + end cookbook_loader["openldap"] end diff --git a/spec/unit/resource/route_spec.rb b/spec/unit/resource/route_spec.rb index d4248755b5..dd79d04024 100644 --- a/spec/unit/resource/route_spec.rb +++ b/spec/unit/resource/route_spec.rb @@ -56,8 +56,13 @@ describe Chef::Resource::Route do expect(resource.device).to eql("eth0") end - it "allows you to specify the route type" do - resource.route_type "host" + it "allows you to specify the route type as a symbol" do + resource.route_type :host + expect(resource.route_type).to eql(:host) + end + + it "allows you to specify the route type as a string" do + resource.route_type :host expect(resource.route_type).to eql(:host) end diff --git a/spec/unit/resource/windows_certificate.rb b/spec/unit/resource/windows_certificate.rb new file mode 100644 index 0000000000..4a60be6e87 --- /dev/null +++ b/spec/unit/resource/windows_certificate.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::WindowsCertificate do + let(:resource) { Chef::Resource::WindowsCertificate.new("foobar") } + + it "sets resource name as :windows_certificate" do + expect(resource.resource_name).to eql(:windows_certificate) + end + + it "the source property is the name_property" do + expect(resource.source).to eql("foobar") + end + + it "sets the default action as :create" do + expect(resource.action).to eql([:create]) + end + + it "supports :create, :acl_add, :delete, and :verify actions" do + expect { resource.action :create }.not_to raise_error + expect { resource.action :acl_add }.not_to raise_error + expect { resource.action :delete }.not_to raise_error + expect { resource.action :verify }.not_to raise_error + end + + it "sets sensitive to true if the pfx_password property is set" do + resource.pfx_password "foo" + expect(resource.sensitive).to be_truthy + end +end diff --git a/spec/unit/resource/windows_share.rb b/spec/unit/resource/windows_share.rb new file mode 100644 index 0000000000..ee1c24529f --- /dev/null +++ b/spec/unit/resource/windows_share.rb @@ -0,0 +1,39 @@ +# +# 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::WindowsShare do + let(:resource) { Chef::Resource::WindowsShare.new("foobar") } + + it "sets resource name as :windows_share" do + expect(resource.resource_name).to eql(:windows_share) + end + + it "the share_name property is the name_property" do + expect(resource.share_name).to eql("foobar") + end + + it "sets the default action as :create" do + expect(resource.action).to eql([:create]) + end + + it "supports :create and :delete actions" do + expect { resource.action :create }.not_to raise_error + expect { resource.action :delete }.not_to raise_error + end +end |