# class Chef::CookbookVersion

## Chef::CookbookVersion¶ ↑

CookbookVersion is a model object encapsulating the data about a Chef cookbook. Chef supports maintaining multiple versions of a cookbook on a single server; each version is represented by a distinct instance of this class.

### Constants

COOKBOOK_SEGMENTS

### Attributes

attribute_filenames[R]

#attribute_filenames also has a setter that has non-default functionality.

attribute_filenames_by_short_filename[R]
attribute_files[R]

#attribute_filenames also has a setter that has non-default functionality.

definition_filenames[RW]
file_filenames[RW]
library_filenames[RW]
metadata[RW]
metadata_filenames[RW]
name[RW]
provider_filenames[RW]
recipe_filenames[R]

#recipe_filenames also has a setter that has non-default functionality.

recipe_filenames_by_name[R]
recipe_files[R]

#recipe_filenames also has a setter that has non-default functionality.

resource_filenames[RW]
root_dir[RW]
root_filenames[RW]
status[RW]
template_filenames[RW]

### Public Class Methods

available_versions(cookbook_name) click to toggle source

Given a `cookbook_name`, get a list of all versions that exist on the server.

### Returns¶ ↑

[String]

  Array of cookbook versions, which are strings like ‘x.y.z’

nil

  if the cookbook doesn’t exist. an error will also be logged.

    # File lib/chef/cookbook_version.rb, line 547
    def self.available_versions(cookbook_name)
      chef_server_rest.get_rest("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map do |cb|
        cb["version"]
      end
    rescue Net::HTTPServerException => e
      if e.to_s =~ /^404/
        Chef::Log.error("Cannot find a cookbook named #{cookbook_name}")
        nil
      else
        raise
      end
    end

cache() click to toggle source

    # File lib/chef/cookbook_version.rb, line 78
    def self.cache
      Chef::FileCache
    end

checksum_cookbook_file(filepath) click to toggle source

This is the one and only method that knows how cookbook files’ checksums are generated.

    # File lib/chef/cookbook_version.rb, line 71
    def self.checksum_cookbook_file(filepath)
      Chef::Digester.generate_md5_checksum_for_file(filepath)
    rescue Errno::ENOENT
      Chef::Log.debug("File #{filepath} does not exist, so there is no checksum to generate")
      nil
    end

chef_server_rest() click to toggle source

REST API

    # File lib/chef/cookbook_version.rb, line 500
    def self.chef_server_rest
      Chef::REST.new(Chef::Config[:chef_server_url])
    end

json_create(o) click to toggle source

    # File lib/chef/cookbook_version.rb, line 457
    def self.json_create(o)
      cookbook_version = new(o["cookbook_name"])
      # We want the Chef::Cookbook::Metadata object to always be inflated
      cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"])
      cookbook_version.manifest = o

      # We don't need the following step when we decide to stop supporting deprecated operators in the metadata (e.g. <<, >>)
      cookbook_version.manifest["metadata"] = Chef::JSONCompat.from_json(cookbook_version.metadata.to_json)

      cookbook_version.freeze_version if o["frozen?"]
      cookbook_version
    end

latest_cookbooks() click to toggle source

Get the newest version of all cookbooks

    # File lib/chef/cookbook_version.rb, line 561
    def self.latest_cookbooks
      chef_server_rest.get_rest('cookbooks/_latest')
    end

list() click to toggle source

The API returns only a single version of each cookbook in the result from the cookbooks method

    # File lib/chef/cookbook_version.rb, line 533
    def self.list
      chef_server_rest.get_rest('cookbooks')
    end

list_all_versions() click to toggle source

    # File lib/chef/cookbook_version.rb, line 537
    def self.list_all_versions
      chef_server_rest.get_rest('cookbooks?num_versions=all')
    end

load(name, version="_latest") click to toggle source

    # File lib/chef/cookbook_version.rb, line 527
    def self.load(name, version="_latest")
      version = "_latest" if version == "latest"
      chef_server_rest.get_rest("cookbooks/#{name}/#{version}")
    end

new(name) click to toggle source

Creates a new Chef::CookbookVersion object.

### Returns¶ ↑

object<Chef::CookbookVersion>

  Duh. :)

    # File lib/chef/cookbook_version.rb, line 86
    def initialize(name)
      @name = name
      @frozen = false
      @attribute_filenames = Array.new
      @definition_filenames = Array.new
      @template_filenames = Array.new
      @file_filenames = Array.new
      @recipe_filenames = Array.new
      @recipe_filenames_by_name = Hash.new
      @library_filenames = Array.new
      @resource_filenames = Array.new
      @provider_filenames = Array.new
      @metadata_filenames = Array.new
      @root_dir = nil
      @root_filenames = Array.new
      @status = :ready
      @manifest = nil
      @file_vendor = nil
      @metadata = Chef::Cookbook::Metadata.new
    end

### Public Instance Methods

<=>(o) click to toggle source

    # File lib/chef/cookbook_version.rb, line 565
    def <=>(o)
      raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != o.name
      # FIXME: can we change the interface to the Metadata class such
      # that metadata.version returns a Chef::Version instance instead
      # of a string?
      Chef::Version.new(self.version) <=> Chef::Version.new(o.version)
    end

attribute_filenames=(*filenames) click to toggle source

    # File lib/chef/cookbook_version.rb, line 194
    def attribute_filenames=(*filenames)
      @attribute_filenames = filenames.flatten
      @attribute_filenames_by_short_filename = filenames_by_name(attribute_filenames)
      attribute_filenames
    end

Also aliased as: attribute_files=
attribute_files=(*filenames)
Alias for: attribute_filenames=
checksums() click to toggle source

Returns a hash of checksums to either nil or the on disk path (which is done by generate_manifest).

    # File lib/chef/cookbook_version.rb, line 178
    def checksums
      unless @checksums
        generate_manifest
      end
      @checksums
    end

chef_server_rest() click to toggle source

    # File lib/chef/cookbook_version.rb, line 504
    def chef_server_rest
      self.class.chef_server_rest
    end

destroy() click to toggle source

    # File lib/chef/cookbook_version.rb, line 522
    def destroy
      chef_server_rest.delete_rest("cookbooks/#{name}/#{version}")
      self
    end

force_save_url() click to toggle source

Adds the `force=true` parameter to the upload URL. This allows the user to overwrite a frozen cookbook (a PUT against the normal save_url raises a 409 Conflict in this case).

    # File lib/chef/cookbook_version.rb, line 518
    def force_save_url
      "cookbooks/#{name}/#{version}?force=true"
    end

freeze_version() click to toggle source

    # File lib/chef/cookbook_version.rb, line 118
    def freeze_version
      @frozen = true
    end

frozen_version?() click to toggle source

Indicates if this version is frozen or not. Freezing a coobkook version indicates that a new cookbook with the same name and version number shoule

    # File lib/chef/cookbook_version.rb, line 114
    def frozen_version?
      @frozen
    end

full_name() click to toggle source

    # File lib/chef/cookbook_version.rb, line 190
    def full_name
      "#{name}-#{version}"
    end

fully_qualified_recipe_names() click to toggle source

Return recipe names in the form of cookbook_name::recipe_name

    # File lib/chef/cookbook_version.rb, line 205
    def fully_qualified_recipe_names
      results = Array.new
      recipe_filenames_by_name.each_key do |rname|
        results << "#{name}::#{rname}"
      end
      results
    end

generate_manifest_with_urls(&url_generator) click to toggle source

    # File lib/chef/cookbook_version.rb, line 470
    def generate_manifest_with_urls(&url_generator)
      rendered_manifest = manifest.dup
      COOKBOOK_SEGMENTS.each do |segment|
        if rendered_manifest.has_key?(segment)
          rendered_manifest[segment].each do |manifest_record|
            url_options = { :cookbook_name => name.to_s, :cookbook_version => version, :checksum => manifest_record["checksum"] }
            manifest_record["url"] = url_generator.call(url_options)
          end
        end
      end
      rendered_manifest
    end

has_cookbook_file_for_node?(node, cookbook_filename) click to toggle source

Query whether a cookbook_file file `cookbook_filename` is available. File specificity for the given `node` is obeyed in the lookup.

    # File lib/chef/cookbook_version.rb, line 276
    def has_cookbook_file_for_node?(node, cookbook_filename)
      !!find_preferred_manifest_record(node, :files, cookbook_filename)
    end

has_template_for_node?(node, template_filename) click to toggle source

Query whether a template file `template_filename` is available. File specificity for the given `node` is obeyed in the lookup.

    # File lib/chef/cookbook_version.rb, line 270
    def has_template_for_node?(node, template_filename)
      !!find_preferred_manifest_record(node, :templates, template_filename)
    end

load_recipe(recipe_name, run_context) click to toggle source

called from DSL

    # File lib/chef/cookbook_version.rb, line 224
    def load_recipe(recipe_name, run_context)
      unless recipe_filenames_by_name.has_key?(recipe_name)
        raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}"
      end

      Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{name}")
      recipe = Chef::Recipe.new(name, recipe_name, run_context)
      recipe_filename = recipe_filenames_by_name[recipe_name]

      unless recipe_filename
        raise Chef::Exceptions::RecipeNotFound, "could not find #{recipe_name} files for cookbook #{name}"
      end

      recipe.from_file(recipe_filename)
      recipe
    end

manifest() click to toggle source

A manifest is a Mash that maps segment names to arrays of manifest records (see preferred_manifest_record for format of manifest records), as well as describing cookbook metadata. The manifest follows a form like the following:

    {
      :cookbook_name = "apache2",
      :version = "1.0",
      :name = "Apache 2"
      :metadata = ???TODO: timh/cw: 5-24-2010: describe this format,

      :files => [
        {
          :name => "afile.rb",
          :path => "files/ubuntu-9.10/afile.rb",
          :checksum => "2222",
          :specificity => "ubuntu-9.10"
        },
      ],
      :templates => [ manifest_record1, ... ],
      ...
    }

    # File lib/chef/cookbook_version.rb, line 149
    def manifest
      unless @manifest
        generate_manifest
      end
      @manifest
    end

manifest=(new_manifest) click to toggle source

    # File lib/chef/cookbook_version.rb, line 156
    def manifest=(new_manifest)
      @manifest = Mash.new new_manifest
      @checksums = extract_checksums_from_manifest(@manifest)
      @manifest_records_by_path = extract_manifest_records_by_path(@manifest)

      COOKBOOK_SEGMENTS.each do |segment|
        next unless @manifest.has_key?(segment)
        filenames = @manifest[segment].map{|manifest_record| manifest_record['name']}

        if segment == :recipes
          self.recipe_filenames = filenames
        elsif segment == :attributes
          self.attribute_filenames = filenames
        else
          segment_filenames(segment).clear
          filenames.each { |filename| segment_filenames(segment) << filename }
        end
      end
    end

manifest_records_by_path() click to toggle source

    # File lib/chef/cookbook_version.rb, line 185
    def manifest_records_by_path
      @manifest_records_by_path || generate_manifest
      @manifest_records_by_path
    end

metadata_json_file() click to toggle source

    # File lib/chef/cookbook_version.rb, line 483
    def metadata_json_file
      File.join(root_dir, "metadata.json")
    end

metadata_rb_file() click to toggle source

    # File lib/chef/cookbook_version.rb, line 487
    def metadata_rb_file
      File.join(root_dir, "metadata.rb")
    end

preferred_filename_on_disk_location(node, segment, filename, current_filepath=nil) click to toggle source

    # File lib/chef/cookbook_version.rb, line 318
    def preferred_filename_on_disk_location(node, segment, filename, current_filepath=nil)
      manifest_record = preferred_manifest_record(node, segment, filename)
      if current_filepath && (manifest_record['checksum'] == self.class.checksum_cookbook_file(current_filepath))
        nil
      else
        file_vendor.get_filename(manifest_record['path'])
      end
    end

preferred_manifest_record(node, segment, filename) click to toggle source

Determine the most specific manifest record for the given segment/filename, given information in the node. Throws FileNotFound if there is no such segment and filename in the manifest.

A manifest record is a Mash that follows the following form: {

    :name => "example.rb",
    :path => "files/default/example.rb",
    :specificity => "default",
    :checksum => "1234"

}

    # File lib/chef/cookbook_version.rb, line 292
    def preferred_manifest_record(node, segment, filename)
      found_pref = find_preferred_manifest_record(node, segment, filename)
      if found_pref
        @manifest_records_by_path[found_pref]
      else
        if segment == :files || segment == :templates
          error_message = "Cookbook '#{name}' (#{version}) does not contain a file at any of these locations:\n"
          error_locations = [
            "  #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}",
            "  #{segment}/#{node[:platform]}/#{filename}",
            "  #{segment}/default/#{filename}",
          ]
          error_message << error_locations.join("\n")
          existing_files = segment_filenames(segment)
          # Show the files that the cookbook does have. If the user made a typo,
          # hopefully they'll see it here.
          unless existing_files.empty?
            error_message << "\n\nThis cookbook _does_ contain: ['#{existing_files.join("','")}']"
          end
          raise Chef::Exceptions::FileNotFound, error_message
        else
          raise Chef::Exceptions::FileNotFound, "cookbook #{name} does not contain file #{segment}/#{filename}"
        end
      end
    end

preferred_manifest_records_for_directory(node, segment, dirname) click to toggle source

Determine the manifest records from the most specific directory for the given node. See preferred_manifest_record for a description of entries of the returned Array.

    # File lib/chef/cookbook_version.rb, line 368
    def preferred_manifest_records_for_directory(node, segment, dirname)
      preferences = preferences_for_path(node, segment, dirname)
      records_by_pref = Hash.new
      preferences.each { |pref| records_by_pref[pref] = Array.new }

      manifest[segment].each do |manifest_record|
        manifest_record_path = manifest_record[:path]

        # extract the preference part from the path.
        if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/
          # Note the specificy_dirname includes the segment and
          # dirname argument as above, which is what
          # preferences_for_path returns. It could be
          # "files/ubuntu-9.10/dirname", for example.
          specificity_dirname = $1

          # Record the specificity_dirname only if it's in the list of
          # valid preferences
          if records_by_pref[specificity_dirname]
            records_by_pref[specificity_dirname] << manifest_record
          end
        end
      end

      best_pref = preferences.find { |pref| !records_by_pref[pref].empty? }

      raise Chef::Exceptions::FileNotFound, "cookbook #{name} (#{version}) has no directory #{segment}/default/#{dirname}" unless best_pref

      records_by_pref[best_pref]
    end

recipe_filenames=(*filenames) click to toggle source

    # File lib/chef/cookbook_version.rb, line 213
    def recipe_filenames=(*filenames)
      @recipe_filenames = filenames.flatten
      @recipe_filenames_by_name = filenames_by_name(recipe_filenames)
      recipe_filenames
    end

Also aliased as: recipe_files=
recipe_files=(*filenames)
Alias for: recipe_filenames=
relative_filenames_in_preferred_directory(node, segment, dirname) click to toggle source

    # File lib/chef/cookbook_version.rb, line 327
    def relative_filenames_in_preferred_directory(node, segment, dirname)
      preferences = preferences_for_path(node, segment, dirname)
      filenames_by_pref = Hash.new
      preferences.each { |pref| filenames_by_pref[pref] = Array.new }

      manifest[segment].each do |manifest_record|
        manifest_record_path = manifest_record[:path]

        # find the NON SPECIFIC filenames, but prefer them by filespecificity.
        # For example, if we have a file:
        # 'files/default/somedir/somefile.conf' we only keep
        # 'somedir/somefile.conf'. If there is also
        # 'files/$hostspecific/somedir/otherfiles' that matches the requested
        # hostname specificity, that directory will win, as it is more specific.
        #
        # This is clearly ugly b/c the use case is for remote directory, where
        # we're just going to make cookbook_files out of these and make the
        # cookbook find them by filespecificity again. but it's the shortest
        # path to "success" for now.
        if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/
          specificity_dirname = $1
          non_specific_path = manifest_record_path[/#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)}\/(.+)$/, 1]
          # Record the specificity_dirname only if it's in the list of
          # valid preferences
          if filenames_by_pref[specificity_dirname]
            filenames_by_pref[specificity_dirname] << non_specific_path
          end
        end
      end

      best_pref = preferences.find { |pref| !filenames_by_pref[pref].empty? }

      raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/default/#{dirname}" unless best_pref

      filenames_by_pref[best_pref]

    end

reload_metadata!() click to toggle source

    # File lib/chef/cookbook_version.rb, line 491
    def reload_metadata!
      if File.exists?(metadata_json_file)
        metadata.from_json(IO.read(metadata_json_file))
      end
    end

save_url() click to toggle source

Return the URL to save (PUT) this object to the server via the REST api. If there is an existing document on the server and it is marked frozen, a PUT will result in a 409 Conflict.

    # File lib/chef/cookbook_version.rb, line 511
    def save_url
      "cookbooks/#{name}/#{version}"
    end

segment_filenames(segment) click to toggle source

    # File lib/chef/cookbook_version.rb, line 241
    def segment_filenames(segment)
      unless COOKBOOK_SEGMENTS.include?(segment)
        raise ArgumentError, "invalid segment #{segment}: must be one of #{COOKBOOK_SEGMENTS.join(', ')}"
      end

      case segment.to_sym
      when :resources
        @resource_filenames
      when :providers
        @provider_filenames
      when :recipes
        @recipe_filenames
      when :libraries
        @library_filenames
      when :definitions
        @definition_filenames
      when :attributes
        @attribute_filenames
      when :files
        @file_filenames
      when :templates
        @template_filenames
      when :root_files
        @root_filenames
      end
    end

to_hash() click to toggle source

    # File lib/chef/cookbook_version.rb, line 444
    def to_hash
      result = manifest.dup
      result['frozen?'] = frozen_version?
      result['chef_type'] = 'cookbook_version'
      result.to_hash
    end

to_json(*a) click to toggle source

    # File lib/chef/cookbook_version.rb, line 451
    def to_json(*a)
      result = self.to_hash
      result['json_class'] = self.class.name
      result.to_json(*a)
    end

version() click to toggle source

    # File lib/chef/cookbook_version.rb, line 107
    def version
      metadata.version
    end

version=(new_version) click to toggle source

    # File lib/chef/cookbook_version.rb, line 122
    def version=(new_version)
      manifest["version"] = new_version
      metadata.version(new_version)
    end