Class: Chef::CookbookVersion

Inherits:
Object
  • Object
show all
Includes:
IndexQueue::Indexable
Defined in:
lib/chef/cookbook_version.rb

Overview

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. – TODO: timh/cw: 5-24-2010: mutators for files (e.g., recipe_filenames=, recipe_filenames.insert) should dirty the manifest so it gets regenerated.

Constant Summary collapse

COOKBOOK_SEGMENTS =
[ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ]
DESIGN_DOCUMENT =
{
  "version" => 7,
  "language" => "javascript",
  "views" => {
    "all" => {
      "map" => <<-EOJS
      function(doc) { 
        if (doc.chef_type == "cookbook_version") {
          emit(doc.name, doc);
        }
      }
      EOJS
    },
    "all_id" => {
      "map" => <<-EOJS
      function(doc) { 
        if (doc.chef_type == "cookbook_version") {
          emit(doc.name, doc.name);
        }
      }
      EOJS
    },
    "all_with_version" => {
      "map" => <<-EOJS
      function(doc) { 
        if (doc.chef_type == "cookbook_version") {
          emit(doc.cookbook_name, doc.version);
        }
      }
      EOJS
    },
    "all_latest_version" => {
      "map" => %q@
      function(doc) { 
        if (doc.chef_type == "cookbook_version") {
          emit(doc.cookbook_name, doc.version);
        }
      }
      @,
      "reduce" => %q@
      function(keys, values, rereduce) {
        var result = null;

        for (var idx in values) {
          var value = values[idx];
          
          if (idx == 0) {
            result = value;
            continue;
          }
          
          var valueParts = value.split('.').map(function(v) { return parseInt(v); });
          var resultParts = result.split('.').map(function(v) { return parseInt(v); });

          if (valueParts[0] != resultParts[0]) {
            if (valueParts[0] > resultParts[0]) {
              result = value;
            }
          }
          else if (valueParts[1] != resultParts[1]) {
            if (valueParts[1] > resultParts[1]) {
              result = value;
            }
          }
          else if (valueParts[2] != resultParts[2]) {
            if (valueParts[2] > resultParts[2]) {
              result = value;
            }
          }
        }
        return result;
      }
      @
    },
    "all_latest_version_by_id" => {
      "map" => %q@
      function(doc) {
        if (doc.chef_type == "cookbook_version") {
          emit(doc.cookbook_name, {version: doc.version, id:doc._id});
        }
      }
      @,
      "reduce" => %q@
      function(keys, values, rereduce) {
        var result = null;

        for (var idx in values) {
          var value = values[idx];

          if (idx == 0) {
            result = value;
            continue;
          }

          var valueParts = value.version.split('.').map(function(v) { return parseInt(v); });
          var resultParts = result.version.split('.').map(function(v) { return parseInt(v); });

          if (valueParts[0] != resultParts[0]) {
            if (valueParts[0] > resultParts[0]) {
              result = value;
            }
          }
          else if (valueParts[1] != resultParts[1]) {
            if (valueParts[1] > resultParts[1]) {
              result = value;
            }
          }
          else if (valueParts[2] != resultParts[2]) {
            if (valueParts[2] > resultParts[2]) {
              result = value;
            }
          }
        }
        return result;
      }
      @
    },
  }
}

Instance Attribute Summary collapse

Attributes included from IndexQueue::Indexable

#index_id

Class Method Summary collapse

Instance Method Summary collapse

Methods included from IndexQueue::Indexable

#add_to_index, #delete_from_index, included, #index_object_type, #with_indexer_metadata

Constructor Details

#initialize(name, couchdb = nil) ⇒ CookbookVersion

Creates a new Chef::CookbookVersion object.

Returns

object<Chef::CookbookVersion>

Duh. :)



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/chef/cookbook_version.rb', line 336

def initialize(name, couchdb=nil)
  @name = name
  @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
  @couchdb_id = nil
  @couchdb = couchdb || Chef::CouchDB.new
  @couchdb_rev = nil
  @status = :ready
  @manifest = nil
  @file_vendor = nil
  @metadata = Chef::Cookbook::Metadata.new
end

Instance Attribute Details

#attribute_filenamesObject Also known as: attribute_files

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



182
183
184
# File 'lib/chef/cookbook_version.rb', line 182

def attribute_filenames
  @attribute_filenames
end

#attribute_filenames_by_short_filenameObject (readonly)

Returns the value of attribute attribute_filenames_by_short_filename.



189
190
191
# File 'lib/chef/cookbook_version.rb', line 189

def attribute_filenames_by_short_filename
  @attribute_filenames_by_short_filename
end

#couchdbObject

Returns the value of attribute couchdb.



176
177
178
# File 'lib/chef/cookbook_version.rb', line 176

def couchdb
  @couchdb
end

#couchdb_idObject

Returns the value of attribute couchdb_id.



178
179
180
# File 'lib/chef/cookbook_version.rb', line 178

def couchdb_id
  @couchdb_id
end

#couchdb_revObject

Returns the value of attribute couchdb_rev.



175
176
177
# File 'lib/chef/cookbook_version.rb', line 175

def couchdb_rev
  @couchdb_rev
end

#definition_filenamesObject

Returns the value of attribute definition_filenames.



164
165
166
# File 'lib/chef/cookbook_version.rb', line 164

def definition_filenames
  @definition_filenames
end

#file_filenamesObject

Returns the value of attribute file_filenames.



166
167
168
# File 'lib/chef/cookbook_version.rb', line 166

def file_filenames
  @file_filenames
end

#library_filenamesObject

Returns the value of attribute library_filenames.



167
168
169
# File 'lib/chef/cookbook_version.rb', line 167

def library_filenames
  @library_filenames
end

#metadataObject

Returns the value of attribute metadata.



172
173
174
# File 'lib/chef/cookbook_version.rb', line 172

def 
  @metadata
end

#metadata_filenamesObject

Returns the value of attribute metadata_filenames.



173
174
175
# File 'lib/chef/cookbook_version.rb', line 173

def 
  @metadata_filenames
end

#nameObject

Returns the value of attribute name.



171
172
173
# File 'lib/chef/cookbook_version.rb', line 171

def name
  @name
end

#provider_filenamesObject

Returns the value of attribute provider_filenames.



169
170
171
# File 'lib/chef/cookbook_version.rb', line 169

def provider_filenames
  @provider_filenames
end

#recipe_filenamesObject Also known as: recipe_files

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



186
187
188
# File 'lib/chef/cookbook_version.rb', line 186

def recipe_filenames
  @recipe_filenames
end

#recipe_filenames_by_nameObject (readonly)

Returns the value of attribute recipe_filenames_by_name.



188
189
190
# File 'lib/chef/cookbook_version.rb', line 188

def recipe_filenames_by_name
  @recipe_filenames_by_name
end

#resource_filenamesObject

Returns the value of attribute resource_filenames.



168
169
170
# File 'lib/chef/cookbook_version.rb', line 168

def resource_filenames
  @resource_filenames
end

#root_dirObject

Returns the value of attribute root_dir.



163
164
165
# File 'lib/chef/cookbook_version.rb', line 163

def root_dir
  @root_dir
end

#root_filenamesObject

Returns the value of attribute root_filenames.



170
171
172
# File 'lib/chef/cookbook_version.rb', line 170

def root_filenames
  @root_filenames
end

#statusObject

Returns the value of attribute status.



174
175
176
# File 'lib/chef/cookbook_version.rb', line 174

def status
  @status
end

#template_filenamesObject

Returns the value of attribute template_filenames.



165
166
167
# File 'lib/chef/cookbook_version.rb', line 165

def template_filenames
  @template_filenames
end

Class Method Details

.available_versions(cookbook_name) ⇒ Object

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.



741
742
743
744
745
746
747
748
749
750
# File 'lib/chef/cookbook_version.rb', line 741

def self.available_versions(cookbook_name)
  chef_server_rest.get_rest("cookbooks/#{cookbook_name}").values.flatten
rescue Net::HTTPServerException => e
  if e.to_s =~ /^404/
    Chef::Log.error("Cannot find a cookbook named #{cookbook_name}")
    nil
  else
    raise
  end
end

.cacheObject



212
213
214
# File 'lib/chef/cookbook_version.rb', line 212

def self.cache
  Chef::FileCache
end

.cdb_by_name(cookbook_name, couchdb = nil) ⇒ Object

Couchdb



761
762
763
764
765
766
# File 'lib/chef/cookbook_version.rb', line 761

def self.cdb_by_name(cookbook_name, couchdb=nil)
  cdb = (couchdb || Chef::CouchDB.new)
  options = { :startkey => cookbook_name, :endkey => cookbook_name }
  rs = cdb.get_view("cookbooks", "all_with_version", options)
  rs["rows"].inject({}) { |memo, row| memo.has_key?(row["key"]) ? memo[row["key"]] << row["value"] : memo[row["key"]] = [ row["value"] ]; memo }
end

.cdb_list(inflate = false, couchdb = nil) ⇒ Object



789
790
791
792
793
# File 'lib/chef/cookbook_version.rb', line 789

def self.cdb_list(inflate=false, couchdb=nil)
  rs = (couchdb || Chef::CouchDB.new).list("cookbooks", inflate)
  lookup = (inflate ? "value" : "key")
  rs["rows"].collect { |r| r[lookup] }            
end

.cdb_list_latest(inflate = false, couchdb = nil) ⇒ Object



772
773
774
775
776
777
778
779
780
781
# File 'lib/chef/cookbook_version.rb', line 772

def self.cdb_list_latest(inflate=false, couchdb=nil)
  couchdb ||= Chef::CouchDB.new
  if inflate
    doc_ids = cdb_list_latest_ids.map {|i|i["id"]}
    couchdb.bulk_get(doc_ids)
  else
    results = couchdb.get_view("cookbooks", "all_latest_version", :group=>true)["rows"]
    results.inject({}) { |mapped, row| mapped[row["key"]] = row["value"]; mapped}
  end
end

.cdb_list_latest_ids(inflate = false, couchdb = nil) ⇒ Object



783
784
785
786
787
# File 'lib/chef/cookbook_version.rb', line 783

def self.cdb_list_latest_ids(inflate=false, couchdb=nil)
  couchdb ||= Chef::CouchDB.new
  results = couchdb.get_view("cookbooks", "all_latest_version_by_id", :group=>true)["rows"]
  results.map { |name_and_id| name_and_id["value"]}
end

.cdb_load(name, version = 'latest', couchdb = nil) ⇒ Object



795
796
797
798
799
800
801
802
803
# File 'lib/chef/cookbook_version.rb', line 795

def self.cdb_load(name, version='latest', couchdb=nil)
  cdb = couchdb || Chef::CouchDB.new
  if version == "latest" || version == "_latest"
    rs = cdb.get_view("cookbooks", "all_latest_version", :key => name, :descending => true, :group => true, :reduce => true)["rows"].first
    cdb.load("cookbook_version", "#{rs["key"]}-#{rs["value"]}")
  else
    cdb.load("cookbook_version", "#{name}-#{version}")
  end
end

.checksum_cookbook_file(filepath) ⇒ Object

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



193
194
195
196
197
198
# File 'lib/chef/cookbook_version.rb', line 193

def self.checksum_cookbook_file(filepath)
  Chef::ChecksumCache.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_restObject

REST API



707
708
709
# File 'lib/chef/cookbook_version.rb', line 707

def self.chef_server_rest
  Chef::REST.new(Chef::Config[:chef_server_url])
end

.cleanup_file_cacheObject



314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/chef/cookbook_version.rb', line 314

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(%w{cookbooks ** *})).each do |cache_filename|
      unless valid_cache_entries[cache_filename]
        Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer on the server.")
        cache.delete(cache_filename)
      end
    end
  end
end

.clear_obsoleted_cookbooks(cookbook_hash) ⇒ Object

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



242
243
244
245
246
247
248
249
250
251
# File 'lib/chef/cookbook_version.rb', line 242

def self.clear_obsoleted_cookbooks(cookbook_hash)
  # Remove all cookbooks no longer relevant to this node
  cache.find(File.join(%w{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

.create_design_document(couchdb = nil) ⇒ Object



768
769
770
# File 'lib/chef/cookbook_version.rb', line 768

def self.create_design_document(couchdb=nil)
  (couchdb || Chef::CouchDB.new).create_design_document("cookbooks", DESIGN_DOCUMENT)
end

.json_create(o) ⇒ Object



660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
# File 'lib/chef/cookbook_version.rb', line 660

def self.json_create(o)
  cookbook_version = new(o["cookbook_name"])
  if o.has_key?('_rev')
    cookbook_version.couchdb_rev = o["_rev"] if o.has_key?("_rev")
    o.delete("_rev")
  end
  if o.has_key?("_id")
    cookbook_version.couchdb_id = o["_id"] if o.has_key?("_id")
    cookbook_version.index_id = cookbook_version.couchdb_id
    o.delete("_id")
  end
  cookbook_version.manifest = o
  # We want the Chef::Cookbook::Metadata object to always be inflated
  cookbook_version. = Chef::Cookbook::Metadata.from_hash(o["metadata"])
  cookbook_version
end

.latest_cookbooksObject

Get the newest version of all cookbooks



753
754
755
# File 'lib/chef/cookbook_version.rb', line 753

def self.latest_cookbooks
  chef_server_rest.get_rest('cookbooks/_latest')
end

.listObject



731
732
733
# File 'lib/chef/cookbook_version.rb', line 731

def self.list
  chef_server_rest.get_rest('cookbooks')
end

.load(name, version = "_latest") ⇒ Object



726
727
728
729
# File 'lib/chef/cookbook_version.rb', line 726

def self.load(name, version="_latest")
  version = "_latest" if version == "latest"
  chef_server_rest.get_rest("cookbooks/#{name}/#{version}")
end

.reset_cache_validityObject



208
209
210
# File 'lib/chef/cookbook_version.rb', line 208

def self.reset_cache_validity
  @valid_cache_entries = nil
end

.sync_cookbook_file_cache(cookbook) ⇒ Object

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



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/chef/cookbook_version.rb', line 261

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
  eager_segments.delete(:files)
  eager_segments.delete(:templates)

  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) ⇒ Object

Synchronizes all the cookbooks from the chef-server.

Returns

true

Always returns true



226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/chef/cookbook_version.rb', line 226

def self.sync_cookbooks(cookbook_hash)
  Chef::Log.debug("Cookbooks to load: #{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

.valid_cache_entriesObject

Keep track of the filenames that we use in both eager cookbook downloading (during sync_cookbooks) and lazy (during the run itself, through FileVendor). After the run is over, clean up the cache.



204
205
206
# File 'lib/chef/cookbook_version.rb', line 204

def self.valid_cache_entries
  @valid_cache_entries ||= {}
end

Instance Method Details

#cdb_destroyObject



805
806
807
# File 'lib/chef/cookbook_version.rb', line 805

def cdb_destroy
  (couchdb || Chef::CouchDB.new).delete("cookbook_version", full_name, couchdb_rev)
end

#cdb_saveObject



821
822
823
# File 'lib/chef/cookbook_version.rb', line 821

def cdb_save
  @couchdb_rev = couchdb.store("cookbook_version", full_name, self)["rev"]
end

#checksumsObject

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



419
420
421
422
423
424
# File 'lib/chef/cookbook_version.rb', line 419

def checksums
  unless @checksums
    generate_manifest
  end
  @checksums
end

#chef_server_restObject



711
712
713
# File 'lib/chef/cookbook_version.rb', line 711

def chef_server_rest
  self.class.chef_server_rest
end

#destroyObject



721
722
723
724
# File 'lib/chef/cookbook_version.rb', line 721

def destroy
  chef_server_rest.delete_rest("cookbooks/#{name}/#{version}")
  self
end

#full_nameObject



426
427
428
# File 'lib/chef/cookbook_version.rb', line 426

def full_name
  "#{name}-#{version}"
end

#fully_qualified_recipe_namesObject

Return recipe names in the form of cookbook_name::recipe_name



441
442
443
444
445
446
447
# File 'lib/chef/cookbook_version.rb', line 441

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) ⇒ Object



677
678
679
680
681
682
683
684
685
686
687
688
# File 'lib/chef/cookbook_version.rb', line 677

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) ⇒ Object

called from DSL



460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/chef/cookbook_version.rb', line 460

def load_recipe(recipe_name, run_context)
  unless recipe_filenames_by_name.has_key?(recipe_name)
    raise ArgumentError, "Cannot find a recipe matching #{recipe_name} in 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 #{recipe_name} for cookbook #{name}"
  end
  
  recipe.from_file(recipe_filename)
  recipe
end

#manifestObject

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, ... ],
  ...
}


390
391
392
393
394
395
# File 'lib/chef/cookbook_version.rb', line 390

def manifest
  unless @manifest
    generate_manifest
  end
  @manifest
end

#manifest=(new_manifest) ⇒ Object



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/chef/cookbook_version.rb', line 397

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

#metadata_json_fileObject



690
691
692
# File 'lib/chef/cookbook_version.rb', line 690

def 
  File.join(root_dir, "metadata.json")
end

#metadata_rb_fileObject



694
695
696
# File 'lib/chef/cookbook_version.rb', line 694

def 
  File.join(root_dir, "metadata.rb")
end

#preferred_filename_on_disk_location(node, segment, filename, current_filepath = nil) ⇒ Object



532
533
534
535
536
537
538
539
# File 'lib/chef/cookbook_version.rb', line 532

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) ⇒ Object

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"



516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
# File 'lib/chef/cookbook_version.rb', line 516

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
    raise Chef::Exceptions::FileNotFound, "cookbook #{name} does not contain file #{segment}/#{filename}"
  end
end

#preferred_manifest_records_for_directory(node, segment, dirname) ⇒ Object

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.



582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
# File 'lib/chef/cookbook_version.rb', line 582

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} has no directory #{segment}/#{dirname}" unless best_pref

  records_by_pref[best_pref]
end

#purgeObject

Runs on Chef Server (API); deletes the cookbook from couchdb and also destroys associated checksum documents



811
812
813
814
815
816
817
818
819
# File 'lib/chef/cookbook_version.rb', line 811

def purge
  checksums.keys.each do |checksum|
    begin
      Chef::Checksum.cdb_load(checksum, couchdb).purge
    rescue Chef::Exceptions::CouchDBNotFound
    end
  end
  cdb_destroy
end

#relative_filenames_in_preferred_directory(node, segment, dirname) ⇒ Object



541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
# File 'lib/chef/cookbook_version.rb', line 541

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}/#{dirname}" unless best_pref

  filenames_by_pref[best_pref]

end

#reload_metadata!Object



698
699
700
701
702
# File 'lib/chef/cookbook_version.rb', line 698

def reload_metadata!
  if File.exists?()
    .from_json(IO.read())
  end
end

#saveObject Also known as: create



715
716
717
718
# File 'lib/chef/cookbook_version.rb', line 715

def save
  chef_server_rest.put_rest("cookbooks/#{name}/#{version}", self)
  self
end

#segment_filenames(segment) ⇒ Object



477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/chef/cookbook_version.rb', line 477

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_hashObject



647
648
649
650
651
652
# File 'lib/chef/cookbook_version.rb', line 647

def to_hash
  result = manifest.dup
  result['chef_type'] = 'cookbook_version'
  result["_rev"] = couchdb_rev if couchdb_rev
  result.to_hash
end

#to_json(*a) ⇒ Object



654
655
656
657
658
# File 'lib/chef/cookbook_version.rb', line 654

def to_json(*a)
  result = self.to_hash
  result['json_class'] = self.class.name
  result.to_json(*a)
end

#versionObject



359
360
361
# File 'lib/chef/cookbook_version.rb', line 359

def version
  .version
end

#version=(new_version) ⇒ Object



363
364
365
366
# File 'lib/chef/cookbook_version.rb', line 363

def version=(new_version)
  manifest["version"] = new_version
  .version(new_version)
end