diff options
Diffstat (limited to 'lib')
36 files changed, 470 insertions, 285 deletions
diff --git a/lib/chef/application.rb b/lib/chef/application.rb index f9735a3769..7f15859c8f 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -28,7 +28,6 @@ require "mixlib/cli" require "tmpdir" require "rbconfig" require "chef/application/exit_code" -require "yaml" class Chef class Application @@ -111,20 +110,13 @@ class Chef end extra_config_options = config.delete(:config_option) Chef::Config.merge!(config) - if extra_config_options - extra_parsed_options = extra_config_options.inject({}) do |memo, option| - # Sanity check value. - Chef::Application.fatal!("Unparsable config option #{option.inspect}") if option.empty? || !option.include?("=") - # Split including whitespace if someone does truly odd like - # --config-option "foo = bar" - key, value = option.split(/\s*=\s*/, 2) - # Call to_sym because Chef::Config expects only symbol keys. Also - # runs a simple parse on the string for some common types. - memo[key.to_sym] = YAML.safe_load(value) - memo - end - Chef::Config.merge!(extra_parsed_options) - end + apply_extra_config_options(extra_config_options) + end + + def apply_extra_config_options(extra_config_options) + Chef::Config.apply_extra_config_options(extra_config_options) + rescue ChefConfig::UnparsableConfigOption => e + Chef::Application.fatal!(e.message) end def set_specific_recipes diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb index cbaa494b71..000aff905b 100644 --- a/lib/chef/application/client.rb +++ b/lib/chef/application/client.rb @@ -202,7 +202,7 @@ class Chef::Application::Client < Chef::Application :short => "-o RunlistItem,RunlistItem...", :long => "--override-runlist RunlistItem,RunlistItem...", :description => "Replace current run list with specified items for a single run", - :proc => lambda {|items| + :proc => lambda { |items| items = items.split(",") items.compact.map do |item| Chef::RunList::RunListItem.new(item) @@ -213,7 +213,7 @@ class Chef::Application::Client < Chef::Application :short => "-r RunlistItem,RunlistItem...", :long => "--runlist RunlistItem,RunlistItem...", :description => "Permanently replace current run list with specified items", - :proc => lambda {|items| + :proc => lambda { |items| items = items.split(",") items.compact.map do |item| Chef::RunList::RunListItem.new(item) diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb index 446a0f007d..1481338a9c 100644 --- a/lib/chef/application/solo.rb +++ b/lib/chef/application/solo.rb @@ -166,7 +166,7 @@ class Chef::Application::Solo < Chef::Application :short => "-o RunlistItem,RunlistItem...", :long => "--override-runlist RunlistItem,RunlistItem...", :description => "Replace current run list with specified items", - :proc => lambda {|items| + :proc => lambda { |items| items = items.split(",") items.compact.map do |item| Chef::RunList::RunListItem.new(item) diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb index f019448bd8..0bb15c03ca 100644 --- a/lib/chef/chef_class.rb +++ b/lib/chef/chef_class.rb @@ -30,6 +30,7 @@ require "chef/platform/provider_priority_map" require "chef/platform/resource_priority_map" require "chef/platform/provider_handler_map" require "chef/platform/resource_handler_map" +require "chef/event_dispatch/dsl" class Chef class << self diff --git a/lib/chef/chef_fs/file_system/chef_server/versioned_cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_server/versioned_cookbook_dir.rb index 269e160d43..b7c96c42e1 100644 --- a/lib/chef/chef_fs/file_system/chef_server/versioned_cookbook_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server/versioned_cookbook_dir.rb @@ -24,7 +24,7 @@ class Chef module ChefServer class VersionedCookbookDir < CookbookDir # See Erchef code - # https://github.com/opscode/chef_objects/blob/968a63344d38fd507f6ace05f73d53e9cd7fb043/src/chef_regex.erl#L94 + # https://github.com/chef/chef_objects/blob/968a63344d38fd507f6ace05f73d53e9cd7fb043/src/chef_regex.erl#L94 VALID_VERSIONED_COOKBOOK_NAME = /^([.a-zA-Z0-9_-]+)-(\d+\.\d+\.\d+)$/ def initialize(name, parent, options = {}) diff --git a/lib/chef/dsl/core.rb b/lib/chef/dsl/core.rb index 11507857cf..d7c5b6a006 100644 --- a/lib/chef/dsl/core.rb +++ b/lib/chef/dsl/core.rb @@ -1,7 +1,7 @@ #-- # Author:: Adam Jacob (<adam@chef.io>) # Author:: Christopher Walters (<cw@chef.io>) -# Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc. +# Copyright:: Copyright 2008-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/lib/chef/dsl/declare_resource.rb b/lib/chef/dsl/declare_resource.rb index ed3083e1ca..5bd1f7fb8e 100644 --- a/lib/chef/dsl/declare_resource.rb +++ b/lib/chef/dsl/declare_resource.rb @@ -1,7 +1,7 @@ #-- # Author:: Adam Jacob (<adam@chef.io>) # Author:: Christopher Walters -# Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc. +# Copyright:: Copyright 2008-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,7 +47,7 @@ class Chef when Chef::RunContext rc when :root - Chef.run_context + run_context.root_run_context when :parent run_context.parent_run_context else diff --git a/lib/chef/dsl/method_missing.rb b/lib/chef/dsl/method_missing.rb index 0d7ded30f3..51c3eac606 100644 --- a/lib/chef/dsl/method_missing.rb +++ b/lib/chef/dsl/method_missing.rb @@ -1,5 +1,5 @@ #-- -# Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc. +# Copyright:: Copyright 2008-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb index 1bb8f130af..e2bd070179 100644 --- a/lib/chef/dsl/recipe.rb +++ b/lib/chef/dsl/recipe.rb @@ -1,7 +1,7 @@ #-- # Author:: Adam Jacob (<adam@chef.io>) # Author:: Christopher Walters (<cw@chef.io>) -# Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc. +# Copyright:: Copyright 2008-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/lib/chef/dsl/universal.rb b/lib/chef/dsl/universal.rb index 810792f542..6e3d162b6f 100644 --- a/lib/chef/dsl/universal.rb +++ b/lib/chef/dsl/universal.rb @@ -1,7 +1,7 @@ #-- # Author:: Adam Jacob (<adam@chef.io>) # Author:: Christopher Walters (<cw@chef.io>) -# Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc. +# Copyright:: Copyright 2008-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/lib/chef/http.rb b/lib/chef/http.rb index 924081bc6b..12acae953c 100644 --- a/lib/chef/http.rb +++ b/lib/chef/http.rb @@ -5,7 +5,7 @@ # Author:: Christopher Brown (<cb@chef.io>) # Author:: Christopher Walters (<cw@chef.io>) # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2009-2016, 2013-2015 Chef Software, Inc. +# Copyright:: Copyright 2009-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -329,10 +329,9 @@ class Chef # Runs a synchronous HTTP request, with no middleware applied (use #request # to have the middleware applied). The entire response will be loaded into memory. # @api private - def send_http_request(method, url, headers, body, &response_handler) - headers = build_headers(method, url, headers, body) - + def send_http_request(method, url, base_headers, body, &response_handler) retrying_http_errors(url) do + headers = build_headers(method, url, base_headers, body) client = http_client(url) return_value = nil if block_given? diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index 0dbd02ceb4..8d40e06a43 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -408,11 +408,25 @@ class Chef config_loader = self.class.load_config(config[:config_file]) config[:config_file] = config_loader.config_location + # For CLI options like `--config-option key=value`. These have to get + # parsed and applied separately. + extra_config_options = config.delete(:config_option) + merge_configs apply_computed_config - Chef::Config.export_proxies + # This has to be after apply_computed_config so that Mixlib::Log is configured Chef::Log.info("Using configuration from #{config[:config_file]}") if config[:config_file] + + begin + Chef::Config.apply_extra_config_options(extra_config_options) + rescue ChefConfig::UnparsableConfigOption => e + ui.error e.message + show_usage + exit(1) + end + + Chef::Config.export_proxies end def show_usage @@ -525,7 +539,11 @@ class Chef # FIXME: yard with @yield def create_object(object, pretty_name = nil, object_class: nil) - output = edit_data(object, object_class: object_class) + output = if object_class + edit_data(object, object_class: object_class) + else + edit_hash(object) + end if Kernel.block_given? output = yield(output) diff --git a/lib/chef/mixin/powershell_out.rb b/lib/chef/mixin/powershell_out.rb index 74de85f86f..ab7cf00a72 100644 --- a/lib/chef/mixin/powershell_out.rb +++ b/lib/chef/mixin/powershell_out.rb @@ -91,7 +91,7 @@ class Chef "-InputFormat None", ] - "powershell.exe #{flags.join(' ')} -Command \"#{script}\"" + "powershell.exe #{flags.join(' ')} -Command \"#{script.gsub('"', '\"')}\"" end end end diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb index a258a91075..d8607c8de7 100644 --- a/lib/chef/mixin/shell_out.rb +++ b/lib/chef/mixin/shell_out.rb @@ -61,7 +61,7 @@ class Chef [:command_log_prepend, :log_tag] ] # CHEF-3090: Deprecate command_log_level and command_log_prepend - # Patterned after https://github.com/opscode/chef/commit/e1509990b559984b43e428d4d801c394e970f432 + # Patterned after https://github.com/chef/chef/commit/e1509990b559984b43e428d4d801c394e970f432 def run_command_compatible_options(command_args) return command_args unless command_args.last.is_a?(Hash) diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 212b1ced14..34a92d325b 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -197,7 +197,6 @@ class Chef # Set a normal attribute of this node, but auto-vivify any Mashes that # might be missing def normal - attributes.top_level_breadcrumb = nil attributes.normal end @@ -209,14 +208,12 @@ class Chef # Set a default of this node, but auto-vivify any Mashes that might # be missing def default - attributes.top_level_breadcrumb = nil attributes.default end # Set an override attribute of this node, but auto-vivify any Mashes that # might be missing def override - attributes.top_level_breadcrumb = nil attributes.override end @@ -237,7 +234,6 @@ class Chef end def automatic_attrs - attributes.top_level_breadcrumb = nil attributes.automatic end diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb index a4a07275c0..2d6aff0b21 100644 --- a/lib/chef/node/attribute.rb +++ b/lib/chef/node/attribute.rb @@ -17,6 +17,9 @@ # limitations under the License. # +require "chef/node/mixin/deep_merge_cache" +require "chef/node/mixin/immutablize_hash" +require "chef/node/mixin/state_tracking" require "chef/node/immutable_collections" require "chef/node/attribute_collections" require "chef/decorator/unchain" @@ -34,9 +37,18 @@ class Chef class Attribute < Mash include Immutablize - + # FIXME: what is include Enumerable doing up here, when down below we delegate + # most of the Enumerable/Hash things to the underlying merged ImmutableHash. That + # is, in fact, the correct, thing to do, while including Enumerable to try to create + # a hash-like API gets lots of things wrong because of the difference between the + # Hash `each do |key, value|` vs the Array-like `each do |value|` API that Enumerable + # expects. This include should probably be deleted? include Enumerable + include Chef::Node::Mixin::DeepMergeCache + include Chef::Node::Mixin::StateTracking + include Chef::Node::Mixin::ImmutablizeHash + # List of the component attribute hashes, in order of precedence, low to # high. COMPONENTS = [ @@ -175,39 +187,21 @@ class Chef # return the automatic level attribute component attr_reader :automatic - # This is used to track the top level key as we descend through method chaining into - # a precedence level (e.g. node.default['foo']['bar']['baz']= results in 'foo' here). We - # need this so that when we hit the end of a method chain which results in a mutator method - # that we can invalidate the whole top-level deep merge cache for the top-level key. It is - # the responsibility of the accessor on the Chef::Node object to reset this to nil, and then - # the first VividMash#[] call can ||= and set this to the first key we encounter. - attr_accessor :top_level_breadcrumb - - # Cache of deep merged values by top-level key. This is a simple hash which has keys that are the - # top-level keys of the node object, and we save the computed deep-merge for that key here. There is - # no cache of subtrees. - attr_accessor :deep_merge_cache - def initialize(normal, default, override, automatic) - @default = VividMash.new(self, default) - @env_default = VividMash.new(self, {}) - @role_default = VividMash.new(self, {}) - @force_default = VividMash.new(self, {}) - - @normal = VividMash.new(self, normal) + @default = VividMash.new(default, self) + @env_default = VividMash.new({}, self) + @role_default = VividMash.new({}, self) + @force_default = VividMash.new({}, self) - @override = VividMash.new(self, override) - @role_override = VividMash.new(self, {}) - @env_override = VividMash.new(self, {}) - @force_override = VividMash.new(self, {}) + @normal = VividMash.new(normal, self) - @automatic = VividMash.new(self, automatic) + @override = VividMash.new(override, self) + @role_override = VividMash.new({}, self) + @env_override = VividMash.new({}, self) + @force_override = VividMash.new({}, self) - @merged_attributes = nil - @combined_override = nil - @combined_default = nil - @top_level_breadcrumb = nil - @deep_merge_cache = {} + @automatic = VividMash.new(automatic, self) + super() end # Debug what's going on with an attribute. +args+ is a path spec to the @@ -235,76 +229,62 @@ class Chef end end - # Invalidate a key in the deep_merge_cache. If called with nil, or no arg, this will invalidate - # the entire deep_merge cache. In the case of the user doing node.default['foo']['bar']['baz']= - # that eventually results in a call to reset_cache('foo') here. A node.default=hash_thing call - # must invalidate the entire cache and re-deep-merge the entire node object. - def reset_cache(path = nil) - if path.nil? - @deep_merge_cache = {} - else - deep_merge_cache.delete(path.to_s) - end - end - - alias :reset :reset_cache - # Set the cookbook level default attribute component to +new_data+. def default=(new_data) reset - @default = VividMash.new(self, new_data) + @default = VividMash.new(new_data, self) end # Set the role level default attribute component to +new_data+ def role_default=(new_data) reset - @role_default = VividMash.new(self, new_data) + @role_default = VividMash.new(new_data, self) end # Set the environment level default attribute component to +new_data+ def env_default=(new_data) reset - @env_default = VividMash.new(self, new_data) + @env_default = VividMash.new(new_data, self) end # Set the force_default (+default!+) level attributes to +new_data+ def force_default=(new_data) reset - @force_default = VividMash.new(self, new_data) + @force_default = VividMash.new(new_data, self) end # Set the normal level attribute component to +new_data+ def normal=(new_data) reset - @normal = VividMash.new(self, new_data) + @normal = VividMash.new(new_data, self) end # Set the cookbook level override attribute component to +new_data+ def override=(new_data) reset - @override = VividMash.new(self, new_data) + @override = VividMash.new(new_data, self) end # Set the role level override attribute component to +new_data+ def role_override=(new_data) reset - @role_override = VividMash.new(self, new_data) + @role_override = VividMash.new(new_data, self) end # Set the environment level override attribute component to +new_data+ def env_override=(new_data) reset - @env_override = VividMash.new(self, new_data) + @env_override = VividMash.new(new_data, self) end def force_override=(new_data) reset - @force_override = VividMash.new(self, new_data) + @force_override = VividMash.new(new_data, self) end def automatic=(new_data) reset - @automatic = VividMash.new(self, new_data) + @automatic = VividMash.new(new_data, self) end # @@ -413,36 +393,6 @@ class Chef write(:force_override, *args, value) end - # method-style access to attributes - - def read(*path) - merged_attributes.read(*path) - end - - def read!(*path) - merged_attributes.read!(*path) - end - - def exist?(*path) - merged_attributes.exist?(*path) - end - - def write(level, *args, &block) - self.send(level).write(*args, &block) - end - - def write!(level, *args, &block) - self.send(level).write!(*args, &block) - end - - def unlink(level, *path) - self.send(level).unlink(*path) - end - - def unlink!(level, *path) - self.send(level).unlink!(*path) - end - # # Accessing merged attributes. # @@ -484,24 +434,39 @@ class Chef write(:normal, *args) if read(*args[0...-1]).nil? end - def [](key) - if deep_merge_cache.has_key?(key.to_s) - # return the cache of the deep merged values by top-level key - deep_merge_cache[key.to_s] - else - # save all the work of computing node[key] - deep_merge_cache[key.to_s] = merged_attributes(key) + def has_key?(key) + COMPONENTS.any? do |component_ivar| + instance_variable_get(component_ivar).has_key?(key) end end + # method-style access to attributes (has to come after the prepended ImmutablizeHash) - def []=(key, value) - raise Exceptions::ImmutableAttributeModification + def read(*path) + merged_attributes.read(*path) end - def has_key?(key) - COMPONENTS.any? do |component_ivar| - instance_variable_get(component_ivar).has_key?(key) - end + def read!(*path) + merged_attributes.read!(*path) + end + + def exist?(*path) + merged_attributes.exist?(*path) + end + + def write(level, *args, &block) + self.send(level).write(*args, &block) + end + + def write!(level, *args, &block) + self.send(level).write!(*args, &block) + end + + def unlink(level, *path) + self.send(level).unlink(*path) + end + + def unlink!(level, *path) + self.send(level).unlink!(*path) end alias :attribute? :has_key? @@ -600,7 +565,7 @@ class Chef return nil if components.compact.empty? - components.inject(ImmutableMash.new({})) do |merged, component| + components.inject(ImmutableMash.new({}, self)) do |merged, component| Chef::Mixin::DeepMerge.hash_only_merge!(merged, component) end end @@ -631,6 +596,11 @@ class Chef end end + # needed for __path__ + def convert_key(key) + key.kind_of?(Symbol) ? key.to_s : key + end + end end diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb index 1bd31bceb0..b01b447978 100644 --- a/lib/chef/node/attribute_collections.rb +++ b/lib/chef/node/attribute_collections.rb @@ -17,6 +17,7 @@ # require "chef/node/common_api" +require "chef/node/mixin/state_tracking" class Chef class Node @@ -63,15 +64,12 @@ class Chef MUTATOR_METHODS.each do |mutator| define_method(mutator) do |*args, &block| ret = super(*args, &block) - root.reset_cache(root.top_level_breadcrumb) + send_reset_cache ret end end - attr_reader :root - - def initialize(root, data) - @root = root + def initialize(data = []) super(data) map! { |e| convert_value(e) } end @@ -96,14 +94,20 @@ class Chef when AttrArray value when Hash - VividMash.new(root, value) + VividMash.new(value, __root__) when Array - AttrArray.new(root, value) + AttrArray.new(value, __root__) else value end end + # needed for __path__ + def convert_key(key) + key + end + + prepend Chef::Node::Mixin::StateTracking end # == VividMash @@ -117,8 +121,6 @@ class Chef # #fetch, work as normal). # * attr_accessor style element set and get are supported via method_missing class VividMash < Mash - attr_reader :root - include CommonAPI # Methods that mutate a VividMash. Each of them is overridden so that it @@ -126,7 +128,6 @@ class Chef # object. MUTATOR_METHODS = [ :clear, - :delete, :delete_if, :keep_if, :merge!, @@ -140,23 +141,27 @@ class Chef # For all of the mutating methods on Mash, override them so that they # also invalidate the cached `merged_attributes` on the root Attribute # object. + + def delete(key, &block) + send_reset_cache(__path__ + [ key ]) + super + end + MUTATOR_METHODS.each do |mutator| define_method(mutator) do |*args, &block| - root.reset_cache(root.top_level_breadcrumb) + send_reset_cache super(*args, &block) end end - def initialize(root, data = {}) - @root = root + def initialize(data = {}) super(data) end def [](key) - root.top_level_breadcrumb ||= key value = super if !key?(key) - value = self.class.new(root) + value = self.class.new({}, __root__) self[key] = value else value @@ -164,9 +169,8 @@ class Chef end def []=(key, value) - root.top_level_breadcrumb ||= key ret = super - root.reset_cache(root.top_level_breadcrumb) + send_reset_cache(__path__ + [ key ]) ret end @@ -205,9 +209,9 @@ class Chef when AttrArray value when Hash - VividMash.new(root, value) + VividMash.new(value, __root__) when Array - AttrArray.new(root, value) + AttrArray.new(value, __root__) else value end @@ -217,6 +221,7 @@ class Chef Mash.new(self) end + prepend Chef::Node::Mixin::StateTracking end end end diff --git a/lib/chef/node/common_api.rb b/lib/chef/node/common_api.rb index ce2c6b6878..9bb83a5178 100644 --- a/lib/chef/node/common_api.rb +++ b/lib/chef/node/common_api.rb @@ -32,7 +32,6 @@ class Chef # - autovivifying / autoreplacing writer # - non-container-ey intermediate objects are replaced with hashes def write(*args, &block) - root.top_level_breadcrumb = nil if respond_to?(:root) value = block_given? ? yield : args.pop last = args.pop prev_memo = prev_key = nil @@ -56,7 +55,6 @@ class Chef # something that is not a container ("schema violation" issues). # def write!(*args, &block) - root.top_level_breadcrumb = nil if respond_to?(:root) value = block_given? ? yield : args.pop last = args.pop obj = args.inject(self) do |memo, key| @@ -71,7 +69,6 @@ class Chef # return true or false based on if the attribute exists def exist?(*path) - root.top_level_breadcrumb = nil if respond_to?(:root) path.inject(self) do |memo, key| return false unless valid_container?(memo, key) if memo.is_a?(Hash) @@ -103,7 +100,6 @@ class Chef # non-autovivifying reader that throws an exception if the attribute does not exist def read!(*path) raise Chef::Exceptions::NoSuchAttribute unless exist?(*path) - root.top_level_breadcrumb = nil if respond_to?(:root) path.inject(self) do |memo, key| memo[key] end @@ -112,10 +108,8 @@ class Chef # FIXME:(?) does anyone really like the autovivifying reader that we have and wants the same behavior? readers that write? ugh... def unlink(*path, last) - root.top_level_breadcrumb = nil if respond_to?(:root) hash = path.empty? ? self : read(*path) return nil unless hash.is_a?(Hash) || hash.is_a?(Array) - root.top_level_breadcrumb ||= last hash.delete(last) end diff --git a/lib/chef/node/immutable_collections.rb b/lib/chef/node/immutable_collections.rb index d4623ace2a..938135cbee 100644 --- a/lib/chef/node/immutable_collections.rb +++ b/lib/chef/node/immutable_collections.rb @@ -16,6 +16,9 @@ # require "chef/node/common_api" +require "chef/node/mixin/state_tracking" +require "chef/node/mixin/immutablize_array" +require "chef/node/mixin/immutablize_hash" class Chef class Node @@ -24,9 +27,9 @@ class Chef def immutablize(value) case value when Hash - ImmutableMash.new(value) + ImmutableMash.new(value, __root__) when Array - ImmutableArray.new(value) + ImmutableArray.new(value, __root__) else value end @@ -49,55 +52,12 @@ class Chef alias :internal_push :<< private :internal_push - # A list of methods that mutate Array. Each of these is overridden to - # raise an error, making this instances of this class more or less - # immutable. - DISALLOWED_MUTATOR_METHODS = [ - :<<, - :[]=, - :clear, - :collect!, - :compact!, - :default=, - :default_proc=, - :delete, - :delete_at, - :delete_if, - :fill, - :flatten!, - :insert, - :keep_if, - :map!, - :merge!, - :pop, - :push, - :update, - :reject!, - :reverse!, - :replace, - :select!, - :shift, - :slice!, - :sort!, - :sort_by!, - :uniq!, - :unshift, - ] - - def initialize(array_data) + def initialize(array_data = []) array_data.each do |value| internal_push(immutablize(value)) end end - # Redefine all of the methods that mutate a Hash to raise an error when called. - # This is the magic that makes this object "Immutable" - DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name| - define_method(mutator_method_name) do |*args, &block| - raise Exceptions::ImmutableAttributeModification - end - end - # For elements like Fixnums, true, nil... def safe_dup(e) e.dup @@ -125,6 +85,13 @@ class Chef a end + # for consistency's sake -- integers 'converted' to integers + def convert_key(key) + key + end + + prepend Chef::Node::Mixin::StateTracking + prepend Chef::Node::Mixin::ImmutablizeArray end # == ImmutableMash @@ -140,36 +107,13 @@ class Chef # it is stale. # * Values can be accessed in attr_reader-like fashion via method_missing. class ImmutableMash < Mash - include Immutablize include CommonAPI alias :internal_set :[]= private :internal_set - DISALLOWED_MUTATOR_METHODS = [ - :[]=, - :clear, - :collect!, - :default=, - :default_proc=, - :delete, - :delete_if, - :keep_if, - :map!, - :merge!, - :update, - :reject!, - :replace, - :select!, - :shift, - :write, - :write!, - :unlink, - :unlink!, - ] - - def initialize(mash_data) + def initialize(mash_data = {}) mash_data.each do |key, value| internal_set(key, immutablize(value)) end @@ -181,14 +125,6 @@ class Chef alias :attribute? :has_key? - # Redefine all of the methods that mutate a Hash to raise an error when called. - # This is the magic that makes this object "Immutable" - DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name| - define_method(mutator_method_name) do |*args, &block| - raise Exceptions::ImmutableAttributeModification - end - end - def method_missing(symbol, *args) if symbol == :to_ary super @@ -238,7 +174,8 @@ class Chef h end + prepend Chef::Node::Mixin::StateTracking + prepend Chef::Node::Mixin::ImmutablizeHash end - end end diff --git a/lib/chef/node/mixin/deep_merge_cache.rb b/lib/chef/node/mixin/deep_merge_cache.rb new file mode 100644 index 0000000000..b18d6b10cb --- /dev/null +++ b/lib/chef/node/mixin/deep_merge_cache.rb @@ -0,0 +1,61 @@ +#-- +# Copyright:: Copyright 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. +# + +class Chef + class Node + module Mixin + module DeepMergeCache + # Cache of deep merged values by top-level key. This is a simple hash which has keys that are the + # top-level keys of the node object, and we save the computed deep-merge for that key here. There is + # no cache of subtrees. + attr_accessor :deep_merge_cache + + def initialize + @merged_attributes = nil + @combined_override = nil + @combined_default = nil + @deep_merge_cache = {} + end + + # Invalidate a key in the deep_merge_cache. If called with nil, or no arg, this will invalidate + # the entire deep_merge cache. In the case of the user doing node.default['foo']['bar']['baz']= + # that eventually results in a call to reset_cache('foo') here. A node.default=hash_thing call + # must invalidate the entire cache and re-deep-merge the entire node object. + def reset_cache(path = nil) + if path.nil? + deep_merge_cache.clear + else + deep_merge_cache.delete(path.to_s) + end + end + + alias :reset :reset_cache + + def [](key) + if deep_merge_cache.has_key?(key.to_s) + # return the cache of the deep merged values by top-level key + deep_merge_cache[key.to_s] + else + # save all the work of computing node[key] + deep_merge_cache[key.to_s] = merged_attributes(key) + end + end + + end + end + end +end diff --git a/lib/chef/node/mixin/immutablize_array.rb b/lib/chef/node/mixin/immutablize_array.rb new file mode 100644 index 0000000000..cfa7266b9a --- /dev/null +++ b/lib/chef/node/mixin/immutablize_array.rb @@ -0,0 +1,67 @@ +#-- +# Copyright:: Copyright 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. +# + +class Chef + class Node + module Mixin + module ImmutablizeArray + # A list of methods that mutate Array. Each of these is overridden to + # raise an error, making this instances of this class more or less + # immutable. + DISALLOWED_MUTATOR_METHODS = [ + :<<, + :[]=, + :clear, + :collect!, + :compact!, + :default=, + :default_proc=, + :delete, + :delete_at, + :delete_if, + :fill, + :flatten!, + :insert, + :keep_if, + :map!, + :merge!, + :pop, + :push, + :update, + :reject!, + :reverse!, + :replace, + :select!, + :shift, + :slice!, + :sort!, + :sort_by!, + :uniq!, + :unshift, + ] + + # Redefine all of the methods that mutate a Hash to raise an error when called. + # This is the magic that makes this object "Immutable" + DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name| + define_method(mutator_method_name) do |*args, &block| + raise Exceptions::ImmutableAttributeModification + end + end + end + end + end +end diff --git a/lib/chef/node/mixin/immutablize_hash.rb b/lib/chef/node/mixin/immutablize_hash.rb new file mode 100644 index 0000000000..f09e6944fc --- /dev/null +++ b/lib/chef/node/mixin/immutablize_hash.rb @@ -0,0 +1,54 @@ +#-- +# Copyright:: Copyright 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. +# + +class Chef + class Node + module Mixin + module ImmutablizeHash + DISALLOWED_MUTATOR_METHODS = [ + :[]=, + :clear, + :collect!, + :default=, + :default_proc=, + :delete, + :delete_if, + :keep_if, + :map!, + :merge!, + :update, + :reject!, + :replace, + :select!, + :shift, + :write, + :write!, + :unlink, + :unlink!, + ] + + # Redefine all of the methods that mutate a Hash to raise an error when called. + # This is the magic that makes this object "Immutable" + DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name| + define_method(mutator_method_name) do |*args, &block| + raise Exceptions::ImmutableAttributeModification + end + end + end + end + end +end diff --git a/lib/chef/node/mixin/state_tracking.rb b/lib/chef/node/mixin/state_tracking.rb new file mode 100644 index 0000000000..9be102eeeb --- /dev/null +++ b/lib/chef/node/mixin/state_tracking.rb @@ -0,0 +1,71 @@ +#-- +# Copyright:: Copyright 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. +# + +class Chef + class Node + module Mixin + module StateTracking + attr_reader :__path__ + attr_reader :__root__ + + NULL = Object.new + + def initialize(data = NULL, root = self) + # __path__ and __root__ must be nil when we call super so it knows + # to avoid resetting the cache on construction + data == NULL ? super() : super(data) + @__path__ = [] + @__root__ = root + end + + def [](key) + ret = super + if ret.is_a?(StateTracking) + ret.__path__ = __path__ + [ convert_key(key) ] + ret.__root__ = __root__ + end + ret + end + + def []=(key, value) + ret = super + if ret.is_a?(StateTracking) + ret.__path__ = __path__ + [ convert_key(key) ] + ret.__root__ = __root__ + end + ret + end + + protected + + def __path__=(path) + @__path__ = path + end + + def __root__=(root) + @__root__ = root + end + + private + + def send_reset_cache(path = __path__) + __root__.reset_cache(path.first) if !__root__.nil? && __root__.respond_to?(:reset_cache) && !path.nil? + end + end + end + end +end diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb index 7baaeec0c5..35e5bb874c 100644 --- a/lib/chef/provider/cron.rb +++ b/lib/chef/provider/cron.rb @@ -199,7 +199,7 @@ class Chef def set_environment_var(attr_name, attr_value) if %w{MAILTO PATH SHELL HOME}.include?(attr_name) - @current_resource.send(attr_name.downcase.to_sym, attr_value) + @current_resource.send(attr_name.downcase.to_sym, attr_value.gsub(/^"|"$/, "")) else @current_resource.environment(@current_resource.environment.merge(attr_name => attr_value)) end diff --git a/lib/chef/provider/group/suse.rb b/lib/chef/provider/group/suse.rb index a79038e25e..71336f9311 100644 --- a/lib/chef/provider/group/suse.rb +++ b/lib/chef/provider/group/suse.rb @@ -17,6 +17,7 @@ # require "chef/provider/group/groupadd" +require "etc" class Chef class Provider @@ -36,24 +37,42 @@ class Chef a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/groupmod for #{@new_resource.name}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end + + requirements.assert(:create, :manage, :modify) do |a| + a.assertion do + begin + to_add(@new_resource.members).all? { |member| Etc.getpwnam(member) } + rescue + false + end + end + a.failure_message Chef::Exceptions::Group, "Could not add users #{to_add(@new_resource.members).join(", ")} to #{@new_resource.group_name}: one of these users does not exist" + a.whyrun "Could not find one of these users: #{to_add(@new_resource.members).join(", ")}. Assuming it will be created by a prior step" + end end def set_members(members) - to_delete = @current_resource.members - members - to_delete.each do |member| + to_remove(members).each do |member| remove_member(member) end - to_add = members - @current_resource.members - to_add.each do |member| + to_add(members).each do |member| add_member(member) end end + def to_add(members) + members - @current_resource.members + end + def add_member(member) shell_out!("groupmod -A #{member} #{@new_resource.group_name}") end + def to_remove(members) + @current_resource.members - members + end + def remove_member(member) shell_out!("groupmod -R #{member} #{@new_resource.group_name}") end diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb index 3f641145e6..3fed63c914 100644 --- a/lib/chef/provider/package.rb +++ b/lib/chef/provider/package.rb @@ -31,6 +31,8 @@ class Chef include Chef::Mixin::ShellOut extend Chef::Mixin::SubclassDirective + use_inline_resources + # subclasses declare this if they want all their arguments as arrays of packages and names subclass_directive :use_multipackage_api # subclasses declare this if they want sources (filenames) pulled from their package names @@ -81,7 +83,7 @@ class Chef end end - def action_install + action :install do unless target_version_array.any? Chef::Log.debug("#{@new_resource} is already installed - nothing to do") return @@ -116,7 +118,7 @@ class Chef private :install_description - def action_upgrade + action :upgrade do if !target_version_array.any? Chef::Log.debug("#{@new_resource} no versions to upgrade - nothing to do") return @@ -146,7 +148,7 @@ class Chef private :upgrade_description - def action_remove + action :remove do if removing_package? description = @new_resource.version ? "version #{@new_resource.version} of " : "" converge_by("remove #{description}package #{@current_resource.package_name}") do @@ -181,7 +183,7 @@ class Chef end end - def action_purge + action :purge do if removing_package? description = @new_resource.version ? "version #{@new_resource.version} of" : "" converge_by("purge #{description} package #{@current_resource.package_name}") do @@ -193,7 +195,7 @@ class Chef end end - def action_reconfig + action :reconfig do if @current_resource.version == nil Chef::Log.debug("#{@new_resource} is NOT installed - nothing to do") return diff --git a/lib/chef/provider/package/windows/exe.rb b/lib/chef/provider/package/windows/exe.rb index 44a2f19d1e..60065d9019 100644 --- a/lib/chef/provider/package/windows/exe.rb +++ b/lib/chef/provider/package/windows/exe.rb @@ -89,9 +89,10 @@ class Chef end def current_installed_version - @current_installed_version ||= uninstall_entries.count == 0 ? nil : begin - uninstall_entries.map { |entry| entry.display_version }.uniq - end + @current_installed_version ||= + if uninstall_entries.count != 0 + uninstall_entries.map { |entry| entry.display_version }.uniq + end end # http://unattended.sourceforge.net/installers.php diff --git a/lib/chef/provider/package/windows/msi.rb b/lib/chef/provider/package/windows/msi.rb index 301baa4ed5..ee3b2f7e8e 100644 --- a/lib/chef/provider/package/windows/msi.rb +++ b/lib/chef/provider/package/windows/msi.rb @@ -50,7 +50,7 @@ class Chef Chef::Log.debug("#{new_resource} checking package status and version for #{product_code}") get_installed_version(product_code) else - uninstall_entries.count == 0 ? nil : begin + if uninstall_entries.count != 0 uninstall_entries.map { |entry| entry.display_version }.uniq end end diff --git a/lib/chef/provider/ruby_block.rb b/lib/chef/provider/ruby_block.rb index 0817b14044..18ee9cecc5 100644 --- a/lib/chef/provider/ruby_block.rb +++ b/lib/chef/provider/ruby_block.rb @@ -1,7 +1,7 @@ # # Author:: Adam Jacob (<adam@chef.io>) # Author:: AJ Christensen (<aj@chef.io>) -# Copyright:: Copyright 2009-2016, Opscode +# Copyright:: Copyright 2009-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb index f6b088d333..4b05ac8f5e 100644 --- a/lib/chef/provider/user.rb +++ b/lib/chef/provider/user.rb @@ -113,15 +113,13 @@ class Chef # <true>:: If a change is required # <false>:: If the users are identical def compare_user - changed = [ :comment, :home, :shell, :password ].select do |user_attrib| - !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib) - end + return true if !@new_resource.home.nil? && Pathname.new(@new_resource.home).cleanpath != Pathname.new(@current_resource.home).cleanpath - changed += [ :uid, :gid ].select do |user_attrib| - !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib).to_s != @current_resource.send(user_attrib).to_s + [ :comment, :shell, :password, :uid, :gid ].each do |user_attrib| + return true if !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib).to_s != @current_resource.send(user_attrib).to_s end - changed.any? + false end def action_create diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 8048330ce1..074ab772a1 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -186,22 +186,16 @@ class Chef # This should most likely be paired with action :nothing # # @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`) - # @return [Array[Symbol]] the list of actions. # - def delayed_action(arg = nil) - if arg - arg = Array(arg).map(&:to_sym) - arg.each do |action| - validate( - { action: action }, - { action: { kind_of: Symbol, equal_to: allowed_actions } } - ) - # the resource effectively sends a delayed notification to itself - run_context.add_delayed_action(Notification.new(self, action, self)) - end - @delayed_action = arg - else - @delayed_action + def delayed_action(arg) + arg = Array(arg).map(&:to_sym) + arg.map do |action| + validate( + { action: action }, + { action: { kind_of: Symbol, equal_to: allowed_actions } } + ) + # the resource effectively sends a delayed notification to itself + run_context.add_delayed_action(Notification.new(self, action, self)) end end diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb index 207de63778..5c275a574f 100644 --- a/lib/chef/resource/file.rb +++ b/lib/chef/resource/file.rb @@ -1,7 +1,7 @@ # # Author:: Adam Jacob (<adam@chef.io>) # Author:: Seth Chisamore (<schisamo@chef.io>) -# Copyright:: Copyright 2008-2016, 2011-2015 Chef Software, Inc. +# Copyright:: Copyright 2008-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/lib/chef/resource/scm.rb b/lib/chef/resource/scm.rb index 1e8c71e59d..533723c2c4 100644 --- a/lib/chef/resource/scm.rb +++ b/lib/chef/resource/scm.rb @@ -89,13 +89,7 @@ class Chef ) end - def svn_password(arg = nil) - set_or_return( - :svn_password, - arg, - :kind_of => String - ) - end + property :svn_password, String, sensitive: true, desired_state: false def svn_arguments(arg = nil) @svn_arguments, arg = nil, nil if arg == false diff --git a/lib/chef/resource/yum_repository.rb b/lib/chef/resource/yum_repository.rb index b1859361b1..1c215b51ff 100644 --- a/lib/chef/resource/yum_repository.rb +++ b/lib/chef/resource/yum_repository.rb @@ -57,7 +57,7 @@ class Chef property :password, String, regex: /.*/ property :repo_gpgcheck, [TrueClass, FalseClass] property :report_instanceid, [TrueClass, FalseClass] - property :repositoryid, String, regex: /.*/, name_attribute: true + property :repositoryid, String, regex: /.*/, name_property: true property :sensitive, [TrueClass, FalseClass], default: false property :skip_if_unavailable, [TrueClass, FalseClass] property :source, String, regex: /.*/ diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index 5d29f766c9..626977b09f 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -85,6 +85,17 @@ class Chef attr_reader :parent_run_context # + # The root run context. + # + # @return [Chef::RunContext] The root run context. + # + def root_run_context + rc = self + rc = rc.parent_run_context until rc.parent_run_context.nil? + rc + end + + # # The collection of resources intended to be converged (and able to be # notified). # @@ -653,6 +664,7 @@ ERROR_MESSAGE notifies_immediately notifies_delayed parent_run_context + root_run_context resource_collection resource_collection= }.map { |x| x.to_sym } diff --git a/lib/chef/version.rb b/lib/chef/version.rb index 078d42c305..3413b702c4 100644 --- a/lib/chef/version.rb +++ b/lib/chef/version.rb @@ -21,7 +21,7 @@ class Chef CHEF_ROOT = File.expand_path("../..", __FILE__) - VERSION = "12.15.26" + VERSION = "12.16.23" end # |