chef 0.9.8 → 0.9.10.rc.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/README.rdoc +1 -1
  2. data/distro/common/man/man8/knife.8 +89 -79
  3. data/distro/common/markdown/knife.mkd +7 -0
  4. data/distro/debian/etc/default/chef-server +3 -0
  5. data/distro/debian/etc/default/chef-server-webui +3 -0
  6. data/distro/debian/etc/default/chef-solr +3 -0
  7. data/distro/debian/etc/default/chef-solr-indexer +3 -0
  8. data/distro/debian/etc/init.d/chef-server +3 -1
  9. data/distro/debian/etc/init.d/chef-server-webui +3 -1
  10. data/distro/redhat/etc/init.d/chef-client +1 -1
  11. data/lib/chef/application.rb +2 -0
  12. data/lib/chef/application/client.rb +5 -3
  13. data/lib/chef/application/knife.rb +16 -5
  14. data/lib/chef/application/solo.rb +0 -1
  15. data/lib/chef/checksum.rb +65 -1
  16. data/lib/chef/checksum_cache.rb +173 -0
  17. data/lib/chef/client.rb +84 -121
  18. data/lib/chef/cookbook/remote_file_vendor.rb +10 -3
  19. data/lib/chef/cookbook/syntax_check.rb +2 -2
  20. data/lib/chef/cookbook_loader.rb +2 -0
  21. data/lib/chef/cookbook_site_streaming_uploader.rb +29 -0
  22. data/lib/chef/cookbook_uploader.rb +8 -7
  23. data/lib/chef/cookbook_version.rb +155 -114
  24. data/lib/chef/exceptions.rb +5 -0
  25. data/lib/chef/handler.rb +43 -0
  26. data/lib/chef/index_queue/consumer.rb +1 -1
  27. data/lib/chef/index_queue/indexable.rb +1 -1
  28. data/lib/chef/knife.rb +18 -5
  29. data/lib/chef/knife/bootstrap.rb +2 -2
  30. data/lib/chef/knife/bootstrap/archlinux-gems.erb +44 -0
  31. data/lib/chef/knife/bootstrap/client-install.vbs +80 -0
  32. data/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb +2 -2
  33. data/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb +6 -7
  34. data/lib/chef/knife/bootstrap/windows-gems.erb +34 -0
  35. data/lib/chef/knife/configure_client.rb +4 -2
  36. data/lib/chef/knife/cookbook_metadata.rb +1 -1
  37. data/lib/chef/knife/cookbook_site_share.rb +2 -1
  38. data/lib/chef/knife/cookbook_site_vendor.rb +6 -0
  39. data/lib/chef/knife/cookbook_test.rb +1 -1
  40. data/lib/chef/knife/ec2_server_create.rb +51 -26
  41. data/lib/chef/knife/exec.rb +52 -0
  42. data/lib/chef/knife/ssh.rb +27 -15
  43. data/lib/chef/knife/status.rb +27 -10
  44. data/lib/chef/knife/windows_bootstrap.rb +154 -0
  45. data/lib/chef/mixin/checksum.rb +2 -2
  46. data/lib/chef/mixin/xml_escape.rb +75 -49
  47. data/lib/chef/node.rb +54 -58
  48. data/lib/chef/node/attribute.rb +61 -53
  49. data/lib/chef/platform.rb +19 -2
  50. data/lib/chef/provider/breakpoint.rb +1 -1
  51. data/lib/chef/provider/cookbook_file.rb +3 -3
  52. data/lib/chef/provider/cron.rb +3 -3
  53. data/lib/chef/provider/cron/solaris.rb +195 -0
  54. data/lib/chef/provider/deploy.rb +3 -3
  55. data/lib/chef/provider/directory.rb +2 -2
  56. data/lib/chef/provider/env.rb +5 -5
  57. data/lib/chef/provider/execute.rb +1 -1
  58. data/lib/chef/provider/file.rb +10 -9
  59. data/lib/chef/provider/git.rb +12 -4
  60. data/lib/chef/provider/group.rb +5 -5
  61. data/lib/chef/provider/http_request.rb +25 -9
  62. data/lib/chef/provider/ifconfig.rb +2 -2
  63. data/lib/chef/provider/link.rb +11 -6
  64. data/lib/chef/provider/log.rb +1 -0
  65. data/lib/chef/provider/mdadm.rb +3 -3
  66. data/lib/chef/provider/mount.rb +5 -5
  67. data/lib/chef/provider/mount/mount.rb +1 -1
  68. data/lib/chef/provider/ohai.rb +41 -0
  69. data/lib/chef/provider/package.rb +5 -5
  70. data/lib/chef/provider/package/yum-dump.py +5 -2
  71. data/lib/chef/provider/remote_directory.rb +11 -5
  72. data/lib/chef/provider/remote_file.rb +2 -2
  73. data/lib/chef/provider/route.rb +154 -133
  74. data/lib/chef/provider/ruby_block.rb +1 -1
  75. data/lib/chef/provider/service.rb +6 -6
  76. data/lib/chef/provider/subversion.rb +12 -9
  77. data/lib/chef/provider/template.rb +2 -2
  78. data/lib/chef/provider/user.rb +7 -7
  79. data/lib/chef/provider/user/useradd.rb +15 -1
  80. data/lib/chef/providers.rb +2 -0
  81. data/lib/chef/resource.rb +164 -58
  82. data/lib/chef/resource/http_request.rb +9 -0
  83. data/lib/chef/resource/ohai.rb +40 -0
  84. data/lib/chef/resource/remote_directory.rb +10 -1
  85. data/lib/chef/resource/rpm_package.rb +34 -0
  86. data/lib/chef/resource_collection.rb +3 -2
  87. data/lib/chef/resources.rb +2 -0
  88. data/lib/chef/rest.rb +13 -7
  89. data/lib/chef/rest/auth_credentials.rb +1 -1
  90. data/lib/chef/rest/rest_request.rb +3 -1
  91. data/lib/chef/runner.rb +31 -55
  92. data/lib/chef/shef/shef_session.rb +1 -1
  93. data/lib/chef/util/windows/net_use.rb +1 -1
  94. data/lib/chef/version.rb +1 -1
  95. data/lib/chef/webui_user.rb +0 -1
  96. metadata +38 -19
  97. data/lib/chef/cache.rb +0 -61
  98. data/lib/chef/cache/checksum.rb +0 -91
@@ -19,6 +19,7 @@
19
19
  # limitations under the License.
20
20
 
21
21
  require 'chef/log'
22
+ require 'chef/client'
22
23
  require 'chef/node'
23
24
  require 'chef/resource_definition_list'
24
25
  require 'chef/recipe'
@@ -39,7 +40,7 @@ class Chef
39
40
  COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ]
40
41
 
41
42
  DESIGN_DOCUMENT = {
42
- "version" => 5,
43
+ "version" => 7,
43
44
  "language" => "javascript",
44
45
  "views" => {
45
46
  "all" => {
@@ -89,8 +90,8 @@ class Chef
89
90
  continue;
90
91
  }
91
92
 
92
- var valueParts = value[1].split('.').map(function(v) { return parseInt(v); });
93
- var resultParts = result[1].split('.').map(function(v) { return parseInt(v); });
93
+ var valueParts = value.split('.').map(function(v) { return parseInt(v); });
94
+ var resultParts = result.split('.').map(function(v) { return parseInt(v); });
94
95
 
95
96
  if (valueParts[0] != resultParts[0]) {
96
97
  if (valueParts[0] > resultParts[0]) {
@@ -116,7 +117,7 @@ class Chef
116
117
  "map" => %q@
117
118
  function(doc) {
118
119
  if (doc.chef_type == "cookbook_version") {
119
- emit(doc.cookbook_name, doc.version);
120
+ emit(doc.cookbook_name, {version: doc.version, id:doc._id});
120
121
  }
121
122
  }
122
123
  @,
@@ -132,8 +133,8 @@ class Chef
132
133
  continue;
133
134
  }
134
135
 
135
- var valueParts = value[1].split('.').map(function(v) { return parseInt(v); });
136
- var resultParts = result[1].split('.').map(function(v) { return parseInt(v); });
136
+ var valueParts = value.version.split('.').map(function(v) { return parseInt(v); });
137
+ var resultParts = result.version.split('.').map(function(v) { return parseInt(v); });
137
138
 
138
139
  if (valueParts[0] != resultParts[0]) {
139
140
  if (valueParts[0] > resultParts[0]) {
@@ -151,13 +152,14 @@ class Chef
151
152
  }
152
153
  }
153
154
  }
154
- return keys[idx][1];
155
+ return result;
155
156
  }
156
157
  @
157
158
  },
158
159
  }
159
160
  }
160
161
 
162
+ attr_accessor :root_dir
161
163
  attr_accessor :definition_filenames
162
164
  attr_accessor :template_filenames
163
165
  attr_accessor :file_filenames
@@ -188,12 +190,132 @@ class Chef
188
190
  # This is the one and only method that knows how cookbook files'
189
191
  # checksums are generated.
190
192
  def self.checksum_cookbook_file(filepath)
191
- Chef::Cache::Checksum.generate_md5_checksum_for_file(filepath)
193
+ Chef::ChecksumCache.generate_md5_checksum_for_file(filepath)
192
194
  rescue Errno::ENOENT
193
195
  Chef::Log.debug("File #{filepath} does not exist, so there is no checksum to generate")
194
196
  nil
195
197
  end
196
198
 
199
+ # Keep track of the filenames that we use in both eager cookbook
200
+ # downloading (during sync_cookbooks) and lazy (during the run
201
+ # itself, through FileVendor). After the run is over, clean up the
202
+ # cache.
203
+ def self.valid_cache_entries
204
+ @valid_cache_entries ||= {}
205
+ end
206
+
207
+ def self.reset_cache_validity
208
+ @valid_cache_entries = nil
209
+ end
210
+
211
+ # Setup a notification to clear the valid_cache_entries when a Chef client
212
+ # run starts
213
+ Chef::Client.when_run_starts do |run_status|
214
+ reset_cache_validity
215
+ end
216
+
217
+ # Synchronizes all the cookbooks from the chef-server.
218
+ #
219
+ # === Returns
220
+ # true:: Always returns true
221
+ def self.sync_cookbooks(cookbook_hash)
222
+ Chef::Log.debug("Cookbooks to load: #{cookbook_hash.inspect}")
223
+
224
+ # Remove all cookbooks no longer relevant to this node
225
+ Chef::FileCache.find(File.join(%w{cookbooks ** *})).each do |cache_file|
226
+ cache_file =~ /^cookbooks\/([^\/]+)\//
227
+ unless cookbook_hash.has_key?($1)
228
+ Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.")
229
+ Chef::FileCache.delete(cache_file)
230
+ end
231
+ end
232
+
233
+ # Synchronize each of the node's cookbooks, and add to the
234
+ # valid_cache_entries hash.
235
+ cookbook_hash.values.each do |cookbook|
236
+ sync_cookbook_file_cache(cookbook)
237
+ end
238
+
239
+ true
240
+ end
241
+
242
+ # Update the file caches for a given cache segment. Takes a segment name
243
+ # and a hash that matches one of the cookbooks/_attribute_files style
244
+ # remote file listings.
245
+ #
246
+ # === Parameters
247
+ # cookbook<Chef::Cookbook>:: The cookbook to update
248
+ # valid_cache_entries<Hash>:: Out-param; Added to this hash are the files that
249
+ # were referred to by this cookbook
250
+ def self.sync_cookbook_file_cache(cookbook)
251
+ Chef::Log.debug("Synchronizing cookbook #{cookbook.name}")
252
+
253
+ # files and templates are lazily loaded, and will be done later.
254
+ eager_segments = COOKBOOK_SEGMENTS.dup
255
+ eager_segments.delete(:files)
256
+ eager_segments.delete(:templates)
257
+
258
+ eager_segments.each do |segment|
259
+ segment_filenames = Array.new
260
+ cookbook.manifest[segment].each do |manifest_record|
261
+ # segment = cookbook segment
262
+ # remote_list = list of file hashes
263
+ #
264
+ # We need the list of known good attribute files, so we can delete any that are
265
+ # just laying about.
266
+
267
+ cache_filename = File.join("cookbooks", cookbook.name, manifest_record['path'])
268
+ valid_cache_entries[cache_filename] = true
269
+
270
+ current_checksum = nil
271
+ if Chef::FileCache.has_key?(cache_filename)
272
+ current_checksum = checksum_cookbook_file(Chef::FileCache.load(cache_filename, false))
273
+ end
274
+
275
+ # If the checksums are different between on-disk (current) and on-server
276
+ # (remote, per manifest), do the update. This will also execute if there
277
+ # is no current checksum.
278
+ if current_checksum != manifest_record['checksum']
279
+ raw_file = chef_server_rest.get_rest(manifest_record[:url], true)
280
+
281
+ Chef::Log.info("Storing updated #{cache_filename} in the cache.")
282
+ Chef::FileCache.move_to(raw_file.path, cache_filename)
283
+ else
284
+ Chef::Log.debug("Not storing #{cache_filename}, as the cache is up to date.")
285
+ end
286
+
287
+ # make the segment filenames a full path.
288
+ full_path_cache_filename = Chef::FileCache.load(cache_filename, false)
289
+ segment_filenames << full_path_cache_filename
290
+ end
291
+
292
+ # replace segment filenames with a full-path one.
293
+ if segment.to_sym == :recipes
294
+ cookbook.recipe_filenames = segment_filenames
295
+ elsif segment.to_sym == :attributes
296
+ cookbook.attribute_filenames = segment_filenames
297
+ else
298
+ cookbook.segment_filenames(segment).replace(segment_filenames)
299
+ end
300
+ end
301
+ end
302
+
303
+ def self.cleanup_file_cache
304
+ # Delete each file in the cache that we didn't encounter in the
305
+ # manifest.
306
+ Chef::FileCache.find(File.join(%w{cookbooks ** *})).each do |cache_filename|
307
+ unless valid_cache_entries[cache_filename]
308
+ Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer on the server.")
309
+ Chef::FileCache.delete(cache_filename)
310
+ end
311
+ end
312
+ end
313
+
314
+ # Register a notification to cleanup unused files from cookbooks
315
+ Chef::Client.when_run_completes_successfully do |run_status|
316
+ cleanup_file_cache
317
+ end
318
+
197
319
  # Creates a new Chef::CookbookVersion object.
198
320
  #
199
321
  # === Returns
@@ -210,6 +332,7 @@ class Chef
210
332
  @resource_filenames = Array.new
211
333
  @provider_filenames = Array.new
212
334
  @metadata_filenames = Array.new
335
+ @root_dir = nil
213
336
  @root_filenames = Array.new
214
337
  @couchdb_id = nil
215
338
  @couchdb = couchdb || Chef::CouchDB.new
@@ -473,113 +596,8 @@ class Chef
473
596
 
474
597
  records_by_pref[best_pref]
475
598
  end
476
-
477
- # Given a node, segment and path (filename or directory name),
478
- # return the priority-ordered list of preference locations to
479
- # look.
480
- def preferences_for_path(node, segment, path)
481
- # only files and templates can be platform-specific
482
- if segment.to_sym == :files || segment.to_sym == :templates
483
- begin
484
- platform, version = Chef::Platform.find_platform_and_version(node)
485
- rescue ArgumentError => e
486
- # Skip platform/version if they were not found by find_platform_and_version
487
- if e.message =~ /Cannot find a (?:platform|version)/
488
- platform = "/unknown_platform/"
489
- version = "/unknown_platform_version/"
490
- else
491
- raise
492
- end
493
- end
494
-
495
- fqdn = node[:fqdn]
496
-
497
- # Most specific to least specific places to find the path
498
- [
499
- File.join(segment.to_s, "host-#{fqdn}", path),
500
- File.join(segment.to_s, "#{platform}-#{version}", path),
501
- File.join(segment.to_s, platform.to_s, path),
502
- File.join(segment.to_s, "default", path)
503
- ]
504
- else
505
- [File.join(segment, path)]
506
- end
507
- end
508
- private :preferences_for_path
509
-
510
-
511
- def relative_filenames_in_preferred_directory(node, segment, dirname)
512
- preferences = preferences_for_path(node, segment, dirname)
513
- filenames_by_pref = Hash.new
514
- preferences.each { |pref| filenames_by_pref[pref] = Array.new }
515
-
516
- manifest[segment].each do |manifest_record|
517
- manifest_record_path = manifest_record[:path]
518
-
519
- # find the NON SPECIFIC filenames, but prefer them by filespecificity.
520
- # For example, if we have a file:
521
- # 'files/default/somedir/somefile.conf' we only keep
522
- # 'somedir/somefile.conf'. If there is also
523
- # 'files/$hostspecific/somedir/otherfiles' that matches the requested
524
- # hostname specificity, that directory will win, as it is more specific.
525
- #
526
- # This is clearly ugly b/c the use case is for remote directory, where
527
- # we're just going to make cookbook_files out of these and make the
528
- # cookbook find them by filespecificity again. but it's the shortest
529
- # path to "success" for now.
530
- if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/
531
- specificity_dirname = $1
532
- non_specific_path = manifest_record_path[/#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)}\/(.+)$/, 1]
533
- # Record the specificity_dirname only if it's in the list of
534
- # valid preferences
535
- if filenames_by_pref[specificity_dirname]
536
- filenames_by_pref[specificity_dirname] << non_specific_path
537
- end
538
- end
539
- end
540
-
541
- best_pref = preferences.find { |pref| !filenames_by_pref[pref].empty? }
542
-
543
- raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
544
-
545
- filenames_by_pref[best_pref]
546
-
547
- end
548
-
549
- # Determine the manifest records from the most specific directory
550
- # for the given node. See #preferred_manifest_record for a
551
- # description of entries of the returned Array.
552
- def preferred_manifest_records_for_directory(node, segment, dirname)
553
- preferences = preferences_for_path(node, segment, dirname)
554
- records_by_pref = Hash.new
555
- preferences.each { |pref| records_by_pref[pref] = Array.new }
556
-
557
- manifest[segment].each do |manifest_record|
558
- manifest_record_path = manifest_record[:path]
559
599
 
560
- # extract the preference part from the path.
561
- if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/
562
- # Note the specificy_dirname includes the segment and
563
- # dirname argument as above, which is what
564
- # preferences_for_path returns. It could be
565
- # "files/ubuntu-9.10/dirname", for example.
566
- specificity_dirname = $1
567
-
568
- # Record the specificity_dirname only if it's in the list of
569
- # valid preferences
570
- if records_by_pref[specificity_dirname]
571
- records_by_pref[specificity_dirname] << manifest_record
572
- end
573
- end
574
- end
575
-
576
- best_pref = preferences.find { |pref| !records_by_pref[pref].empty? }
577
-
578
- raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
579
600
 
580
- records_by_pref[best_pref]
581
- end
582
-
583
601
  # Given a node, segment and path (filename or directory name),
584
602
  # return the priority-ordered list of preference locations to
585
603
  # look.
@@ -656,6 +674,20 @@ class Chef
656
674
  rendered_manifest
657
675
  end
658
676
 
677
+ def metadata_json_file
678
+ File.join(root_dir, "metadata.json")
679
+ end
680
+
681
+ def metadata_rb_file
682
+ File.join(root_dir, "metadata.rb")
683
+ end
684
+
685
+ def reload_metadata!
686
+ if File.exists?(metadata_json_file)
687
+ metadata.from_json(IO.read(metadata_json_file))
688
+ end
689
+ end
690
+
659
691
  ##
660
692
  # REST API
661
693
  ##
@@ -727,7 +759,7 @@ class Chef
727
759
  def self.cdb_list_latest(inflate=false, couchdb=nil)
728
760
  couchdb ||= Chef::CouchDB.new
729
761
  if inflate
730
- doc_ids = cdb_list_latest_ids
762
+ doc_ids = cdb_list_latest_ids.map {|i|i["id"]}
731
763
  couchdb.bulk_get(doc_ids)
732
764
  else
733
765
  results = couchdb.get_view("cookbooks", "all_latest_version", :group=>true)["rows"]
@@ -761,6 +793,15 @@ class Chef
761
793
  (couchdb || Chef::CouchDB.new).delete("cookbook_version", full_name, couchdb_rev)
762
794
  end
763
795
 
796
+ # Runs on Chef Server (API); deletes the cookbook from couchdb and also destroys associated
797
+ # checksum documents
798
+ def purge
799
+ checksums.keys.each do |checksum|
800
+ Chef::Checksum.cdb_load(checksum, couchdb).purge
801
+ end
802
+ cdb_destroy
803
+ end
804
+
764
805
  def cdb_save
765
806
  @couchdb_rev = couchdb.store("cookbook_version", full_name, self)["rev"]
766
807
  end
@@ -57,5 +57,10 @@ class Chef
57
57
  class DsclCommandFailed < RuntimeError; end
58
58
  class UserIDNotFound < ArgumentError; end
59
59
  class GroupIDNotFound < ArgumentError; end
60
+ class InvalidResourceReference < RuntimeError; end
61
+ class ResourceNotFound < RuntimeError; end
62
+ class InvalidResourceSpecification < ArgumentError; end
63
+ class SolrConnectionError < RuntimeError; end
64
+ class IllegalChecksumRevert < RuntimeError; end
60
65
  end
61
66
  end
@@ -15,6 +15,7 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
+ require 'chef/client'
18
19
  require 'forwardable'
19
20
 
20
21
  class Chef
@@ -56,8 +57,50 @@ class Chef
56
57
  #
57
58
  class Handler
58
59
 
60
+ # The list of currently configured report handlers
61
+ def self.report_handlers
62
+ Array(Chef::Config[:report_handlers])
63
+ end
64
+
65
+ # Run the report handlers. This will usually be called by a notification
66
+ # from Chef::Client
67
+ def self.run_report_handlers(run_status)
68
+ Chef::Log.info("Running report handlers")
69
+ report_handlers.each do |handler|
70
+ handler.run_report_safely(run_status)
71
+ end
72
+ Chef::Log.info("Report handlers complete")
73
+ end
74
+
75
+ # Wire up a notification to run the report handlers if the chef run
76
+ # succeeds.
77
+ Chef::Client.when_run_completes_successfully do |run_status|
78
+ run_report_handlers(run_status)
79
+ end
80
+
81
+ # The list of currently configured exception handlers
82
+ def self.exception_handlers
83
+ Array(Chef::Config[:exception_handlers])
84
+ end
85
+
86
+ # Run the exception handlers. Usually will be called by a notification
87
+ # from Chef::Client when the run fails.
88
+ def self.run_exception_handlers(run_status)
89
+ Chef::Log.error("Running exception handlers")
90
+ exception_handlers.each do |handler|
91
+ handler.run_report_safely(run_status)
92
+ end
93
+ Chef::Log.error("Exception handlers complete")
94
+ end
95
+
96
+ # Wire up a notification to run the exception handlers if the chef run fails.
97
+ Chef::Client.when_run_fails do |run_status|
98
+ run_exception_handlers(run_status)
99
+ end
100
+
59
101
  extend Forwardable
60
102
 
103
+ # The Chef::RunStatus object containing data about the Chef run.
61
104
  attr_reader :run_status
62
105
 
63
106
  ##
@@ -56,7 +56,7 @@ class Chef
56
56
  alias :start :run
57
57
 
58
58
  def call_action_for_message(message)
59
- amqp_payload = JSON.parse(message[:payload])
59
+ amqp_payload = JSON.parse(message[:payload], :create_additions => false, :max_nesting => false)
60
60
  action = amqp_payload["action"].to_sym
61
61
  app_payload = amqp_payload["payload"]
62
62
  assert_method_whitelisted(action)
@@ -55,7 +55,7 @@ class Chef
55
55
  with_metadata["type"] ||= self.index_object_type
56
56
  with_metadata["id"] ||= self.index_id
57
57
  with_metadata["database"] ||= Chef::Config[:couchdb_database]
58
- with_metadata["item"] ||= self
58
+ with_metadata["item"] ||= self.to_hash
59
59
 
60
60
  raise ArgumentError, "Type, Id, or Database missing in index operation: #{with_metadata.inspect}" if (with_metadata["id"].nil? or with_metadata["type"].nil?)
61
61
  with_metadata
@@ -151,6 +151,18 @@ class Chef
151
151
  subcommand_class || subcommand_not_found!(args)
152
152
  end
153
153
 
154
+ protected
155
+
156
+ def load_late_dependency(dep, gem_name = nil)
157
+ begin
158
+ require dep
159
+ rescue LoadError
160
+ gem_name ||= dep.gsub('/', '-')
161
+ Chef::Log.fatal "#{gem_name} is not installed. run \"gem install #{gem_name}\" to install it."
162
+ exit 1
163
+ end
164
+ end
165
+
154
166
  private
155
167
 
156
168
  # :nodoc:
@@ -255,7 +267,6 @@ class Chef
255
267
  Chef::Config[:node_name] = config[:node_name] if config[:node_name]
256
268
  Chef::Config[:client_key] = config[:client_key] if config[:client_key]
257
269
  Chef::Config[:chef_server_url] = config[:chef_server_url] if config[:chef_server_url]
258
- Mixlib::Log::Formatter.show_time = false
259
270
  Chef::Log.init(Chef::Config[:log_location])
260
271
  Chef::Log.level(Chef::Config[:log_level])
261
272
 
@@ -407,10 +418,12 @@ class Chef
407
418
  object = klass.load(name)
408
419
 
409
420
  output = edit_data(object)
410
-
411
- output.save
412
-
413
- self.msg("Saved #{output}")
421
+ if output.to_s != object.to_s
422
+ output.save
423
+ self.msg("Saved #{output}")
424
+ else
425
+ self.msg("Object unchanged, not saving")
426
+ end
414
427
 
415
428
  output(format_for_display(object)) if config[:print_after]
416
429
  end