chef 0.9.18 → 0.10.0.beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) hide show
  1. data/README.rdoc +0 -3
  2. data/distro/arch/etc/rc.d/chef-server +0 -4
  3. data/distro/arch/etc/rc.d/chef-server-webui +0 -4
  4. data/distro/arch/etc/rc.d/chef-solr +0 -4
  5. data/distro/arch/etc/rc.d/chef-solr-indexer +0 -4
  6. data/lib/chef.rb +3 -3
  7. data/lib/chef/api_client.rb +1 -1
  8. data/lib/chef/application.rb +11 -1
  9. data/lib/chef/application/client.rb +18 -22
  10. data/lib/chef/application/knife.rb +28 -29
  11. data/lib/chef/application/solo.rb +14 -12
  12. data/lib/chef/client.rb +112 -54
  13. data/lib/chef/config.rb +4 -0
  14. data/lib/chef/cookbook/chefignore.rb +66 -0
  15. data/lib/chef/cookbook/cookbook_collection.rb +6 -5
  16. data/lib/chef/cookbook/cookbook_version_loader.rb +151 -0
  17. data/lib/chef/cookbook/file_system_file_vendor.rb +10 -8
  18. data/lib/chef/cookbook/metadata.rb +200 -108
  19. data/lib/chef/cookbook_loader.rb +39 -163
  20. data/lib/chef/cookbook_uploader.rb +100 -78
  21. data/lib/chef/cookbook_version.rb +92 -47
  22. data/lib/chef/cookbook_version_selector.rb +163 -0
  23. data/lib/chef/couchdb.rb +9 -1
  24. data/lib/chef/data_bag.rb +1 -1
  25. data/lib/chef/data_bag_item.rb +1 -1
  26. data/lib/chef/encrypted_data_bag_item.rb +126 -0
  27. data/lib/chef/environment.rb +386 -0
  28. data/lib/chef/exceptions.rb +82 -1
  29. data/lib/chef/index_queue/amqp_client.rb +15 -12
  30. data/lib/chef/index_queue/indexable.rb +38 -4
  31. data/lib/chef/json_compat.rb +3 -3
  32. data/lib/chef/knife.rb +97 -202
  33. data/lib/chef/knife/bootstrap.rb +27 -61
  34. data/lib/chef/knife/bootstrap/archlinux-gems.erb +4 -2
  35. data/lib/chef/knife/bootstrap/centos5-gems.erb +6 -15
  36. data/lib/chef/knife/bootstrap/fedora13-gems.erb +3 -4
  37. data/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb +2 -2
  38. data/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb +6 -5
  39. data/lib/chef/knife/client_bulk_delete.rb +6 -3
  40. data/lib/chef/knife/client_create.rb +13 -10
  41. data/lib/chef/knife/client_delete.rb +10 -7
  42. data/lib/chef/knife/client_edit.rb +9 -6
  43. data/lib/chef/knife/client_list.rb +8 -5
  44. data/lib/chef/knife/client_reregister.rb +9 -6
  45. data/lib/chef/knife/client_show.rb +9 -6
  46. data/lib/chef/knife/configure.rb +15 -19
  47. data/lib/chef/knife/configure_client.rb +4 -4
  48. data/lib/chef/knife/cookbook_bulk_delete.rb +11 -8
  49. data/lib/chef/knife/cookbook_create.rb +120 -55
  50. data/lib/chef/knife/cookbook_delete.rb +18 -12
  51. data/lib/chef/knife/cookbook_download.rb +10 -6
  52. data/lib/chef/knife/cookbook_list.rb +15 -6
  53. data/lib/chef/knife/cookbook_metadata.rb +41 -21
  54. data/lib/chef/knife/cookbook_metadata_from_file.rb +4 -0
  55. data/lib/chef/knife/cookbook_show.rb +16 -5
  56. data/lib/chef/knife/cookbook_site_download.rb +2 -2
  57. data/lib/chef/knife/cookbook_site_share.rb +18 -13
  58. data/lib/chef/knife/cookbook_site_unshare.rb +7 -4
  59. data/lib/chef/knife/cookbook_site_vendor.rb +21 -18
  60. data/lib/chef/knife/cookbook_test.rb +14 -14
  61. data/lib/chef/knife/cookbook_upload.rb +91 -40
  62. data/lib/chef/knife/data_bag_create.rb +41 -6
  63. data/lib/chef/knife/data_bag_delete.rb +5 -3
  64. data/lib/chef/knife/data_bag_edit.rb +55 -11
  65. data/lib/chef/knife/data_bag_from_file.rb +47 -7
  66. data/lib/chef/knife/data_bag_list.rb +4 -1
  67. data/lib/chef/knife/data_bag_show.rb +44 -4
  68. data/lib/chef/knife/environment_create.rb +53 -0
  69. data/lib/chef/knife/environment_delete.rb +45 -0
  70. data/lib/chef/knife/environment_edit.rb +45 -0
  71. data/lib/chef/knife/environment_from_file.rb +39 -0
  72. data/lib/chef/knife/environment_list.rb +42 -0
  73. data/lib/chef/knife/environment_show.rb +46 -0
  74. data/lib/chef/knife/exec.rb +1 -1
  75. data/lib/chef/knife/index_rebuild.rb +8 -9
  76. data/lib/chef/knife/node_bulk_delete.rb +9 -6
  77. data/lib/chef/knife/node_create.rb +9 -6
  78. data/lib/chef/knife/node_delete.rb +10 -7
  79. data/lib/chef/knife/node_edit.rb +129 -10
  80. data/lib/chef/knife/node_from_file.rb +10 -7
  81. data/lib/chef/knife/node_list.rb +11 -6
  82. data/lib/chef/knife/node_run_list_add.rb +10 -7
  83. data/lib/chef/knife/node_run_list_remove.rb +9 -6
  84. data/lib/chef/knife/node_show.rb +15 -7
  85. data/lib/chef/knife/recipe_list.rb +4 -3
  86. data/lib/chef/knife/role_bulk_delete.rb +9 -6
  87. data/lib/chef/knife/role_create.rb +9 -6
  88. data/lib/chef/knife/role_delete.rb +10 -7
  89. data/lib/chef/knife/role_edit.rb +11 -8
  90. data/lib/chef/knife/role_from_file.rb +10 -7
  91. data/lib/chef/knife/role_list.rb +8 -5
  92. data/lib/chef/knife/role_show.rb +11 -8
  93. data/lib/chef/knife/search.rb +33 -10
  94. data/lib/chef/knife/ssh.rb +33 -61
  95. data/lib/chef/knife/status.rb +7 -4
  96. data/lib/chef/knife/subcommand_loader.rb +101 -0
  97. data/lib/chef/knife/tag_create.rb +31 -0
  98. data/lib/chef/knife/tag_delete.rb +31 -0
  99. data/lib/chef/knife/tag_list.rb +29 -0
  100. data/lib/chef/knife/ui.rb +229 -0
  101. data/lib/chef/knife/windows_bootstrap.rb +8 -5
  102. data/lib/chef/log.rb +5 -59
  103. data/lib/chef/mash.rb +211 -0
  104. data/lib/chef/mixins.rb +1 -2
  105. data/lib/chef/nil_argument.rb +3 -0
  106. data/lib/chef/node.rb +96 -34
  107. data/lib/chef/platform.rb +27 -0
  108. data/lib/chef/provider/cookbook_file.rb +21 -20
  109. data/lib/chef/provider/deploy/revision.rb +3 -0
  110. data/lib/chef/provider/file.rb +20 -11
  111. data/lib/chef/provider/git.rb +26 -26
  112. data/lib/chef/provider/group/aix.rb +70 -0
  113. data/lib/chef/provider/group/groupadd.rb +7 -4
  114. data/lib/chef/provider/group/usermod.rb +1 -1
  115. data/lib/chef/provider/package.rb +28 -28
  116. data/lib/chef/provider/package/dpkg.rb +1 -1
  117. data/lib/chef/provider/package/portage.rb +50 -39
  118. data/lib/chef/provider/package/rubygems.rb +1 -1
  119. data/lib/chef/provider/package/zypper.rb +3 -20
  120. data/lib/chef/provider/remote_directory.rb +0 -2
  121. data/lib/chef/provider/remote_file.rb +2 -3
  122. data/lib/chef/provider/service/arch.rb +28 -35
  123. data/lib/chef/provider/service/simple.rb +1 -1
  124. data/lib/chef/provider/subversion.rb +22 -22
  125. data/lib/chef/providers.rb +1 -0
  126. data/lib/chef/recipe.rb +10 -12
  127. data/lib/chef/resource.rb +49 -42
  128. data/lib/chef/resource/gem_package.rb +7 -3
  129. data/lib/chef/resource/git.rb +5 -5
  130. data/lib/chef/resource/package.rb +7 -7
  131. data/lib/chef/resource/scm.rb +2 -1
  132. data/lib/chef/resource/solaris_package.rb +0 -1
  133. data/lib/chef/resource/yum_package.rb +0 -1
  134. data/lib/chef/rest.rb +7 -16
  135. data/lib/chef/rest/rest_request.rb +0 -16
  136. data/lib/chef/role.rb +67 -13
  137. data/lib/chef/run_context.rb +37 -21
  138. data/lib/chef/run_list.rb +30 -15
  139. data/lib/chef/run_list/run_list_expansion.rb +41 -20
  140. data/lib/chef/run_list/run_list_item.rb +20 -6
  141. data/lib/chef/run_list/versioned_recipe_list.rb +68 -0
  142. data/lib/chef/runner.rb +7 -15
  143. data/lib/chef/search/query.rb +12 -7
  144. data/lib/chef/shef.rb +6 -7
  145. data/lib/chef/shef/shef_session.rb +40 -35
  146. data/lib/chef/shell_out.rb +22 -201
  147. data/lib/chef/shell_out/unix.rb +224 -0
  148. data/lib/chef/shell_out/windows.rb +95 -0
  149. data/lib/chef/solr_query.rb +187 -0
  150. data/lib/chef/solr_query/lucene.treetop +145 -0
  151. data/lib/chef/solr_query/lucene_nodes.rb +285 -0
  152. data/lib/chef/solr_query/query_transform.rb +65 -0
  153. data/lib/chef/solr_query/solr_http_request.rb +118 -0
  154. data/lib/chef/version.rb +4 -2
  155. data/lib/chef/version_class.rb +70 -0
  156. data/lib/chef/version_constraint.rb +116 -0
  157. metadata +68 -37
  158. data/lib/chef/cookbook/metadata/version.rb +0 -87
  159. data/lib/chef/knife/bluebox_images_list.rb +0 -54
  160. data/lib/chef/knife/bluebox_server_create.rb +0 -157
  161. data/lib/chef/knife/bluebox_server_delete.rb +0 -63
  162. data/lib/chef/knife/bluebox_server_list.rb +0 -59
  163. data/lib/chef/knife/ec2_instance_data.rb +0 -46
  164. data/lib/chef/knife/ec2_server_create.rb +0 -218
  165. data/lib/chef/knife/ec2_server_delete.rb +0 -87
  166. data/lib/chef/knife/ec2_server_list.rb +0 -89
  167. data/lib/chef/knife/rackspace_server_create.rb +0 -184
  168. data/lib/chef/knife/rackspace_server_delete.rb +0 -57
  169. data/lib/chef/knife/rackspace_server_list.rb +0 -59
  170. data/lib/chef/knife/slicehost_images_list.rb +0 -53
  171. data/lib/chef/knife/slicehost_server_create.rb +0 -103
  172. data/lib/chef/knife/slicehost_server_delete.rb +0 -61
  173. data/lib/chef/knife/slicehost_server_list.rb +0 -64
  174. data/lib/chef/knife/terremark_server_create.rb +0 -152
  175. data/lib/chef/knife/terremark_server_delete.rb +0 -87
  176. data/lib/chef/knife/terremark_server_list.rb +0 -77
  177. data/lib/chef/mixin/find_preferred_file.rb +0 -92
@@ -1,17 +1,17 @@
1
- #
2
1
  # Author:: Adam Jacob (<adam@opscode.com>)
3
2
  # Author:: Nuo Yan (<nuo@opscode.com>)
4
3
  # Author:: Christopher Walters (<cw@opscode.com>)
5
4
  # Author:: Tim Hinderliter (<tim@opscode.com>)
6
- # Copyright:: Copyright (c) 2008-2010 Opscode, Inc.
5
+ # Author:: Seth Falcon (<seth@opscode.com>)
6
+ # Copyright:: Copyright 2008-2010 Opscode, Inc.
7
7
  # License:: Apache License, Version 2.0
8
8
  #
9
9
  # Licensed under the Apache License, Version 2.0 (the "License");
10
10
  # you may not use this file except in compliance with the License.
11
11
  # You may obtain a copy of the License at
12
- #
12
+ #
13
13
  # http://www.apache.org/licenses/LICENSE-2.0
14
- #
14
+ #
15
15
  # Unless required by applicable law or agreed to in writing, software
16
16
  # distributed under the License is distributed on an "AS IS" BASIS,
17
17
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -25,6 +25,8 @@ require 'chef/resource_definition_list'
25
25
  require 'chef/recipe'
26
26
  require 'chef/cookbook/file_vendor'
27
27
  require 'chef/checksum'
28
+ require 'chef/cookbook/metadata'
29
+ require 'chef/version_class'
28
30
 
29
31
  class Chef
30
32
  # == Chef::CookbookVersion
@@ -37,16 +39,17 @@ class Chef
37
39
  # recipe_filenames.insert) should dirty the manifest so it gets regenerated.
38
40
  class CookbookVersion
39
41
  include Chef::IndexQueue::Indexable
42
+ include Comparable
40
43
 
41
44
  COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ]
42
-
45
+
43
46
  DESIGN_DOCUMENT = {
44
47
  "version" => 7,
45
48
  "language" => "javascript",
46
49
  "views" => {
47
50
  "all" => {
48
51
  "map" => <<-EOJS
49
- function(doc) {
52
+ function(doc) {
50
53
  if (doc.chef_type == "cookbook_version") {
51
54
  emit(doc.name, doc);
52
55
  }
@@ -55,7 +58,7 @@ class Chef
55
58
  },
56
59
  "all_id" => {
57
60
  "map" => <<-EOJS
58
- function(doc) {
61
+ function(doc) {
59
62
  if (doc.chef_type == "cookbook_version") {
60
63
  emit(doc.name, doc.name);
61
64
  }
@@ -64,7 +67,7 @@ class Chef
64
67
  },
65
68
  "all_with_version" => {
66
69
  "map" => <<-EOJS
67
- function(doc) {
70
+ function(doc) {
68
71
  if (doc.chef_type == "cookbook_version") {
69
72
  emit(doc.cookbook_name, doc.version);
70
73
  }
@@ -73,7 +76,7 @@ class Chef
73
76
  },
74
77
  "all_latest_version" => {
75
78
  "map" => %q@
76
- function(doc) {
79
+ function(doc) {
77
80
  if (doc.chef_type == "cookbook_version") {
78
81
  emit(doc.cookbook_name, doc.version);
79
82
  }
@@ -85,12 +88,12 @@ class Chef
85
88
 
86
89
  for (var idx in values) {
87
90
  var value = values[idx];
88
-
91
+
89
92
  if (idx == 0) {
90
93
  result = value;
91
94
  continue;
92
95
  }
93
-
96
+
94
97
  var valueParts = value.split('.').map(function(v) { return parseInt(v); });
95
98
  var resultParts = result.split('.').map(function(v) { return parseInt(v); });
96
99
 
@@ -187,7 +190,7 @@ class Chef
187
190
 
188
191
  attr_reader :recipe_filenames_by_name
189
192
  attr_reader :attribute_filenames_by_short_filename
190
-
193
+
191
194
  # This is the one and only method that knows how cookbook files'
192
195
  # checksums are generated.
193
196
  def self.checksum_cookbook_file(filepath)
@@ -196,7 +199,7 @@ class Chef
196
199
  Chef::Log.debug("File #{filepath} does not exist, so there is no checksum to generate")
197
200
  nil
198
201
  end
199
-
202
+
200
203
  # Keep track of the filenames that we use in both eager cookbook
201
204
  # downloading (during sync_cookbooks) and lazy (during the run
202
205
  # itself, through FileVendor). After the run is over, clean up the
@@ -224,7 +227,8 @@ class Chef
224
227
  # === Returns
225
228
  # true:: Always returns true
226
229
  def self.sync_cookbooks(cookbook_hash)
227
- Chef::Log.debug("Cookbooks to load: #{cookbook_hash.inspect}")
230
+ Chef::Log.info("Loading cookbooks [#{cookbook_hash.keys.sort.join(', ')}]")
231
+ Chef::Log.debug("Cookbooks detail: #{cookbook_hash.inspect}")
228
232
 
229
233
  clear_obsoleted_cookbooks(cookbook_hash)
230
234
 
@@ -317,7 +321,7 @@ class Chef
317
321
  # manifest.
318
322
  cache.find(File.join(%w{cookbooks ** *})).each do |cache_filename|
319
323
  unless valid_cache_entries[cache_filename]
320
- Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer on the server.")
324
+ Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer needed by chef-client.")
321
325
  cache.delete(cache_filename)
322
326
  end
323
327
  end
@@ -329,12 +333,13 @@ class Chef
329
333
  cleanup_file_cache
330
334
  end
331
335
 
332
- # Creates a new Chef::CookbookVersion object.
336
+ # Creates a new Chef::CookbookVersion object.
333
337
  #
334
338
  # === Returns
335
339
  # object<Chef::CookbookVersion>:: Duh. :)
336
340
  def initialize(name, couchdb=nil)
337
341
  @name = name
342
+ @frozen = false
338
343
  @attribute_filenames = Array.new
339
344
  @definition_filenames = Array.new
340
345
  @template_filenames = Array.new
@@ -359,7 +364,18 @@ class Chef
359
364
  def version
360
365
  metadata.version
361
366
  end
362
-
367
+
368
+ # Indicates if this version is frozen or not. Freezing a coobkook version
369
+ # indicates that a new cookbook with the same name and version number
370
+ # shoule
371
+ def frozen_version?
372
+ @frozen
373
+ end
374
+
375
+ def freeze_version
376
+ @frozen = true
377
+ end
378
+
363
379
  def version=(new_version)
364
380
  manifest["version"] = new_version
365
381
  metadata.version(new_version)
@@ -375,7 +391,7 @@ class Chef
375
391
  # :version = "1.0",
376
392
  # :name = "Apache 2"
377
393
  # :metadata = ???TODO: timh/cw: 5-24-2010: describe this format,
378
- #
394
+ #
379
395
  # :files => [
380
396
  # {
381
397
  # :name => "afile.rb",
@@ -393,7 +409,7 @@ class Chef
393
409
  end
394
410
  @manifest
395
411
  end
396
-
412
+
397
413
  def manifest=(new_manifest)
398
414
  @manifest = Mash.new new_manifest
399
415
  @checksums = extract_checksums_from_manifest(@manifest)
@@ -402,7 +418,7 @@ class Chef
402
418
  COOKBOOK_SEGMENTS.each do |segment|
403
419
  next unless @manifest.has_key?(segment)
404
420
  filenames = @manifest[segment].map{|manifest_record| manifest_record['name']}
405
-
421
+
406
422
  if segment == :recipes
407
423
  self.recipe_filenames = filenames
408
424
  elsif segment == :attributes
@@ -413,7 +429,7 @@ class Chef
413
429
  end
414
430
  end
415
431
  end
416
-
432
+
417
433
  # Returns a hash of checksums to either nil or the on disk path (which is
418
434
  # done by generate_manifest).
419
435
  def checksums
@@ -426,17 +442,17 @@ class Chef
426
442
  def full_name
427
443
  "#{name}-#{version}"
428
444
  end
429
-
445
+
430
446
  def attribute_filenames=(*filenames)
431
447
  @attribute_filenames = filenames.flatten
432
448
  @attribute_filenames_by_short_filename = filenames_by_name(attribute_filenames)
433
449
  attribute_filenames
434
450
  end
435
-
451
+
436
452
  ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
437
453
  alias :attribute_files :attribute_filenames
438
454
  alias :attribute_files= :attribute_filenames=
439
-
455
+
440
456
  # Return recipe names in the form of cookbook_name::recipe_name
441
457
  def fully_qualified_recipe_names
442
458
  results = Array.new
@@ -445,17 +461,17 @@ class Chef
445
461
  end
446
462
  results
447
463
  end
448
-
464
+
449
465
  def recipe_filenames=(*filenames)
450
466
  @recipe_filenames = filenames.flatten
451
467
  @recipe_filenames_by_name = filenames_by_name(recipe_filenames)
452
468
  recipe_filenames
453
469
  end
454
-
470
+
455
471
  ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
456
472
  alias :recipe_files :recipe_filenames
457
473
  alias :recipe_files= :recipe_filenames=
458
-
474
+
459
475
  # called from DSL
460
476
  def load_recipe(recipe_name, run_context)
461
477
  unless recipe_filenames_by_name.has_key?(recipe_name)
@@ -469,7 +485,7 @@ class Chef
469
485
  unless recipe_filename
470
486
  raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}"
471
487
  end
472
-
488
+
473
489
  recipe.from_file(recipe_filename)
474
490
  recipe
475
491
  end
@@ -519,7 +535,7 @@ class Chef
519
535
  # ensure that we generate the manifest, which will also generate
520
536
  # @manifest_records_by_path
521
537
  manifest
522
-
538
+
523
539
  # in order of prefernce, look for the filename in the manifest
524
540
  found_pref = preferences.find {|preferred_filename| @manifest_records_by_path[preferred_filename] }
525
541
  if found_pref
@@ -528,7 +544,7 @@ class Chef
528
544
  raise Chef::Exceptions::FileNotFound, "cookbook #{name} does not contain file #{segment}/#{filename}"
529
545
  end
530
546
  end
531
-
547
+
532
548
  def preferred_filename_on_disk_location(node, segment, filename, current_filepath=nil)
533
549
  manifest_record = preferred_manifest_record(node, segment, filename)
534
550
  if current_filepath && (manifest_record['checksum'] == self.class.checksum_cookbook_file(current_filepath))
@@ -594,7 +610,7 @@ class Chef
594
610
  # preferences_for_path returns. It could be
595
611
  # "files/ubuntu-9.10/dirname", for example.
596
612
  specificity_dirname = $1
597
-
613
+
598
614
  # Record the specificity_dirname only if it's in the list of
599
615
  # valid preferences
600
616
  if records_by_pref[specificity_dirname]
@@ -602,9 +618,9 @@ class Chef
602
618
  end
603
619
  end
604
620
  end
605
-
621
+
606
622
  best_pref = preferences.find { |pref| !records_by_pref[pref].empty? }
607
-
623
+
608
624
  raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
609
625
 
610
626
  records_by_pref[best_pref]
@@ -628,7 +644,7 @@ class Chef
628
644
  raise
629
645
  end
630
646
  end
631
-
647
+
632
648
  fqdn = node[:fqdn]
633
649
 
634
650
  # Most specific to least specific places to find the path
@@ -646,6 +662,7 @@ class Chef
646
662
 
647
663
  def to_hash
648
664
  result = manifest.dup
665
+ result['frozen?'] = frozen_version?
649
666
  result['chef_type'] = 'cookbook_version'
650
667
  result["_rev"] = couchdb_rev if couchdb_rev
651
668
  result.to_hash
@@ -668,12 +685,17 @@ class Chef
668
685
  cookbook_version.index_id = cookbook_version.couchdb_id
669
686
  o.delete("_id")
670
687
  end
671
- cookbook_version.manifest = o
672
688
  # We want the Chef::Cookbook::Metadata object to always be inflated
673
689
  cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"])
690
+ cookbook_version.manifest = o
691
+
692
+ # We don't need the following step when we decide to stop supporting deprecated operators in the metadata (e.g. <<, >>)
693
+ cookbook_version.manifest["metadata"] = JSON.parse(cookbook_version.metadata.to_json)
694
+
695
+ cookbook_version.freeze_version if o["frozen?"]
674
696
  cookbook_version
675
697
  end
676
-
698
+
677
699
  def generate_manifest_with_urls(&url_generator)
678
700
  rendered_manifest = manifest.dup
679
701
  COOKBOOK_SEGMENTS.each do |segment|
@@ -712,12 +734,23 @@ class Chef
712
734
  self.class.chef_server_rest
713
735
  end
714
736
 
737
+ # Save this object to the server via the REST api. If there is an existing
738
+ # document on the server and it is marked frozen, a
739
+ # Net::HTTPServerException will be raised for 409 Conflict.
715
740
  def save
716
741
  chef_server_rest.put_rest("cookbooks/#{name}/#{version}", self)
717
742
  self
718
743
  end
719
744
  alias :create :save
720
745
 
746
+ # Adds the `force=true` parameter to the upload. This allows the user to
747
+ # overwrite a frozen cookbook (normal #save raises a
748
+ # Net::HTTPServerException for 409 Conflict in this case).
749
+ def force_save
750
+ chef_server_rest.put_rest("cookbooks/#{name}/#{version}?force=true", self)
751
+ self
752
+ end
753
+
721
754
  def destroy
722
755
  chef_server_rest.delete_rest("cookbooks/#{name}/#{version}")
723
756
  self
@@ -757,7 +790,7 @@ class Chef
757
790
  ##
758
791
  # Couchdb
759
792
  ##
760
-
793
+
761
794
  def self.cdb_by_name(cookbook_name, couchdb=nil)
762
795
  cdb = (couchdb || Chef::CouchDB.new)
763
796
  options = { :startkey => cookbook_name, :endkey => cookbook_name }
@@ -787,9 +820,13 @@ class Chef
787
820
  end
788
821
 
789
822
  def self.cdb_list(inflate=false, couchdb=nil)
790
- rs = (couchdb || Chef::CouchDB.new).list("cookbooks", inflate)
791
- lookup = (inflate ? "value" : "key")
792
- rs["rows"].collect { |r| r[lookup] }
823
+ couchdb ||= Chef::CouchDB.new
824
+ if inflate
825
+ couchdb.list("cookbooks", true)["rows"].collect{|r| r["value"]}
826
+ else
827
+ # If you modify this, please make sure the desc sorted order on the versions doesn't get broken.
828
+ couchdb.get_view("cookbooks", "all_with_version")["rows"].inject({}) { |mapped, row| mapped[row["key"]]||=Array.new; mapped[row["key"]].push(Chef::Version.new(row["value"])); mapped[row["key"]].sort!.reverse!; mapped}
829
+ end
793
830
  end
794
831
 
795
832
  def self.cdb_load(name, version='latest', couchdb=nil)
@@ -807,7 +844,7 @@ class Chef
807
844
  end
808
845
 
809
846
  # Runs on Chef Server (API); deletes the cookbook from couchdb and also destroys associated
810
- # checksum documents
847
+ # checksum documents
811
848
  def purge
812
849
  checksums.keys.each do |checksum|
813
850
  begin
@@ -827,8 +864,16 @@ class Chef
827
864
  @index_id = value
828
865
  end
829
866
 
867
+ def <=>(o)
868
+ raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != o.name
869
+ # FIXME: can we change the interface to the Metadata class such
870
+ # that metadata.version returns a Chef::Version instance instead
871
+ # of a string?
872
+ Chef::Version.new(self.version) <=> Chef::Version.new(o.version)
873
+ end
874
+
830
875
  private
831
-
876
+
832
877
  # For each filename, produce a mapping of base filename (i.e. recipe name
833
878
  # or attribute file) to on disk location
834
879
  def filenames_by_name(filenames)
@@ -858,7 +903,7 @@ class Chef
858
903
  file_name = nil
859
904
  path = nil
860
905
  specificity = "default"
861
-
906
+
862
907
  if segment == :root_files
863
908
  matcher = segment_file.match(".+/#{Regexp.escape(name.to_s)}/(.+)")
864
909
  file_name = matcher[1]
@@ -877,7 +922,7 @@ class Chef
877
922
  path = matcher[1]
878
923
  file_name = matcher[2]
879
924
  end
880
-
925
+
881
926
  csum = self.class.checksum_cookbook_file(segment_file)
882
927
  checksums_to_on_disk_paths[csum] = segment_file
883
928
  rs = Mash.new({
@@ -900,7 +945,7 @@ class Chef
900
945
  @manifest = manifest
901
946
  @manifest_records_by_path = extract_manifest_records_by_path(manifest)
902
947
  end
903
-
948
+
904
949
  def file_vendor
905
950
  unless @file_vendor
906
951
  @file_vendor = Chef::Cookbook::FileVendor.create_from_manifest(manifest)
@@ -918,7 +963,7 @@ class Chef
918
963
  end
919
964
  checksums
920
965
  end
921
-
966
+
922
967
  def extract_manifest_records_by_path(manifest)
923
968
  manifest_records_by_path = {}
924
969
  COOKBOOK_SEGMENTS.each do |segment|
@@ -929,6 +974,6 @@ class Chef
929
974
  end
930
975
  manifest_records_by_path
931
976
  end
932
-
977
+
933
978
  end
934
979
  end
@@ -0,0 +1,163 @@
1
+ #
2
+ # Author:: Tim Hinderliter (<tim@opscode.com>)
3
+ # Copyright:: Copyright (c) 2011 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'dep_selector'
19
+
20
+ class Chef
21
+ module CookbookVersionSelector
22
+ # This method replaces verbiage from DepSelector messages with
23
+ # Chef-domain-specific verbiage, such as replacing package with
24
+ # cookbook.
25
+ #
26
+ # TODO [cw, 2011/2/25]: this is a near-term hack. In the long run,
27
+ # we'll do this better.
28
+ def self.filter_dep_selector_message(message)
29
+ m = message
30
+ m.gsub!("Package", "Cookbook")
31
+ m.gsub!("package", "cookbook")
32
+ m.gsub!("Solution constraint", "Run list item")
33
+ m.gsub!("solution constraint", "run list item")
34
+ m
35
+ end
36
+
37
+ # all_cookbooks - a hash mapping cookbook names to an array of
38
+ # available CookbookVersions.
39
+ #
40
+ # Creates a DependencyGraph from CookbookVersion objects
41
+ def self.create_dependency_graph_from_cookbooks(all_cookbooks)
42
+ dep_graph = DepSelector::DependencyGraph.new
43
+
44
+ all_cookbooks.each do |cb_name, cb_versions|
45
+ cb_versions.each do |cb_version|
46
+ cb_version_deps = cb_version.metadata.dependencies
47
+ # TODO [cw. 2011/2/10]: CookbookVersion#version returns a
48
+ # String even though we're storing as a DepSelector::Version
49
+ # object underneath. This should be changed so that we
50
+ # return the object and handle proper serialization and
51
+ # de-serialization. For now, I'm just going to create a
52
+ # Version object from the String representation.
53
+ pv = dep_graph.package(cb_name).add_version(Chef::Version.new(cb_version.version))
54
+ cb_version_deps.each_pair do |dep_name, constraint_str|
55
+ # if the dependency is specified as cookbook::recipe,
56
+ # extract the cookbook component
57
+ dep_cb_name = dep_name.split("::").first
58
+ constraint = Chef::VersionConstraint.new(constraint_str)
59
+ pv.dependencies << DepSelector::Dependency.new(dep_graph.package(dep_cb_name), constraint)
60
+ end
61
+ end
62
+ end
63
+
64
+ dep_graph
65
+ end
66
+
67
+ # Return a hash mapping cookbook names to a CookbookVersion
68
+ # object. If there is no solution that satisfies the constraints,
69
+ # the first run list item that caused unsatisfiability is
70
+ # returned.
71
+ #
72
+ # This is the final version-resolved list of cookbooks for the
73
+ # RunList.
74
+ #
75
+ # all_cookbooks - a hash mapping cookbook names to an array of
76
+ # available CookbookVersions.
77
+ #
78
+ # recipe_constraints - an array of hashes describing the expanded
79
+ # run list. Each element is a hash containing keys :name and
80
+ # :version_constraint. The :name component is either the
81
+ # fully-qualified recipe name (e.g. "cookbook1::non_default_recipe")
82
+ # or just a cookbook name, indicating the default recipe is to be
83
+ # run (e.g. "cookbook1").
84
+ def self.constrain(all_cookbooks, recipe_constraints)
85
+ dep_graph = create_dependency_graph_from_cookbooks(all_cookbooks)
86
+
87
+ # extract cookbook names from (possibly) fully-qualified recipe names
88
+ cookbook_constraints = recipe_constraints.map do |recipe_spec|
89
+ cookbook_name = (recipe_spec[:name][/^(.+)::/, 1] || recipe_spec[:name])
90
+ DepSelector::SolutionConstraint.new(dep_graph.package(cookbook_name),
91
+ recipe_spec[:version_constraint])
92
+ end
93
+
94
+ # Pass in the list of all available cookbooks (packages) so that
95
+ # DepSelector can distinguish between "no version available for
96
+ # cookbook X" and "no such cookbook X" when an environment
97
+ # filters out all versions for a given cookbook.
98
+ all_packages = all_cookbooks.inject([]) do |acc, (cookbook_name, cookbook_versions)|
99
+ acc << dep_graph.package(cookbook_name)
100
+ acc
101
+ end
102
+
103
+ # find a valid assignment of CoookbookVersions. If no valid
104
+ # assignment exists, indicate which run_list_item causes the
105
+ # unsatisfiability and try to hint at what might be wrong.
106
+ soln =
107
+ begin
108
+ DepSelector::Selector.new(dep_graph).find_solution(cookbook_constraints, all_packages)
109
+ rescue DepSelector::Exceptions::InvalidSolutionConstraints => e
110
+ non_existent_cookbooks = e.non_existent_packages.map {|constraint| constraint.package.name}
111
+ cookbooks_with_no_matching_versions = e.constrained_to_no_versions.map {|constraint| constraint.package.name}
112
+
113
+ # Spend a whole lot of effort for pluralizing and
114
+ # prettifying the message.
115
+ message = ""
116
+ if non_existent_cookbooks.length > 0
117
+ message += "no such " + (non_existent_cookbooks.length > 1 ? "cookbooks" : "cookbook")
118
+ message += " #{non_existent_cookbooks.join(", ")}"
119
+ end
120
+
121
+ if cookbooks_with_no_matching_versions.length > 0
122
+ if message.length > 0
123
+ message += "; "
124
+ end
125
+
126
+ message += "no versions match the constraints on " + (cookbooks_with_no_matching_versions.length > 1 ? "cookbooks" : "cookbook")
127
+ message += " #{cookbooks_with_no_matching_versions.join(", ")}"
128
+ end
129
+
130
+ message = "Run list contains invalid items: #{message}."
131
+
132
+ raise Chef::Exceptions::CookbookVersionSelection::InvalidRunListItems.new(message, non_existent_cookbooks, cookbooks_with_no_matching_versions)
133
+ rescue DepSelector::Exceptions::NoSolutionExists => e
134
+ raise Chef::Exceptions::CookbookVersionSelection::UnsatisfiableRunListItem.new(filter_dep_selector_message(e.message), e.unsatisfiable_solution_constraint, e.disabled_non_existent_packages, e.disabled_most_constrained_packages)
135
+ end
136
+
137
+
138
+ # map assignment back to CookbookVersion objects
139
+ selected_cookbooks = {}
140
+ soln.each_pair do |cb_name, cb_version|
141
+ # TODO [cw, 2011/2/10]: related to the TODO in
142
+ # create_dependency_graph_from_cookbooks, cbv.version
143
+ # currently returns a String, so we must compare to
144
+ # cb_version.to_s, since it's a for-real Version object.
145
+ selected_cookbooks[cb_name] = all_cookbooks[cb_name].find{|cbv| cbv.version == cb_version.to_s}
146
+ end
147
+ selected_cookbooks
148
+ end
149
+
150
+ # Expands the run_list, constrained to the environment's CookbookVersion
151
+ # constraints.
152
+ #
153
+ # Returns:
154
+ # Hash of: name to CookbookVersion
155
+ def self.expand_to_cookbook_versions(run_list, environment, couchdb=nil)
156
+ # expand any roles in this run_list.
157
+ expanded_run_list = run_list.expand(environment, 'couchdb', :couchdb => couchdb).recipes.with_version_constraints
158
+
159
+ cookbooks_for_environment = Chef::Environment.cdb_load_filtered_cookbook_versions(environment, couchdb)
160
+ constrain(cookbooks_for_environment, expanded_run_list)
161
+ end
162
+ end
163
+ end