chef 0.9.8 → 0.9.10.rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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