class Chef module ChefFS module DataHandler # # The base class for all *DataHandlers. # # DataHandlers' job is to know the innards of Chef objects and manipulate # JSON for them, adding defaults and formatting them. # class DataHandlerBase # # Remove all default values from a Chef object's JSON so that the only # thing you see are the values that have been explicitly set. # Achieves this by calling normalize({}, entry) to get the list of # defaults, and subtracting anything that is the same. # def minimize(object, entry) default_object = default(entry) object.each_pair do |key, value| if default_object[key] == value && !preserve_key?(key) object.delete(key) end end object end def remove_file_extension(name, ext = ".*") if %w{ .rb .json }.include?(File.extname(name)) File.basename(name, ext) else name end end alias_method :remove_dot_json, :remove_file_extension # # Return true if minimize() should preserve a key even if it is the same # as the default. Often used for ids and names. # def preserve_key?(key) false end # # Get the default value for an entry. Calls normalize({}, entry). # def default(entry) normalize({}, entry) end # # Utility function to help subclasses do normalize(). Pass in a hash # and a list of keys with defaults, and normalize will: # # 1. Fill in the defaults # 2. Put the actual values in the order of the defaults # 3. Move any other values to the end # # @example # normalize_hash({x: 100, c: 2, a: 1}, { a: 10, b: 20, c: 30}) # -> { a: 1, b: 20, c: 2, x: 100} # def normalize_hash(object, defaults) # Make a normalized result in the specified order for diffing result = {} defaults.each_pair do |key, value| result[key] = object.is_a?(Hash) && object.key?(key) ? object[key] : value end if object.is_a?(Hash) object.each_pair do |key, value| result[key] = value unless result.key?(key) end else Chef::Log.warn "Encountered invalid object during normalization. Using these defaults #{defaults}" end result end # Specialized function to normalize an object before POSTing it, since # some object types want slightly different values on POST. # If not overridden, this just calls normalize() def normalize_for_post(object, entry) normalize(object, entry) end # Specialized function to normalize an object before PUTing it, since # some object types want slightly different values on PUT. # If not overridden, this just calls normalize(). def normalize_for_put(object, entry) normalize(object, entry) end # # normalize a run list (an array of run list items). # Leaves recipe[name] and role[name] alone, and translates # name to recipe[name]. Then calls uniq on the result. # def normalize_run_list(run_list) run_list.map do |item| case item.to_s when /^recipe\[.*\]$/ item # explicit recipe when /^role\[.*\]$/ item # explicit role else "recipe[#{item}]" end end.uniq end # # Bring in an instance of this object from Ruby. (Like roles/x.rb) # def from_ruby(path) r = chef_class.new r.from_file(path) r.to_h end # # Turn a JSON hash into a bona fide Chef object (like Chef::Node). # def chef_object(object) chef_class.from_hash(object) end # # Write out the Ruby file for this instance. (Like roles/x.rb) # def to_ruby(object) raise NotImplementedError end # # Get the class for instances of this type. Must be overridden. # def chef_class raise NotImplementedError end # # Helper to write out a Ruby file for a JSON hash. Writes out only # the keys specified in "keys"; anything else must be emitted by the # caller. # # @example # to_ruby_keys({"name" => "foo", "environment" => "desert", "foo": "bar"}, [ "name", "environment" ]) # -> # 'name "foo" # environment "desert"' # def to_ruby_keys(object, keys) result = "" keys.each do |key| if object[key] if object[key].is_a?(Hash) if object[key].size > 0 result << key first = true object[key].each_pair do |k, v| if first first = false else result << " " * key.length end result << " #{k.inspect} => #{v.inspect}\n" end end elsif object[key].is_a?(Array) if object[key].size > 0 result << key first = true object[key].each do |value| if first first = false else result << ", " end result << value.inspect end result << "\n" end elsif !object[key].nil? result << "#{key} #{object[key].inspect}\n" end end end result end # Verify that the JSON hash for this type has a key that matches its name. # # @param object [Object] JSON hash of the object # @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying # @yield [s] callback to handle errors # @yieldparam [s] error message def verify_integrity(object, entry) base_name = remove_file_extension(entry.name) if object["name"] != base_name yield("Name must be '#{base_name}' (is '#{object['name']}')") end end end # class DataHandlerBase end end end