Class: Chef::CookbookVersion

Inherits:
Object
  • Object
show all
Includes:
Comparable
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.

Constant Summary collapse

COOKBOOK_SEGMENTS =
[ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, *root_paths, chef_server_rest: nil) ⇒ CookbookVersion

Creates a new Chef::CookbookVersion object.

Returns

object<Chef::CookbookVersion>

Duh. :)



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/chef/cookbook_version.rb', line 116

def initialize(name, *root_paths, chef_server_rest: nil)
  @name = name
  @root_paths = root_paths
  @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_filenames = Array.new

  @all_files = Array.new

  # deprecated
  @status = :ready
  @file_vendor = nil
  @metadata = Chef::Cookbook::Metadata.new
  @chef_server_rest = chef_server_rest
end

Instance Attribute Details

#all_filesObject

Returns the value of attribute all_files.



43
44
45
# File 'lib/chef/cookbook_version.rb', line 43

def all_files
  @all_files
end

#attribute_filenamesObject Also known as: attribute_files

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



73
74
75
# File 'lib/chef/cookbook_version.rb', line 73

def attribute_filenames
  @attribute_filenames
end

#attribute_filenames_by_short_filenameObject (readonly)

Returns the value of attribute attribute_filenames_by_short_filename.



80
81
82
# File 'lib/chef/cookbook_version.rb', line 80

def attribute_filenames_by_short_filename
  @attribute_filenames_by_short_filename
end

#chef_server_restObject

Returns the value of attribute chef_server_rest.



82
83
84
# File 'lib/chef/cookbook_version.rb', line 82

def chef_server_rest
  @chef_server_rest
end

#definition_filenamesObject

Returns the value of attribute definition_filenames.



46
47
48
# File 'lib/chef/cookbook_version.rb', line 46

def definition_filenames
  @definition_filenames
end

#file_filenamesObject

Returns the value of attribute file_filenames.



48
49
50
# File 'lib/chef/cookbook_version.rb', line 48

def file_filenames
  @file_filenames
end

#identifierObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The ‘identifier` field is used for cookbook_artifacts, which are organized on the chef server according to their content. If the policy_mode option to CookbookManifest is set to true it will include this field in the manifest Hash and in the upload URL.

This field may be removed or have different behavior in the future, don’t use it in 3rd party code.



92
93
94
# File 'lib/chef/cookbook_version.rb', line 92

def identifier
  @identifier
end

#library_filenamesObject

Returns the value of attribute library_filenames.



49
50
51
# File 'lib/chef/cookbook_version.rb', line 49

def library_filenames
  @library_filenames
end

#metadataObject

A Chef::Cookbook::Metadata object. It has a setter that fixes up the metadata to add descriptions of the recipes contained in this CookbookVersion.



69
70
71
# File 'lib/chef/cookbook_version.rb', line 69

def 
  @metadata
end

#metadata_filenamesObject

Returns the value of attribute metadata_filenames.



54
55
56
# File 'lib/chef/cookbook_version.rb', line 54

def 
  @metadata_filenames
end

#nameObject

Returns the value of attribute name.



53
54
55
# File 'lib/chef/cookbook_version.rb', line 53

def name
  @name
end

#provider_filenamesObject

Returns the value of attribute provider_filenames.



51
52
53
# File 'lib/chef/cookbook_version.rb', line 51

def provider_filenames
  @provider_filenames
end

#recipe_filenamesObject Also known as: recipe_files

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



77
78
79
# File 'lib/chef/cookbook_version.rb', line 77

def recipe_filenames
  @recipe_filenames
end

#recipe_filenames_by_nameObject (readonly)

Returns the value of attribute recipe_filenames_by_name.



79
80
81
# File 'lib/chef/cookbook_version.rb', line 79

def recipe_filenames_by_name
  @recipe_filenames_by_name
end

#resource_filenamesObject

Returns the value of attribute resource_filenames.



50
51
52
# File 'lib/chef/cookbook_version.rb', line 50

def resource_filenames
  @resource_filenames
end

#root_filenamesObject

Returns the value of attribute root_filenames.



52
53
54
# File 'lib/chef/cookbook_version.rb', line 52

def root_filenames
  @root_filenames
end

#root_pathsObject

Returns the value of attribute root_paths.



45
46
47
# File 'lib/chef/cookbook_version.rb', line 45

def root_paths
  @root_paths
end

#template_filenamesObject

Returns the value of attribute template_filenames.



47
48
49
# File 'lib/chef/cookbook_version.rb', line 47

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.



589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/chef/cookbook_version.rb', line 589

def self.available_versions(cookbook_name)
  chef_server_rest.get("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

.cacheObject



108
109
110
# File 'lib/chef/cookbook_version.rb', line 108

def self.cache
  Chef::FileCache
end

.checksum_cookbook_file(filepath) ⇒ Object

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



101
102
103
104
105
106
# File 'lib/chef/cookbook_version.rb', line 101

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_restObject



555
556
557
# File 'lib/chef/cookbook_version.rb', line 555

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

.from_cb_artifact_data(o) ⇒ Object



492
493
494
# File 'lib/chef/cookbook_version.rb', line 492

def self.from_cb_artifact_data(o)
  from_hash(o)
end

.from_hash(o) ⇒ Object



472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/chef/cookbook_version.rb', line 472

def self.from_hash(o)
  cookbook_version = new(o["cookbook_name"] || o["name"])

  # We want the Chef::Cookbook::Metadata object to always be inflated
  cookbook_version. = Chef::Cookbook::Metadata.from_hash(o["metadata"])
  cookbook_version.manifest = o
  cookbook_version.identifier = o["identifier"] if o.key?("identifier")

  # 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(Chef::JSONCompat.to_json(cookbook_version.))

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

.json_create(o) ⇒ Object



487
488
489
490
# File 'lib/chef/cookbook_version.rb', line 487

def self.json_create(o)
  Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::CookbookVersion#from_hash")
  from_hash(o)
end

.listObject Also known as: latest_cookbooks

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



570
571
572
# File 'lib/chef/cookbook_version.rb', line 570

def self.list
  chef_server_rest.get("cookbooks")
end

.list_all_versionsObject



579
580
581
# File 'lib/chef/cookbook_version.rb', line 579

def self.list_all_versions
  chef_server_rest.get("cookbooks?num_versions=all")
end

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



564
565
566
567
# File 'lib/chef/cookbook_version.rb', line 564

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

Instance Method Details

#<=>(o) ⇒ Object



602
603
604
605
606
607
608
# File 'lib/chef/cookbook_version.rb', line 602

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

#checksumsObject

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



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

def checksums
  cookbook_manifest.checksums
end

#destroyObject



559
560
561
562
# File 'lib/chef/cookbook_version.rb', line 559

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

#force_save_urlObject



546
547
548
549
# File 'lib/chef/cookbook_version.rb', line 546

def force_save_url
  # TODO: this should become deprecated when the API for CookbookManifest becomes stable
  cookbook_manifest.force_save_url
end

#freeze_versionObject



153
154
155
# File 'lib/chef/cookbook_version.rb', line 153

def freeze_version
  @frozen = true
end

#frozen_version?Boolean

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

Returns:

  • (Boolean)


149
150
151
# File 'lib/chef/cookbook_version.rb', line 149

def frozen_version?
  @frozen
end

#full_nameObject



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

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

#fully_qualified_recipe_namesObject

Return recipe names in the form of cookbook_name::recipe_name



201
202
203
204
205
206
207
# File 'lib/chef/cookbook_version.rb', line 201

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_urlsObject

Deprecated.

This method was used by the Ruby Chef Server and is no longer needed. There is no replacement.



498
499
500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/chef/cookbook_version.rb', line 498

def generate_manifest_with_urls
  Chef.log_deprecation("Deprecated method #generate_manifest_with_urls.")

  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"] = yield(url_options)
      end
    end
  end
  rendered_manifest
end

#has_cookbook_file_for_node?(node, cookbook_filename) ⇒ Boolean

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

Returns:

  • (Boolean)


283
284
285
# File 'lib/chef/cookbook_version.rb', line 283

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

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

Returns:

  • (Boolean)


277
278
279
# File 'lib/chef/cookbook_version.rb', line 277

def has_template_for_node?(node, template_filename)
  !!find_preferred_manifest_record(node, :templates, template_filename)
end

#load_recipe(recipe_name, run_context) ⇒ Object

called from DSL



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/chef/cookbook_version.rb', line 220

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

#manifestObject



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

def manifest
  cookbook_manifest.manifest
end

#manifest=(new_manifest) ⇒ Object



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

def manifest=(new_manifest)
  cookbook_manifest.update_from(new_manifest)
end

#manifest_records_by_pathObject



192
193
194
# File 'lib/chef/cookbook_version.rb', line 192

def manifest_records_by_path
  cookbook_manifest.manifest_records_by_path
end

#metadata_json_fileObject



523
524
525
# File 'lib/chef/cookbook_version.rb', line 523

def 
  File.join(root_paths[0], "metadata.json")
end

#metadata_rb_fileObject



527
528
529
# File 'lib/chef/cookbook_version.rb', line 527

def 
  File.join(root_paths[0], "metadata.rb")
end

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



338
339
340
341
342
343
344
345
# File 'lib/chef/cookbook_version.rb', line 338

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"



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/chef/cookbook_version.rb', line 299

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 = if filename.is_a?(Array)
                          filename.map { |name| "  #{File.join(segment.to_s, name)}" }
                        else
                          [
                            "  #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}",
                            "  #{segment}/#{node[:platform]}/#{filename}",
                            "  #{segment}/default/#{filename}",
                            "  #{segment}/#{filename}",
                          ]
                        end
      error_message << error_locations.join("\n")
      existing_files = segment_filenames(segment)
      # Strip the root_dir prefix off all files for readability
      pretty_existing_files = existing_files.map { |path|
        if root_dir
          path[root_dir.length + 1..-1]
        else
          path
        end
      }
      # Show the files that the cookbook does have. If the user made a typo,
      # hopefully they'll see it here.
      unless pretty_existing_files.empty?
        error_message << "\n\nThis cookbook _does_ contain: ['#{pretty_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) ⇒ 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.



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/chef/cookbook_version.rb', line 387

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

#relative_filenames_in_preferred_directory(node, segment, dirname) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/chef/cookbook_version.rb', line 347

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!Object



531
532
533
534
535
# File 'lib/chef/cookbook_version.rb', line 531

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

#replace_segment_filenames(segment, filenames) ⇒ Object



264
265
266
267
268
269
270
271
272
273
# File 'lib/chef/cookbook_version.rb', line 264

def replace_segment_filenames(segment, filenames)
  case segment.to_sym
  when :recipes
    self.recipe_filenames = filenames
  when :attributes
    self.attribute_filenames = filenames
  else
    segment_filenames(segment).replace(filenames)
  end
end

#root_dirObject

The first root path is the primary cookbook dir, from which metadata is loaded



95
96
97
# File 'lib/chef/cookbook_version.rb', line 95

def root_dir
  root_paths[0]
end

#save_urlObject

REST API



541
542
543
544
# File 'lib/chef/cookbook_version.rb', line 541

def save_url
  # TODO: this should become deprecated when the API for CookbookManifest becomes stable
  cookbook_manifest.save_url
end

#segment_filenames(segment) ⇒ Object



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/chef/cookbook_version.rb', line 237

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

#statusObject



61
62
63
64
# File 'lib/chef/cookbook_version.rb', line 61

def status
  Chef.log_deprecation("Deprecated method `status' called. This method will be removed.")
  @status
end

#status=(new_status) ⇒ Object



56
57
58
59
# File 'lib/chef/cookbook_version.rb', line 56

def status=(new_status)
  Chef.log_deprecation("Deprecated method `status' called. This method will be removed.")
  @status = new_status
end

#to_hashObject



513
514
515
516
# File 'lib/chef/cookbook_version.rb', line 513

def to_hash
  # TODO: this should become deprecated when the API for CookbookManifest becomes stable
  cookbook_manifest.to_hash
end

#to_json(*a) ⇒ Object



518
519
520
521
# File 'lib/chef/cookbook_version.rb', line 518

def to_json(*a)
  # TODO: this should become deprecated when the API for CookbookManifest becomes stable
  cookbook_manifest.to_json
end

#versionObject



142
143
144
# File 'lib/chef/cookbook_version.rb', line 142

def version
  .version
end

#version=(new_version) ⇒ Object



157
158
159
160
# File 'lib/chef/cookbook_version.rb', line 157

def version=(new_version)
  cookbook_manifest.reset!
  .version(new_version)
end