chef 12.0.0.rc.0 → 12.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -1
  3. data/lib/chef/api_client/registration.rb +3 -1
  4. data/lib/chef/chef_fs/data_handler/group_data_handler.rb +4 -0
  5. data/lib/chef/config.rb +46 -38
  6. data/lib/chef/event_loggers/windows_eventlog.rb +5 -6
  7. data/lib/chef/exceptions.rb +13 -1
  8. data/lib/chef/file_content_management/tempfile.rb +33 -5
  9. data/lib/chef/knife.rb +11 -3
  10. data/lib/chef/knife/bootstrap.rb +8 -7
  11. data/lib/chef/mixin/deep_merge.rb +15 -54
  12. data/lib/chef/mixin/which.rb +37 -0
  13. data/lib/chef/node.rb +14 -25
  14. data/lib/chef/node/attribute.rb +227 -41
  15. data/lib/chef/node/attribute_collections.rb +117 -3
  16. data/lib/chef/node/immutable_collections.rb +6 -6
  17. data/lib/chef/platform/provider_priority_map.rb +3 -2
  18. data/lib/chef/platform/service_helpers.rb +37 -8
  19. data/lib/chef/provider/service/aixinit.rb +1 -1
  20. data/lib/chef/provider/service/arch.rb +1 -1
  21. data/lib/chef/provider/service/debian.rb +5 -1
  22. data/lib/chef/provider/service/init.rb +4 -0
  23. data/lib/chef/provider/service/insserv.rb +5 -1
  24. data/lib/chef/provider/service/invokercd.rb +5 -1
  25. data/lib/chef/provider/service/redhat.rb +5 -1
  26. data/lib/chef/provider/service/systemd.rb +50 -32
  27. data/lib/chef/provider/service/upstart.rb +5 -2
  28. data/lib/chef/provider_resolver.rb +30 -16
  29. data/lib/chef/resource.rb +2 -1
  30. data/lib/chef/resources.rb +7 -0
  31. data/lib/chef/run_context.rb +0 -5
  32. data/lib/chef/run_list/run_list_expansion.rb +2 -2
  33. data/lib/chef/shell.rb +2 -2
  34. data/lib/chef/util/selinux.rb +2 -10
  35. data/lib/chef/version.rb +1 -1
  36. data/lib/chef/workstation_config_loader.rb +1 -1
  37. data/spec/support/shared/unit/resource/static_provider_resolution.rb +1 -6
  38. data/spec/unit/api_client/registration_spec.rb +22 -0
  39. data/spec/unit/application/knife_spec.rb +6 -2
  40. data/spec/unit/chef_fs/data_handler/group_handler_spec.rb +63 -0
  41. data/spec/unit/config_spec.rb +5 -5
  42. data/spec/unit/knife/bootstrap_spec.rb +27 -1
  43. data/spec/unit/knife_spec.rb +5 -0
  44. data/spec/unit/mixin/deep_merge_spec.rb +0 -40
  45. data/spec/unit/node/attribute_spec.rb +37 -50
  46. data/spec/unit/node_spec.rb +321 -13
  47. data/spec/unit/provider/file/content_spec.rb +23 -2
  48. data/spec/unit/provider/service/systemd_service_spec.rb +173 -158
  49. data/spec/unit/provider_resolver_spec.rb +175 -10
  50. data/spec/unit/resource/timestamped_deploy_spec.rb +8 -29
  51. data/spec/unit/runner_spec.rb +3 -1
  52. metadata +141 -191
  53. data/spec/.DS_Store +0 -0
  54. data/spec/data/.DS_Store +0 -0
  55. data/spec/data/lwrp/.DS_Store +0 -0
  56. data/spec/data/lwrp/providers/.DS_Store +0 -0
  57. data/spec/data/lwrp/resources/.DS_Store +0 -0
  58. data/spec/data/lwrp_override/.DS_Store +0 -0
  59. data/spec/data/lwrp_override/providers/.DS_Store +0 -0
  60. data/spec/data/lwrp_override/resources/.DS_Store +0 -0
@@ -44,6 +44,10 @@ describe Chef::Knife do
44
44
  @stderr = StringIO.new
45
45
  end
46
46
 
47
+ after(:each) do
48
+ Chef::Knife.reset_config_loader!
49
+ end
50
+
47
51
  describe "after loading a subcommand" do
48
52
  before do
49
53
  Chef::Knife.reset_subcommands!
@@ -268,6 +272,7 @@ describe Chef::Knife do
268
272
  @knife.config[:verbosity] = 1
269
273
  @knife.config[:config_file] = fake_config
270
274
  config_loader = double("Chef::WorkstationConfigLoader", :load => true, :no_config_found? => false, :chef_config_dir => "/etc/chef", :config_location => fake_config)
275
+ allow(config_loader).to receive(:explicit_config_file=).with(fake_config).and_return(fake_config)
271
276
  allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader)
272
277
  end
273
278
 
@@ -236,20 +236,6 @@ describe Chef::Mixin::DeepMerge, "deep_merge!" do
236
236
  @dm.deep_merge!(hash_src, hash_dst)
237
237
  hash_dst.should == {"item" => "orange"}
238
238
  end
239
-
240
- it 'should overwrite hashes with nil' do
241
- hash_src = {"item" => { "1" => "2"}, "other" => true }
242
- hash_dst = {"item" => nil }
243
- @dm.deep_merge!(hash_src, hash_dst)
244
- hash_dst.should == {"item" => nil, "other" => true }
245
- end
246
-
247
- it 'should overwrite strings with nil' do
248
- hash_src = {"item" => "to_overwrite", "other" => false }
249
- hash_dst = {"item" => nil }
250
- @dm.deep_merge!(hash_src, hash_dst)
251
- hash_dst.should == {"item" => nil, "other" => false }
252
- end
253
239
  end # deep_merge!
254
240
 
255
241
  # Chef specific
@@ -304,32 +290,6 @@ describe Chef::Mixin::DeepMerge do
304
290
 
305
291
  end
306
292
 
307
- describe "role_merge" do
308
- it "errors out if knockout merge use is detected in an array" do
309
- hash_dst = {"property" => ["2","4"]}
310
- hash_src = {"property" => ["1","!merge:4"]}
311
- lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
312
- end
313
-
314
- it "errors out if knockout merge use is detected in an array (reversed merge order)" do
315
- hash_dst = {"property" => ["1","!merge:4"]}
316
- hash_src = {"property" => ["2","4"]}
317
- lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
318
- end
319
-
320
- it "errors out if knockout merge use is detected in a string" do
321
- hash_dst = {"property" => ["2","4"]}
322
- hash_src = {"property" => "!merge"}
323
- lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
324
- end
325
-
326
- it "errors out if knockout merge use is detected in a string (reversed merge order)" do
327
- hash_dst = {"property" => "!merge"}
328
- hash_src= {"property" => ["2","4"]}
329
- lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
330
- end
331
- end
332
-
333
293
  describe "hash-only merging" do
334
294
  it "merges Hashes like normal deep merge" do
335
295
  merge_ee_hash = {"top_level_a" => {"1_deep_a" => "1-a-merge-ee", "1_deep_b" => "1-deep-b-merge-ee"}, "top_level_b" => "top-level-b-merge-ee"}
@@ -285,7 +285,7 @@ describe Chef::Node::Attribute do
285
285
  end
286
286
 
287
287
  it "prefers 'forced default' over any other default" do
288
- @attributes.default!["default"] = "force default"
288
+ @attributes.force_default["default"] = "force default"
289
289
  @attributes.role_default["default"] = "role default"
290
290
  @attributes.env_default["default"] = "environment default"
291
291
  @attributes["default"].should == "force default"
@@ -307,7 +307,7 @@ describe Chef::Node::Attribute do
307
307
  end
308
308
 
309
309
  it "prefers 'forced overrides' over role or cookbook overrides" do
310
- @attributes.override!["override"] = "force override"
310
+ @attributes.force_override["override"] = "force override"
311
311
  @attributes.env_override["override"] = "environment override"
312
312
  @attributes.role_override["override"] = "role override"
313
313
  @attributes["override"].should == "force override"
@@ -554,8 +554,7 @@ describe Chef::Node::Attribute do
554
554
 
555
555
  it "should allow the last method to set a value if it has an = sign on the end" do
556
556
  @attributes.normal.music.mastodon = [ "dream", "still", "shining" ]
557
- @attributes.reset
558
- @attributes.normal.music.mastodon.should == [ "dream", "still", "shining" ]
557
+ expect(@attributes.normal.music.mastodon).to eq([ "dream", "still", "shining" ])
559
558
  end
560
559
  end
561
560
 
@@ -939,7 +938,6 @@ describe Chef::Node::Attribute do
939
938
 
940
939
  end
941
940
 
942
-
943
941
  describe "values" do
944
942
  before do
945
943
  @attributes = Chef::Node::Attribute.new(
@@ -1092,50 +1090,6 @@ describe Chef::Node::Attribute do
1092
1090
  end
1093
1091
  end
1094
1092
 
1095
- # For expedience, this test is implementation-heavy.
1096
- describe "when a component attribute is mutated" do
1097
- [
1098
- :clear,
1099
- :shift
1100
- ].each do |mutator|
1101
- it "resets the cache when the mutator #{mutator} is called" do
1102
- @attributes.should_receive(:reset_cache)
1103
- @attributes.default.send(mutator)
1104
- end
1105
- end
1106
-
1107
- it "resets the cache when the mutator delete is called" do
1108
- @attributes.should_receive(:reset_cache)
1109
- @attributes.default.delete(:music)
1110
- end
1111
-
1112
- [
1113
- :merge!,
1114
- :update,
1115
- :replace
1116
- ].each do |mutator|
1117
- it "resets the cache when the mutator #{mutator} is called" do
1118
- # Implementation of Mash means that this could get called many times. That's okay.
1119
- @attributes.should_receive(:reset_cache).at_least(1).times
1120
- @attributes.default.send(mutator, {:foo => :bar})
1121
- end
1122
- end
1123
-
1124
- [
1125
- :delete_if,
1126
- :keep_if,
1127
- :reject!,
1128
- :select!,
1129
- ].each do |mutator|
1130
- it "resets the cache when the mutator #{mutator} is called" do
1131
- # Implementation of Mash means that this could get called many times. That's okay.
1132
- @attributes.should_receive(:reset_cache).at_least(1).times
1133
- block = lambda {|k,v| true }
1134
- @attributes.default.send(mutator, &block)
1135
- end
1136
- end
1137
- end
1138
-
1139
1093
  describe "when not mutated" do
1140
1094
 
1141
1095
  it "does not reset the cache when dup'd [CHEF-3680]" do
@@ -1173,6 +1127,40 @@ describe Chef::Node::Attribute do
1173
1127
  end
1174
1128
  end
1175
1129
 
1130
+ describe "when deep-merging between precedence levels" do
1131
+ it "correctly deep merges hashes and preserves the original contents" do
1132
+ @attributes.default = { "arglebargle" => { "foo" => "bar" } }
1133
+ @attributes.override = { "arglebargle" => { "fizz" => "buzz" } }
1134
+ expect(@attributes.merged_attributes[:arglebargle]).to eq({ "foo" => "bar", "fizz" => "buzz" })
1135
+ expect(@attributes.default[:arglebargle]).to eq({ "foo" => "bar" })
1136
+ expect(@attributes.override[:arglebargle]).to eq({ "fizz" => "buzz" })
1137
+ end
1138
+
1139
+ it "does not deep merge arrays, and preserves the original contents" do
1140
+ @attributes.default = { "arglebargle" => [ 1, 2, 3 ] }
1141
+ @attributes.override = { "arglebargle" => [ 4, 5, 6 ] }
1142
+ expect(@attributes.merged_attributes[:arglebargle]).to eq([ 4, 5, 6 ])
1143
+ expect(@attributes.default[:arglebargle]).to eq([ 1, 2, 3 ])
1144
+ expect(@attributes.override[:arglebargle]).to eq([ 4, 5, 6 ])
1145
+ end
1146
+
1147
+ it "correctly deep merges hashes and preserves the original contents when merging default and role_default" do
1148
+ @attributes.default = { "arglebargle" => { "foo" => "bar" } }
1149
+ @attributes.role_default = { "arglebargle" => { "fizz" => "buzz" } }
1150
+ expect(@attributes.merged_attributes[:arglebargle]).to eq({ "foo" => "bar", "fizz" => "buzz" })
1151
+ expect(@attributes.default[:arglebargle]).to eq({ "foo" => "bar" })
1152
+ expect(@attributes.role_default[:arglebargle]).to eq({ "fizz" => "buzz" })
1153
+ end
1154
+
1155
+ it "correctly deep merges arrays, and preserves the original contents when merging default and role_default" do
1156
+ @attributes.default = { "arglebargle" => [ 1, 2, 3 ] }
1157
+ @attributes.role_default = { "arglebargle" => [ 4, 5, 6 ] }
1158
+ expect(@attributes.merged_attributes[:arglebargle]).to eq([ 1, 2, 3, 4, 5, 6 ])
1159
+ expect(@attributes.default[:arglebargle]).to eq([ 1, 2, 3 ])
1160
+ expect(@attributes.role_default[:arglebargle]).to eq([ 4, 5, 6 ])
1161
+ end
1162
+ end
1163
+
1176
1164
  describe "when attemping to write without specifying precedence" do
1177
1165
  it "raises an error when using []=" do
1178
1166
  lambda { @attributes[:new_key] = "new value" }.should raise_error(Chef::Exceptions::ImmutableAttributeModification)
@@ -1185,4 +1173,3 @@ describe Chef::Node::Attribute do
1185
1173
  end
1186
1174
 
1187
1175
  end
1188
-
@@ -247,13 +247,6 @@ describe Chef::Node do
247
247
  node.default.fuu.bahrr.baz = "qux"
248
248
  node.fuu.bahrr.baz.should == "qux"
249
249
  end
250
-
251
- it "accesses force defaults via default!" do
252
- node.default![:foo] = "wet bar"
253
- node.default[:foo] = "bar"
254
- node[:foo].should == "wet bar"
255
- end
256
-
257
250
  end
258
251
 
259
252
  describe "override attributes" do
@@ -292,13 +285,330 @@ describe Chef::Node do
292
285
  node.override.fuu.bahrr.baz = "qux"
293
286
  node.fuu.bahrr.baz.should == "qux"
294
287
  end
288
+ end
289
+
290
+ describe "globally deleting attributes" do
291
+ context "with hash values" do
292
+ before do
293
+ node.role_default["mysql"]["server"]["port"] = 1234
294
+ node.normal["mysql"]["server"]["port"] = 2345
295
+ node.override["mysql"]["server"]["port"] = 3456
296
+ end
297
+
298
+ it "deletes all the values and returns the value with the highest precidence" do
299
+ expect( node.rm("mysql", "server", "port") ).to eql(3456)
300
+ expect( node["mysql"]["server"]["port"] ).to be_nil
301
+ expect( node["mysql"]["server"] ).to eql({})
302
+ end
303
+
304
+ it "deletes nested things correctly" do
305
+ node.default["mysql"]["client"]["client_setting"] = "foo"
306
+ expect( node.rm("mysql", "server") ).to eql( {"port" => 3456} )
307
+ expect( node["mysql"] ).to eql( { "client" => { "client_setting" => "foo" } } )
308
+ end
309
+
310
+ it "returns nil if the node attribute does not exist" do
311
+ expect( node.rm("no", "such", "thing") ).to be_nil
312
+ end
295
313
 
296
- it "sets force_overrides via override!" do
297
- node.override![:foo] = "wet bar"
298
- node.override[:foo] = "bar"
299
- node[:foo].should == "wet bar"
314
+ it "can delete the entire tree" do
315
+ expect( node.rm("mysql") ).to eql({"server"=>{"port"=>3456}})
316
+ end
300
317
  end
301
318
 
319
+ context "when trying to delete through a thing that isn't an array-like or hash-like object" do
320
+ before do
321
+ node.default["mysql"] = true
322
+ end
323
+
324
+ it "returns nil when you're two levels deeper" do
325
+ expect( node.rm("mysql", "server", "port") ).to eql(nil)
326
+ end
327
+
328
+ it "returns nil when you're one level deeper" do
329
+ expect( node.rm("mysql", "server") ).to eql(nil)
330
+ end
331
+
332
+ it "correctly deletes at the top level" do
333
+ expect( node.rm("mysql") ).to eql(true)
334
+ end
335
+ end
336
+
337
+ context "with array indexes" do
338
+ before do
339
+ node.role_default["mysql"]["server"][0]["port"] = 1234
340
+ node.normal["mysql"]["server"][0]["port"] = 2345
341
+ node.override["mysql"]["server"][0]["port"] = 3456
342
+ node.override["mysql"]["server"][1]["port"] = 3456
343
+ end
344
+
345
+ it "deletes the array element" do
346
+ expect( node.rm("mysql", "server", 0, "port") ).to eql(3456)
347
+ expect( node["mysql"]["server"][0]["port"] ).to be_nil
348
+ expect( node["mysql"]["server"][1]["port"] ).to eql(3456)
349
+ end
350
+ end
351
+
352
+ context "with real arrays" do
353
+ before do
354
+ node.role_default["mysql"]["server"] = [ {
355
+ "port" => 1234,
356
+ } ]
357
+ node.normal["mysql"]["server"] = [ {
358
+ "port" => 2345,
359
+ } ]
360
+ node.override["mysql"]["server"] = [ {
361
+ "port" => 3456,
362
+ } ]
363
+ end
364
+
365
+ it "deletes the array element" do
366
+ expect( node.rm("mysql", "server", 0, "port") ).to eql(3456)
367
+ expect( node["mysql"]["server"][0]["port"] ).to be_nil
368
+ end
369
+
370
+ it "does not have a horrible error message when mistaking arrays for hashes" do
371
+ expect { node.rm("mysql", "server", "port") }.to raise_error(TypeError, "Wrong type in index of attribute (did you use a Hash index on an Array?)")
372
+ end
373
+ end
374
+ end
375
+
376
+ describe "granular deleting attributes" do
377
+ context "when only defaults exist" do
378
+ before do
379
+ node.role_default["mysql"]["server"]["port"] = 1234
380
+ node.default["mysql"]["server"]["port"] = 2345
381
+ node.force_default["mysql"]["server"]["port"] = 3456
382
+ end
383
+
384
+ it "returns the deleted values" do
385
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
386
+ end
387
+
388
+ it "returns nil for the combined attribues" do
389
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
390
+ expect( node["mysql"]["server"]["port"] ).to eql(nil)
391
+ end
392
+
393
+ it "returns an empty hash for the default attrs" do
394
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
395
+ # this auto-vivifies, should it?
396
+ expect( node.default_attrs["mysql"]["server"]["port"] ).to eql({})
397
+ end
398
+
399
+ it "returns an empty hash after the last key is deleted" do
400
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
401
+ expect( node["mysql"]["server"] ).to eql({})
402
+ end
403
+ end
404
+
405
+ context "when trying to delete through a thing that isn't an array-like or hash-like object" do
406
+ before do
407
+ node.default["mysql"] = true
408
+ end
409
+
410
+ it "returns nil when you're two levels deeper" do
411
+ expect( node.rm_default("mysql", "server", "port") ).to eql(nil)
412
+ end
413
+
414
+ it "returns nil when you're one level deeper" do
415
+ expect( node.rm_default("mysql", "server") ).to eql(nil)
416
+ end
417
+
418
+ it "correctly deletes at the top level" do
419
+ expect( node.rm_default("mysql") ).to eql(true)
420
+ end
421
+ end
422
+
423
+ context "when a higher precedence exists" do
424
+ before do
425
+ node.role_default["mysql"]["server"]["port"] = 1234
426
+ node.default["mysql"]["server"]["port"] = 2345
427
+ node.force_default["mysql"]["server"]["port"] = 3456
428
+
429
+ node.override["mysql"]["server"]["port"] = 9999
430
+ end
431
+
432
+ it "returns the deleted values" do
433
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
434
+ end
435
+
436
+ it "returns the higher precedence values after the delete" do
437
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
438
+ expect( node["mysql"]["server"]["port"] ).to eql(9999)
439
+ end
440
+
441
+ it "returns an empty has for the default attrs" do
442
+ expect( node.rm_default("mysql", "server", "port") ).to eql(3456)
443
+ # this auto-vivifies, should it?
444
+ expect( node.default_attrs["mysql"]["server"]["port"] ).to eql({})
445
+ end
446
+ end
447
+
448
+ context "when a lower precedence exists" do
449
+ before do
450
+ node.default["mysql"]["server"]["port"] = 2345
451
+ node.override["mysql"]["server"]["port"] = 9999
452
+ node.role_override["mysql"]["server"]["port"] = 9876
453
+ node.force_override["mysql"]["server"]["port"] = 6669
454
+ end
455
+
456
+ it "returns the deleted values" do
457
+ expect( node.rm_override("mysql", "server", "port") ).to eql(6669)
458
+ end
459
+
460
+ it "returns the lower precedence levels after the delete" do
461
+ expect( node.rm_override("mysql", "server", "port") ).to eql(6669)
462
+ expect( node["mysql"]["server"]["port"] ).to eql(2345)
463
+ end
464
+
465
+ it "returns an empty has for the override attrs" do
466
+ expect( node.rm_override("mysql", "server", "port") ).to eql(6669)
467
+ # this auto-vivifies, should it?
468
+ expect( node.override_attrs["mysql"]["server"]["port"] ).to eql({})
469
+ end
470
+ end
471
+
472
+ it "rm_default returns nil on deleting non-existent values" do
473
+ expect( node.rm_default("no", "such", "thing") ).to be_nil
474
+ end
475
+
476
+ it "rm_normal returns nil on deleting non-existent values" do
477
+ expect( node.rm_normal("no", "such", "thing") ).to be_nil
478
+ end
479
+
480
+ it "rm_override returns nil on deleting non-existent values" do
481
+ expect( node.rm_override("no", "such", "thing") ).to be_nil
482
+ end
483
+ end
484
+
485
+ describe "granular replacing attributes" do
486
+ it "removes everything at the level of the last key" do
487
+ node.default["mysql"]["server"]["port"] = 2345
488
+
489
+ node.default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql" }
490
+
491
+ expect( node["mysql"]["server"] ).to eql({ "data_dir" => "/my_raid_volume/lib/mysql" })
492
+ end
493
+
494
+ it "replaces a value at the cookbook sub-level of the atributes only" do
495
+ node.default["mysql"]["server"]["port"] = 2345
496
+ node.default["mysql"]["server"]["service_name"] = "fancypants-sql"
497
+ node.role_default["mysql"]["server"]["port"] = 1234
498
+ node.force_default["mysql"]["server"]["port"] = 3456
499
+
500
+ node.default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql" }
501
+
502
+ expect( node["mysql"]["server"]["port"] ).to eql(3456)
503
+ expect( node["mysql"]["server"]["service_name"] ).to be_nil
504
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
505
+ expect( node["mysql"]["server"] ).to eql({ "port" => 3456, "data_dir" => "/my_raid_volume/lib/mysql" })
506
+ end
507
+
508
+ it "higher precedence values aren't removed" do
509
+ node.role_default["mysql"]["server"]["port"] = 1234
510
+ node.default["mysql"]["server"]["port"] = 2345
511
+ node.force_default["mysql"]["server"]["port"] = 3456
512
+ node.override["mysql"]["server"]["service_name"] = "fancypants-sql"
513
+
514
+ node.default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql" }
515
+
516
+ expect( node["mysql"]["server"]["port"] ).to eql(3456)
517
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
518
+ expect( node["mysql"]["server"] ).to eql({ "service_name" => "fancypants-sql", "port" => 3456, "data_dir" => "/my_raid_volume/lib/mysql" })
519
+ end
520
+ end
521
+
522
+ describe "granular force replacing attributes" do
523
+ it "removes everything at the level of the last key" do
524
+ node.force_default["mysql"]["server"]["port"] = 2345
525
+
526
+ node.force_default!["mysql"]["server"] = {
527
+ "data_dir" => "/my_raid_volume/lib/mysql",
528
+ }
529
+
530
+ expect( node["mysql"]["server"] ).to eql({
531
+ "data_dir" => "/my_raid_volume/lib/mysql",
532
+ })
533
+ end
534
+
535
+ it "removes all values from the precedence level when setting" do
536
+ node.role_default["mysql"]["server"]["port"] = 1234
537
+ node.default["mysql"]["server"]["port"] = 2345
538
+ node.force_default["mysql"]["server"]["port"] = 3456
539
+
540
+ node.force_default!["mysql"]["server"] = {
541
+ "data_dir" => "/my_raid_volume/lib/mysql",
542
+ }
543
+
544
+ expect( node["mysql"]["server"]["port"] ).to be_nil
545
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
546
+ expect( node["mysql"]["server"] ).to eql({
547
+ "data_dir" => "/my_raid_volume/lib/mysql",
548
+ })
549
+ end
550
+
551
+ it "higher precedence levels are not removed" do
552
+ node.role_default["mysql"]["server"]["port"] = 1234
553
+ node.default["mysql"]["server"]["port"] = 2345
554
+ node.force_default["mysql"]["server"]["port"] = 3456
555
+ node.override["mysql"]["server"]["service_name"] = "fancypants-sql"
556
+
557
+ node.force_default!["mysql"]["server"] = {
558
+ "data_dir" => "/my_raid_volume/lib/mysql",
559
+ }
560
+
561
+ expect( node["mysql"]["server"]["port"] ).to be_nil
562
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
563
+ expect( node["mysql"]["server"] ).to eql({
564
+ "service_name" => "fancypants-sql",
565
+ "data_dir" => "/my_raid_volume/lib/mysql",
566
+ })
567
+ end
568
+
569
+ it "will autovivify" do
570
+ node.force_default!["mysql"]["server"] = {
571
+ "data_dir" => "/my_raid_volume/lib/mysql",
572
+ }
573
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
574
+ end
575
+
576
+ it "lower precedence levels aren't removed" do
577
+ node.role_override["mysql"]["server"]["port"] = 1234
578
+ node.override["mysql"]["server"]["port"] = 2345
579
+ node.force_override["mysql"]["server"]["port"] = 3456
580
+ node.default["mysql"]["server"]["service_name"] = "fancypants-sql"
581
+
582
+ node.force_override!["mysql"]["server"] = {
583
+ "data_dir" => "/my_raid_volume/lib/mysql",
584
+ }
585
+
586
+ expect( node["mysql"]["server"]["port"] ).to be_nil
587
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
588
+ expect( node["mysql"]["server"] ).to eql({
589
+ "service_name" => "fancypants-sql",
590
+ "data_dir" => "/my_raid_volume/lib/mysql",
591
+ })
592
+ end
593
+
594
+ it "when overwriting a non-hash/array" do
595
+ node.override["mysql"] = false
596
+ node.force_override["mysql"] = true
597
+ node.force_override!["mysql"]["server"] = {
598
+ "data_dir" => "/my_raid_volume/lib/mysql",
599
+ }
600
+ expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql")
601
+ end
602
+
603
+ it "when overwriting an array with a hash" do
604
+ node.force_override["mysql"][0] = true
605
+ node.force_override!["mysql"]["server"] = {
606
+ "data_dir" => "/my_raid_volume/lib/mysql",
607
+ }
608
+ expect( node["mysql"]["server"] ).to eql({
609
+ "data_dir" => "/my_raid_volume/lib/mysql",
610
+ })
611
+ end
302
612
  end
303
613
 
304
614
  it "should raise an ArgumentError if you ask for an attribute that doesn't exist via method_missing" do
@@ -536,7 +846,6 @@ describe Chef::Node do
536
846
  @expansion.default_attrs.replace({:default => "from role", :d_role => "role only"})
537
847
  @expansion.override_attrs.replace({:override => "from role", :o_role => "role only"})
538
848
 
539
-
540
849
  @environment = Chef::Environment.new
541
850
  @environment.default_attributes = {:default => "from env", :d_env => "env only" }
542
851
  @environment.override_attributes = {:override => "from env", :o_env => "env only"}
@@ -753,7 +1062,6 @@ describe Chef::Node do
753
1062
  node_for_json["default"]["env default"].should == "env default"
754
1063
  end
755
1064
 
756
-
757
1065
  it "should deserialize itself from json", :json => true do
758
1066
  node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA))
759
1067
  json = Chef::JSONCompat.to_json(node)