summaryrefslogtreecommitdiff
path: root/lib/chef/node
diff options
context:
space:
mode:
authorSeth Chisamore <schisamo@opscode.com>2012-10-30 10:39:35 -0400
committerSeth Chisamore <schisamo@opscode.com>2012-10-30 10:39:35 -0400
commit24dc69a9a97e82a6e4207de68d6dcc664178249b (patch)
tree19bb289c9f88b4bbab066bc56b95d6d222fd5c35 /lib/chef/node
parent9348c1c9c80ee757354d624b7dc1b78ebc7605c4 (diff)
downloadchef-24dc69a9a97e82a6e4207de68d6dcc664178249b.tar.gz
[OC-3564] move core Chef to the repo root \o/ \m/
The opscode/chef repository now only contains the core Chef library code used by chef-client, knife and chef-solo!
Diffstat (limited to 'lib/chef/node')
-rw-r--r--lib/chef/node/attribute.rb254
-rw-r--r--lib/chef/node/attribute_collections.rb191
-rw-r--r--lib/chef/node/immutable_collections.rb387
3 files changed, 832 insertions, 0 deletions
diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb
new file mode 100644
index 0000000000..22075533b4
--- /dev/null
+++ b/lib/chef/node/attribute.rb
@@ -0,0 +1,254 @@
+#--
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/node/immutable_collections'
+require 'chef/node/attribute_collections'
+require 'chef/mixin/deep_merge'
+require 'chef/log'
+
+class Chef
+ class Node
+
+ # == Attribute
+ # Attribute implements a nested key-value (Hash) and flat collection
+ # (Array) data structure supporting multiple levels of precedence, such
+ # that a given key may have multiple values internally, but will only
+ # return the highest precedence value when reading.
+ class Attribute < Mash
+
+ include Immutablize
+
+ include Enumerable
+
+ COMPONENTS = [:@default, :@normal, :@override, :@automatic].freeze
+ COMPONENT_ACCESSORS = {:default => :@default,
+ :normal => :@normal,
+ :override => :@override,
+ :automatic => :@automatic
+ }
+
+ attr_accessor :properties
+ attr_reader :serial_number
+
+ [:all?,
+ :any?,
+ :assoc,
+ :chunk,
+ :collect,
+ :collect_concat,
+ :compare_by_identity,
+ :compare_by_identity?,
+ :count,
+ :cycle,
+ :detect,
+ :drop,
+ :drop_while,
+ :each,
+ :each_cons,
+ :each_entry,
+ :each_key,
+ :each_pair,
+ :each_slice,
+ :each_value,
+ :each_with_index,
+ :each_with_object,
+ :empty?,
+ :entries,
+ :except,
+ :fetch,
+ :find,
+ :find_all,
+ :find_index,
+ :first,
+ :flat_map,
+ :flatten,
+ :grep,
+ :group_by,
+ :has_value?,
+ :include?,
+ :index,
+ :inject,
+ :invert,
+ :key,
+ :keys,
+ :length,
+ :map,
+ :max,
+ :max_by,
+ :merge,
+ :min,
+ :min_by,
+ :minmax,
+ :minmax_by,
+ :none?,
+ :one?,
+ :partition,
+ :rassoc,
+ :reduce,
+ :reject,
+ :reverse_each,
+ :select,
+ :size,
+ :slice_before,
+ :sort,
+ :sort_by,
+ :store,
+ :symbolize_keys,
+ :take,
+ :take_while,
+ :to_a,
+ :to_hash,
+ :to_set,
+ :value?,
+ :values,
+ :values_at,
+ :zip].each do |delegated_method|
+ class_eval(<<-METHOD_DEFN)
+ def #{delegated_method}(*args, &block)
+ merged_attributes.send(:#{delegated_method}, *args, &block)
+ end
+ METHOD_DEFN
+ end
+
+ def initialize(normal, default, override, automatic)
+ @serial_number = 0
+ @set_unless_present = false
+
+ @normal = VividMash.new(self, normal)
+ @default = VividMash.new(self, default)
+ @override = VividMash.new(self, override)
+ @automatic = VividMash.new(self, automatic)
+
+ @merged_attributes = nil
+ end
+
+ def set_unless_value_present=(setting)
+ @set_unless_present = setting
+ end
+
+ def reset_cache
+ @serial_number += 1
+ @merged_attributes = nil
+ end
+
+ def reset
+ @serial_number += 1
+ @merged_attributes = nil
+ end
+
+ def default
+ @default
+ end
+
+ def default=(new_data)
+ reset
+ @default = VividMash.new(self, new_data)
+ end
+
+ def normal
+ @normal
+ end
+
+ def normal=(new_data)
+ reset
+ @normal = VividMash.new(self, new_data)
+ end
+
+ def override
+ @override
+ end
+
+ def override=(new_data)
+ reset
+ @override = VividMash.new(self, new_data)
+ end
+
+ def automatic
+ @automatic
+ end
+
+ def automatic=(new_data)
+ reset
+ @automatic = VividMash.new(self, new_data)
+ end
+
+ def merged_attributes
+ @merged_attributes ||= begin
+ resolved_attrs = COMPONENTS.inject(Mash.new) do |merged, component_ivar|
+ component_value = instance_variable_get(component_ivar)
+ Chef::Mixin::DeepMerge.merge(merged, component_value)
+ end
+ immutablize(self, resolved_attrs)
+ end
+ end
+
+ def [](key)
+ merged_attributes[key]
+ end
+
+ def []=(key, value)
+ merged_attributes[key] = value
+ end
+
+ def has_key?(key)
+ COMPONENTS.any? do |component_ivar|
+ instance_variable_get(component_ivar).has_key?(key)
+ end
+ end
+
+ alias :attribute? :has_key?
+ alias :member? :has_key?
+ alias :include? :has_key?
+ alias :key? :has_key?
+
+ alias :each_attribute :each
+
+ def method_missing(symbol, *args)
+ if args.empty?
+ if key?(symbol)
+ self[symbol]
+ else
+ raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'"
+ end
+ elsif symbol.to_s =~ /=$/
+ key_to_set = symbol.to_s[/^(.+)=$/, 1]
+ self[key_to_set] = (args.length == 1 ? args[0] : args)
+ else
+ raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'"
+ end
+ end
+
+ def inspect
+ "#<#{self.class} " << (COMPONENTS + [:@merged_attributes, :@properties]).map{|iv|
+ "#{iv}=#{instance_variable_get(iv).inspect}"
+ }.join(', ') << ">"
+ end
+
+ def set_unless?
+ @set_unless_present
+ end
+
+ def stale_subtree?(serial_number)
+ serial_number != @serial_number
+ end
+
+ end
+
+ end
+end
diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb
new file mode 100644
index 0000000000..521687e8df
--- /dev/null
+++ b/lib/chef/node/attribute_collections.rb
@@ -0,0 +1,191 @@
+#--
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, 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
+
+ # == AttrArray
+ # AttrArray is identical to Array, except that it keeps a reference to the
+ # "root" (Chef::Node::Attribute) object, and will trigger a cache
+ # invalidation on that object when mutated.
+ class AttrArray < Array
+
+ 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
+ ]
+
+ # For all of the methods that may mutate an Array, we override them to
+ # also invalidate the cached merged_attributes on the root
+ # Node::Attribute object.
+ MUTATOR_METHODS.each do |mutator|
+ class_eval(<<-METHOD_DEFN)
+ def #{mutator}(*args, &block)
+ root.reset_cache
+ super
+ end
+ METHOD_DEFN
+ end
+
+ attr_reader :root
+
+ def initialize(root, data)
+ @root = root
+ super(data)
+ end
+
+ end
+
+ # == VividMash
+ # VividMash is identical to a Mash, with a few exceptions:
+ # * It has a reference to the root Chef::Node::Attribute to which it
+ # belongs, and will trigger cache invalidation on that object when
+ # mutated.
+ # * It auto-vivifies, that is a reference to a missing element will result
+ # in the creation of a new VividMash for that key. (This only works when
+ # using the element reference method, `[]` -- other methods, such as
+ # #fetch, work as normal).
+ # * It supports a set_unless flag (via the root Attribute object) which
+ # allows `||=` style behavior (`||=` does not work with
+ # auto-vivification). This is only implemented for #[]=; methods such as
+ # #store work as normal.
+ # * attr_accessor style element set and get are supported via method_missing
+ class VividMash < Mash
+ attr_reader :root
+
+ # Methods that mutate a VividMash. Each of them is overridden so that it
+ # also invalidates the cached merged_attributes on the root Attribute
+ # object.
+ MUTATOR_METHODS = [
+ :clear,
+ :delete,
+ :delete_if,
+ :keep_if,
+ :merge!,
+ :update,
+ :reject!,
+ :replace,
+ :select!,
+ :shift
+ ]
+
+ # For all of the mutating methods on Mash, override them so that they
+ # also invalidate the cached `merged_attributes` on the root Attribute
+ # object.
+ MUTATOR_METHODS.each do |mutator|
+ class_eval(<<-METHOD_DEFN)
+ def #{mutator}(*args, &block)
+ root.reset_cache
+ super
+ end
+ METHOD_DEFN
+ end
+
+ def initialize(root, data={})
+ @root = root
+ super(data)
+ end
+
+ def [](key)
+ value = super
+ if !key?(key)
+ value = self.class.new(root)
+ self[key] = value
+ else
+ value
+ end
+ end
+
+ def []=(key, value)
+ if set_unless? && key?(key)
+ self[key]
+ else
+ root.reset_cache
+ super
+ end
+ end
+
+ alias :attribute? :has_key?
+
+ def method_missing(symbol, *args)
+ if args.empty?
+ self[symbol]
+ elsif symbol.to_s =~ /=$/
+ key_to_set = symbol.to_s[/^(.+)=$/, 1]
+ self[key_to_set] = (args.length == 1 ? args[0] : args)
+ else
+ raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'. To set an attribute, use `#{symbol}=value' instead."
+ end
+ end
+
+ def set_unless?
+ @root.set_unless?
+ end
+
+ def convert_key(key)
+ super
+ end
+
+ # Mash uses #convert_value to mashify values on input.
+ # We override it here to convert hash or array values to VividMash or
+ # AttrArray for consistency and to ensure that the added parts of the
+ # attribute tree will have the correct cache invalidation behavior.
+ def convert_value(value)
+ case value
+ when VividMash
+ value
+ when Hash
+ VividMash.new(root, value)
+ when Array
+ AttrArray.new(root, value)
+ else
+ value
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/lib/chef/node/immutable_collections.rb b/lib/chef/node/immutable_collections.rb
new file mode 100644
index 0000000000..33f722cd4a
--- /dev/null
+++ b/lib/chef/node/immutable_collections.rb
@@ -0,0 +1,387 @@
+
+class Chef
+ class Node
+
+ module Immutablize
+ def immutablize(root, value)
+ case value
+ when Hash
+ ImmutableMash.new(root, value)
+ when Array
+ ImmutableArray.new(root, value)
+ else
+ value
+ end
+ end
+ end
+
+ # == ImmutableArray
+ # ImmutableArray is used to implement Array collections when reading node
+ # attributes.
+ #
+ # ImmutableArray acts like an ordinary Array, except:
+ # * Methods that mutate the array are overridden to raise an error, making
+ # the collection more or less immutable.
+ # * Since this class stores values computed from a parent
+ # Chef::Node::Attribute's values, it overrides all reader methods to
+ # detect staleness and raise an error if accessed when stale.
+ class ImmutableArray < Array
+ include Immutablize
+
+ attr_reader :root
+
+ 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
+ ]
+
+ # A list of methods that read values from the Array. Each of these is
+ # overridden to verify that the Chef::Node::Attribute object that this
+ # object belongs to has not been modified since the value was computed.
+ READER_METHODS =
+ [
+ :&,
+ :*,
+ :+,
+ :-,
+ :[],
+ :all?,
+ :any?,
+ :assoc,
+ :at,
+ :chunk,
+ :collect,
+ :collect_concat,
+ :combination,
+ :compact,
+ :concat,
+ :count,
+ :cycle,
+ :detect,
+ :drop,
+ :drop_while,
+ :each,
+ :each_cons,
+ :each_entry,
+ :each_index,
+ :each_slice,
+ :each_with_index,
+ :each_with_object,
+ :empty?,
+ :entries,
+ :fetch,
+ :find,
+ :find_all,
+ :find_index,
+ :first,
+ :flat_map,
+ :flatten,
+ :grep,
+ :group_by,
+ :include?,
+ :index,
+ :inject,
+ :join,
+ :last,
+ :length,
+ :map,
+ :max,
+ :max_by,
+ :member?,
+ :min,
+ :min_by,
+ :minmax,
+ :minmax_by,
+ :none?,
+ :one?,
+ :pack,
+ :partition,
+ :permutation,
+ :product,
+ :rassoc,
+ :reduce,
+ :reject,
+ :repeated_combination,
+ :repeated_permutation,
+ :reverse,
+ :reverse_each,
+ :rindex,
+ :rotate,
+ :sample,
+ :select,
+ :shelljoin,
+ :shuffle,
+ :size,
+ :slice,
+ :slice_before,
+ :sort,
+ :sort_by,
+ :take,
+ :take_while,
+ :to_a,
+ :to_ary,
+ :to_set,
+ :transpose,
+ :uniq,
+ :values_at,
+ :zip,
+ :|
+ ]
+
+ def initialize(root, array_data)
+ @root = root
+ @serial_number = root.serial_number
+ array_data.each do |value|
+ internal_push(immutablize(root, 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|
+ # Ruby 1.8 blocks can't have block arguments, so we must use string eval:
+ class_eval(<<-METHOD_DEFN)
+ def #{mutator_method_name}(*args, &block)
+ msg = "Node attributes are read-only when you do not specify which precedence level to set. " +
+ %Q(To set an attribute use code like `node.default["key"] = "value"')
+ raise Exceptions::ImmutableAttributeModification, msg
+ end
+ METHOD_DEFN
+ end
+
+ READER_METHODS.each do |reader|
+ class_eval(<<-METHOD_DEFN)
+ def #{reader}(*args, &block)
+ if root.stale_subtree?(@serial_number)
+ raise Exceptions::StaleAttributeRead,
+ "Node attributes have been modified since this value was read. Get an updated value by reading from node, e.g., `node[:key]`"
+ end
+ super
+ end
+ METHOD_DEFN
+ end
+
+ def dup
+ Array.new(self)
+ end
+ end
+
+ # == ImmutableMash
+ # ImmutableMash implements Hash/Dict behavior for reading values from node
+ # attributes.
+ #
+ # ImmutableMash acts like a Mash (Hash that is indifferent to String or
+ # Symbol keys), with some important exceptions:
+ # * Methods that mutate state are overridden to raise an error instead.
+ # * Methods that read from the collection are overriden so that they check
+ # if the Chef::Node::Attribute has been modified since an instance of
+ # this class was generated. An error is raised if the object detects that
+ # it is stale.
+ # * Values can be accessed in attr_reader-like fashion via method_missing.
+ class ImmutableMash < Mash
+
+ include Immutablize
+
+ attr_reader :root
+
+ 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
+ ]
+
+ READER_METHODS = [
+ :[],
+ :all?,
+ :any?,
+ :assoc,
+ :chunk,
+ :collect,
+ :collect_concat,
+ :count,
+ :cycle,
+ :detect,
+ :drop,
+ :drop_while,
+ :each,
+ :each_cons,
+ :each_entry,
+ :each_key,
+ :each_pair,
+ :each_slice,
+ :each_value,
+ :each_with_index,
+ :each_with_object,
+ :empty?,
+ :entries,
+ :except,
+ :fetch,
+ :find,
+ :find_all,
+ :find_index,
+ :first,
+ :flat_map,
+ :flatten,
+ :grep,
+ :group_by,
+ :has_key?,
+ :has_value?,
+ :include?,
+ :index,
+ :inject,
+ :invert,
+ :key,
+ :key?,
+ :keys,
+ :length,
+ :map,
+ :max,
+ :max_by,
+ :member?,
+ :merge,
+ :min,
+ :min_by,
+ :minmax,
+ :minmax_by,
+ :none?,
+ :one?,
+ :partition,
+ :rassoc,
+ :reduce,
+ :reject,
+ :reverse_each,
+ :select,
+ :size,
+ :slice_before,
+ :sort,
+ :sort_by,
+ :store,
+ :symbolize_keys,
+ :take,
+ :take_while,
+ :to_a,
+ :to_hash,
+ :to_set,
+ :value?,
+ :values,
+ :values_at,
+ :zip
+ ]
+
+ def initialize(root, mash_data)
+ @serial_number = root.serial_number
+ @root = root
+ mash_data.each do |key, value|
+ internal_set(key, immutablize(root, value))
+ end
+ end
+
+ 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|
+ # Ruby 1.8 blocks can't have block arguments, so we must use string eval:
+ class_eval(<<-METHOD_DEFN)
+ def #{mutator_method_name}(*args, &block)
+ msg = "Node attributes are read-only when you do not specify which precedence level to set. " +
+ %Q(To set an attribute use code like `node.default["key"] = "value"')
+ raise Exceptions::ImmutableAttributeModification, msg
+ end
+ METHOD_DEFN
+ end
+
+ READER_METHODS.each do |reader_method|
+ class_eval(<<-METHOD_DEFN)
+ def #{reader_method}(*args, &block)
+ if root.stale_subtree?(@serial_number)
+ raise Exceptions::StaleAttributeRead,
+ "Node attributes have been modified since this value was read. Get an updated value by reading from node, e.g., `node[:key]`"
+ end
+ super
+ end
+ METHOD_DEFN
+ end
+
+ def method_missing(symbol, *args)
+ if args.empty?
+ if key?(symbol)
+ self[symbol]
+ else
+ raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'"
+ end
+ # This will raise a ImmutableAttributeModification error:
+ elsif symbol.to_s =~ /=$/
+ key_to_set = symbol.to_s[/^(.+)=$/, 1]
+ self[key_to_set] = (args.length == 1 ? args[0] : args)
+ else
+ raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'"
+ end
+ end
+
+ # Mash uses #convert_value to mashify values on input.
+ # Since we're handling this ourselves, override it to be a no-op
+ def convert_value(value)
+ value
+ end
+
+ # NOTE: #default and #default= are likely to be pretty confusing. For a
+ # regular ruby Hash, they control what value is returned for, e.g.,
+ # hash[:no_such_key] #=> hash.default
+ # Of course, 'default' has a specific meaning in Chef-land
+
+ def dup
+ Mash.new(self)
+ end
+ end
+
+ end
+end