Parent

Included Modules

Files

Class/Module Index [+]

Quicksearch

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 651
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 604
def self.chef_server_rest
  Chef::REST.new(Chef::Config[:chef_server_url])
end
cleanup_file_cache() click to toggle source
# File lib/chef/cookbook_version.rb, line 178
def self.cleanup_file_cache
  unless Chef::Config[:solo]
    # Delete each file in the cache that we didn't encounter in the
    # manifest.
    cache.find(File.join(%{cookbooks ** *})).each do |cache_filename|
      unless valid_cache_entries[cache_filename]
        Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer needed by chef-client.")
        cache.delete(cache_filename)
      end
    end
  end
end
clear_obsoleted_cookbooks(cookbook_hash) click to toggle source

Iterates over cached cookbooks' files, removing files belonging to cookbooks that don't appear in cookbook_hash

# File lib/chef/cookbook_version.rb, line 103
def self.clear_obsoleted_cookbooks(cookbook_hash)
  # Remove all cookbooks no longer relevant to this node
  cache.find(File.join(%{cookbooks ** *})).each do |cache_file|
    cache_file =~ /^cookbooks\/([^\/]+)\//
    unless cookbook_hash.has_key?($1)
      Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.")
      cache.delete(cache_file)
    end
  end
end
json_create(o) click to toggle source
# File lib/chef/cookbook_version.rb, line 561
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 665
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 637
def self.list
  chef_server_rest.get_rest('cookbooks')
end
list_all_versions() click to toggle source
# File lib/chef/cookbook_version.rb, line 641
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 631
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 195
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
sync_cookbook_file_cache(cookbook) click to toggle source

Update the file caches for a given cache segment. Takes a segment name and a hash that matches one of the cookbooks/_attribute_files style remote file listings.

Parameters

cookbook<Chef::Cookbook>

The cookbook to update

valid_cache_entries<Hash>

Out-param; Added to this hash are the files that

were referred to by this cookbook

# File lib/chef/cookbook_version.rb, line 122
def self.sync_cookbook_file_cache(cookbook)
  Chef::Log.debug("Synchronizing cookbook #{cookbook.name}")

  # files and templates are lazily loaded, and will be done later.
  eager_segments = COOKBOOK_SEGMENTS.dup

  unless Chef::Config[:no_lazy_load] then
    eager_segments.delete(:files)
    eager_segments.delete(:templates)
  end

  eager_segments.each do |segment|
    segment_filenames = Array.new
    cookbook.manifest[segment].each do |manifest_record|
      # segment = cookbook segment
      # remote_list = list of file hashes
      #
      # We need the list of known good attribute files, so we can delete any that are
      # just laying about.

      cache_filename = File.join("cookbooks", cookbook.name, manifest_record['path'])
      valid_cache_entries[cache_filename] = true

      current_checksum = nil
      if cache.has_key?(cache_filename)
        current_checksum = checksum_cookbook_file(cache.load(cache_filename, false))
      end

      # If the checksums are different between on-disk (current) and on-server
      # (remote, per manifest), do the update. This will also execute if there
      # is no current checksum.
      if current_checksum != manifest_record['checksum']
        raw_file = chef_server_rest.get_rest(manifest_record[:url], true)

        Chef::Log.info("Storing updated #{cache_filename} in the cache.")
        cache.move_to(raw_file.path, cache_filename)
      else
        Chef::Log.debug("Not storing #{cache_filename}, as the cache is up to date.")
      end

      # make the segment filenames a full path.
      full_path_cache_filename = cache.load(cache_filename, false)
      segment_filenames << full_path_cache_filename
    end

    # replace segment filenames with a full-path one.
    if segment.to_sym == :recipes
      cookbook.recipe_filenames = segment_filenames
    elsif segment.to_sym == :attributes
      cookbook.attribute_filenames = segment_filenames
    else
      cookbook.segment_filenames(segment).replace(segment_filenames)
    end
  end
end
sync_cookbooks(cookbook_hash) click to toggle source

Synchronizes all the cookbooks from the chef-server.

Returns

true

Always returns true

# File lib/chef/cookbook_version.rb, line 86
def self.sync_cookbooks(cookbook_hash)
  Chef::Log.info("Loading cookbooks [#{cookbook_hash.keys.sort.join(', ')}]")
  Chef::Log.debug("Cookbooks detail: #{cookbook_hash.inspect}")

  clear_obsoleted_cookbooks(cookbook_hash)

  # Synchronize each of the node's cookbooks, and add to the
  # valid_cache_entries hash.
  cookbook_hash.values.each do |cookbook|
    sync_cookbook_file_cache(cookbook)
  end

  true
end

Public Instance Methods

<=>(o) click to toggle source
# File lib/chef/cookbook_version.rb, line 669
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 303
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) click to toggle source
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 287
def checksums
  unless @checksums
    generate_manifest
  end
  @checksums
end
chef_server_rest() click to toggle source
# File lib/chef/cookbook_version.rb, line 608
def chef_server_rest
  self.class.chef_server_rest
end
destroy() click to toggle source
# File lib/chef/cookbook_version.rb, line 626
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 622
def force_save_url
  "cookbooks/#{name}/#{version}?force=true"
end
freeze_version() click to toggle source
# File lib/chef/cookbook_version.rb, line 227
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 223
def frozen_version?
  @frozen
end
full_name() click to toggle source
# File lib/chef/cookbook_version.rb, line 299
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 314
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 574
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
load_recipe(recipe_name, run_context) click to toggle source

called from DSL

# File lib/chef/cookbook_version.rb, line 333
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 258
def manifest
  unless @manifest
    generate_manifest
  end
  @manifest
end
manifest=(new_manifest) click to toggle source
# File lib/chef/cookbook_version.rb, line 265
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 294
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 587
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 591
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 422
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 389
def preferred_manifest_record(node, segment, filename)
  preferences = preferences_for_path(node, segment, filename)

  # ensure that we generate the manifest, which will also generate
  # @manifest_records_by_path
  manifest

  # in order of prefernce, look for the filename in the manifest
  found_pref = preferences.find {|preferred_filename| @manifest_records_by_path[preferred_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 472
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 322
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) click to toggle source
Alias for: recipe_filenames=
relative_filenames_in_preferred_directory(node, segment, dirname) click to toggle source
# File lib/chef/cookbook_version.rb, line 431
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 595
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 615
def save_url
  "cookbooks/#{name}/#{version}"
end
segment_filenames(segment) click to toggle source
# File lib/chef/cookbook_version.rb, line 350
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 548
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 555
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 216
def version
  metadata.version
end
version=(new_version) click to toggle source
# File lib/chef/cookbook_version.rb, line 231
def version=(new_version)
  manifest["version"] = new_version
  metadata.version(new_version)
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.