fpm-cookery 0.32.0 → 0.33.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 +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +5 -2
  4. data/CHANGELOG.md +19 -0
  5. data/Rakefile +34 -0
  6. data/docs/index.rst +1 -0
  7. data/docs/pages/using-hiera.rst +285 -0
  8. data/fpm-cookery.gemspec +6 -1
  9. data/lib/fpm/cookery/book.rb +29 -2
  10. data/lib/fpm/cookery/book_hook.rb +1 -0
  11. data/lib/fpm/cookery/chain_packager.rb +4 -2
  12. data/lib/fpm/cookery/cli.rb +45 -5
  13. data/lib/fpm/cookery/config.rb +2 -1
  14. data/lib/fpm/cookery/environment.rb +17 -8
  15. data/lib/fpm/cookery/exceptions.rb +3 -1
  16. data/lib/fpm/cookery/facts.rb +50 -35
  17. data/lib/fpm/cookery/hiera.rb +35 -0
  18. data/lib/fpm/cookery/hiera/defaults.rb +50 -0
  19. data/lib/fpm/cookery/hiera/scope.rb +35 -0
  20. data/lib/fpm/cookery/inheritable_attr.rb +222 -0
  21. data/lib/fpm/cookery/log/hiera.rb +21 -0
  22. data/lib/fpm/cookery/omnibus_packager.rb +4 -2
  23. data/lib/fpm/cookery/package/package.rb +1 -0
  24. data/lib/fpm/cookery/package/version.rb +11 -4
  25. data/lib/fpm/cookery/packager.rb +13 -11
  26. data/lib/fpm/cookery/recipe.rb +167 -105
  27. data/lib/fpm/cookery/source.rb +6 -8
  28. data/lib/fpm/cookery/source_handler.rb +18 -3
  29. data/lib/fpm/cookery/source_handler/curl.rb +2 -2
  30. data/lib/fpm/cookery/source_handler/directory.rb +10 -11
  31. data/lib/fpm/cookery/source_handler/noop.rb +1 -2
  32. data/lib/fpm/cookery/source_handler/svn.rb +1 -1
  33. data/lib/fpm/cookery/version.rb +1 -1
  34. data/lib/hiera/fpm_cookery_logger.rb +12 -0
  35. data/recipes/redis/config/common.yaml +11 -0
  36. data/recipes/redis/config/git_2.4.2_tag.yaml +4 -0
  37. data/recipes/redis/config/git_2.4.yaml +4 -0
  38. data/recipes/redis/config/git_sha_072a905.yaml +4 -0
  39. data/recipes/redis/config/svn_r2400.yaml +4 -0
  40. data/recipes/redis/config/svn_trunk.yaml +3 -0
  41. data/recipes/redis/recipe.rb +2 -27
  42. data/spec/book_spec.rb +34 -0
  43. data/spec/config_spec.rb +19 -0
  44. data/spec/environment_spec.rb +37 -0
  45. data/spec/facts_spec.rb +54 -31
  46. data/spec/fixtures/hiera_config/CentOS.yaml +1 -0
  47. data/spec/fixtures/hiera_config/common.yaml +12 -0
  48. data/spec/fixtures/hiera_config/custom.yaml +3 -0
  49. data/spec/fixtures/hiera_config/rpm.yaml +12 -0
  50. data/spec/hiera_spec.rb +158 -0
  51. data/spec/inheritable_attr_spec.rb +202 -0
  52. data/spec/package_dir_spec.rb +37 -0
  53. data/spec/package_maintainer_spec.rb +4 -1
  54. data/spec/package_version_spec.rb +50 -0
  55. data/spec/path_spec.rb +20 -0
  56. data/spec/recipe_spec.rb +161 -56
  57. data/spec/source_integrity_check_spec.rb +7 -6
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/shared_context.rb +71 -0
  60. metadata +108 -4
@@ -67,6 +67,26 @@ describe "Path" do
67
67
  end
68
68
  end
69
69
 
70
+ describe "#=~" do
71
+ let(:path) { FPM::Cookery::Path.new('/bar/baz/quux.txt') }
72
+
73
+ context "given a Regexp matching the path" do
74
+ it "returns the index of the beginning of the the match" do
75
+ expect(path =~ %r[/\w{4}\.\w{3}]).to eq(8)
76
+ end
77
+ end
78
+
79
+ context "given a non-Regexp argument" do
80
+ it "raises TypeError" do
81
+ expect { path =~ 'this' }.to raise_error do |error|
82
+ expect(error).to be_a(TypeError)
83
+ expect(error.message).to match(/type\s+mismatch/)
84
+ end
85
+ end
86
+ end
87
+
88
+ end
89
+
70
90
  describe "#mkdir" do
71
91
  it "creates the directory" do
72
92
  dir = Dir.mktmpdir
@@ -1,23 +1,28 @@
1
1
  require 'spec_helper'
2
+ require 'json'
3
+ require 'fpm/cookery/book'
4
+ require 'fpm/cookery/book_hook'
5
+ require 'fpm/cookery/exceptions'
6
+ require 'fpm/cookery/facts'
2
7
  require 'fpm/cookery/recipe'
3
8
 
4
- describe "Recipe" do
5
- def stub_dir(dir, path)
6
- value = path.nil? ? nil : FPM::Cookery::Path.new(path)
7
- allow(config).to receive(dir).and_return(value)
8
- end
9
-
10
- let(:klass) do
11
- Class.new(FPM::Cookery::Recipe)
12
- end
13
-
14
- let(:config) do
15
- double('Config', :tmp_root => nil, :pkg_dir => nil, :cache_dir => nil).as_null_object
9
+ describe "BaseRecipe" do
10
+ [:config, :filename].each do |method_name|
11
+ describe method_name.to_s do
12
+ it "informs the caller that it must be implemented at runtime" do
13
+ expect { FPM::Cookery::BaseRecipe.send(method_name) }.to raise_error do |error|
14
+ expect(error).to be_a(NotImplementedError)
15
+ expect(error.message).to match(/must be defined when recipe file is loaded/)
16
+ end
17
+ end
18
+ end
16
19
  end
20
+ end
17
21
 
18
- let(:recipe) do
19
- klass.new(__FILE__, config)
20
- end
22
+ describe "Recipe" do
23
+ config_options = { :tmp_root => nil, :pkg_dir => nil, :cache_dir => nil,
24
+ :hiera_config => nil }
25
+ include_context "recipe class", __FILE__, config_options
21
26
 
22
27
  it "sets the filename" do
23
28
  expect(recipe.filename.to_s).to eq(__FILE__)
@@ -31,15 +36,26 @@ describe "Recipe" do
31
36
  describe "with a relative filename path" do
32
37
  it "expands the workdir path" do
33
38
  filename = "spec/#{File.basename(__FILE__)}"
34
- r = klass.new(filename, config)
39
+ r = recipe_klass.new
35
40
  expect(r.workdir.to_s).to eq(File.dirname(__FILE__))
36
41
  end
37
42
  end
38
43
  end
39
44
 
45
+ describe "#input" do
46
+ # Avoid Errno::ENOENT for recipe dir that no longer exists
47
+ before do
48
+ allow(config).to receive(:fetch).with(:input, nil).and_return([])
49
+ end
50
+
51
+ it "defaults to FPM::Cookery::Package::Dir" do
52
+ expect(recipe.input(config)).to be_a(FPM::Cookery::Package::Dir)
53
+ end
54
+ end
55
+
40
56
  describe "#source_handler" do
41
57
  it "returns the recipe's source handler" do
42
- klass.class_eval do
58
+ recipe_klass.class_eval do
43
59
  source 'http://example.com/foo-1.0.tar.gz', :foo => 'bar'
44
60
  end
45
61
 
@@ -53,12 +69,18 @@ describe "Recipe" do
53
69
  def check_attribute(attr, value, expect = nil)
54
70
  expect ||= value
55
71
 
56
- klass.send(attr, value)
72
+ recipe_klass.send(attr, value)
57
73
 
58
- expect(klass.send(attr)).to eq(expect)
74
+ expect(recipe_klass.send(attr)).to eq(expect)
59
75
  expect(recipe.send(attr)).to eq(expect)
60
76
  end
61
77
 
78
+ describe '#rpm_dist' do
79
+ it 'can be set' do
80
+ check_attribute(:rpm_dist, 'el7')
81
+ end
82
+ end
83
+
62
84
  describe "#arch" do
63
85
  it "can be set" do
64
86
  check_attribute(:arch, 'i386')
@@ -205,18 +227,60 @@ describe "Recipe" do
205
227
  end
206
228
  end
207
229
 
230
+ describe "#hiera" do
231
+ before do
232
+ allow(config).to receive(:hiera_config).and_return("/probably/does/not/exist/pretty/sure/anyway")
233
+ end
234
+
235
+ it "raises an error when Hiera config file does not exist" do
236
+ expect { recipe_klass.hiera }.to raise_error do |error|
237
+ expect(error).to be_an(FPM::Cookery::Error::ExecutionFailure)
238
+ expect(error.message).to match(/Encountered error loading Hiera/)
239
+ end
240
+ end
241
+ end
242
+
243
+ describe "#applicator" do
244
+ context "given an attribute not set in the Hiera data file(s)" do
245
+ before do
246
+ allow(recipe_klass).to receive(:lookup).with(:source).and_return(nil)
247
+ allow(recipe_klass).to receive(:source).and_return('http://www.facsimile.co.uk')
248
+ end
249
+
250
+ it "returns `nil'" do
251
+ expect(recipe_klass.send(:applicator, :source)).to be_nil
252
+ end
253
+
254
+ it "does not call the provided block" do
255
+ expect { recipe_klass.send(:applicator, :source) { print "whoa" } }.not_to \
256
+ output("whoa").to_stdout
257
+ end
258
+ end
259
+
260
+ context "given an attribute set in the Hiera data file(s)" do
261
+ before do
262
+ allow(recipe_klass).to receive(:lookup).with(:name).and_return('J.J. Jingleheimer-Schmidt IV')
263
+ end
264
+
265
+ it "calls the provided block" do
266
+ expect { recipe_klass.send(:applicator, :name) { print "whoa" } }.to \
267
+ output("whoa").to_stdout
268
+ end
269
+ end
270
+ end
271
+
208
272
  def self.spec_recipe_attribute_list(name, list)
209
273
  class_eval %Q{
210
274
  describe "##{name}" do
211
275
  it "can be set" do
212
- klass.class_eval do
276
+ recipe_klass.class_eval do
213
277
  #{name} "#{list[0]}"
214
278
  #{name} "#{list[1]}"
215
279
  end
216
- expect(klass.#{name}.size).to eq(#{list.size})
280
+ expect(recipe_klass.#{name}.size).to eq(#{list.size})
217
281
  expect(recipe.#{name}.size).to eq(#{list.size})
218
- expect(klass.#{name}[0]).to eq("#{list[0]}")
219
- expect(klass.#{name}[1]).to eq("#{list[1]}")
282
+ expect(recipe_klass.#{name}[0]).to eq("#{list[0]}")
283
+ expect(recipe_klass.#{name}[1]).to eq("#{list[1]}")
220
284
  expect(recipe.#{name}[0]).to eq("#{list[0]}")
221
285
  expect(recipe.#{name}[1]).to eq("#{list[1]}")
222
286
  end
@@ -238,44 +302,44 @@ describe "Recipe" do
238
302
 
239
303
  describe ".source" do
240
304
  it "sets a source url" do
241
- klass.class_eval do
305
+ recipe_klass.class_eval do
242
306
  source 'http://example.com/foo-1.0.tar.gz'
243
307
  end
244
308
 
245
- expect(klass.source).to eq('http://example.com/foo-1.0.tar.gz')
246
- expect(klass.new(__FILE__, config).source).to eq('http://example.com/foo-1.0.tar.gz')
309
+ expect(recipe_klass.source).to eq('http://example.com/foo-1.0.tar.gz')
310
+ expect(recipe_klass.new.source).to eq('http://example.com/foo-1.0.tar.gz')
247
311
  end
248
312
 
249
313
  describe "with specs" do
250
314
  it "sets specs" do
251
- klass.class_eval do
315
+ recipe_klass.class_eval do
252
316
  source 'http://example.com/foo-1.0.tar.gz', :foo => 'bar'
253
317
  end
254
318
 
255
- expect(klass.spec).to eq({:foo => 'bar'})
256
- expect(klass.new(__FILE__, config).spec).to eq({:foo => 'bar'})
319
+ expect(recipe_klass.spec).to eq({:foo => 'bar'})
320
+ expect(recipe_klass.new.spec).to eq({:foo => 'bar'})
257
321
  end
258
322
  end
259
323
  end
260
324
 
261
325
  describe ".url" do
262
326
  it "sets a source type (homebrew compat)" do
263
- klass.class_eval do
327
+ recipe_klass.class_eval do
264
328
  url 'http://example.com/foo-1.0.tar.gz'
265
329
  end
266
330
 
267
- expect(klass.source).to eq('http://example.com/foo-1.0.tar.gz')
268
- expect(klass.new(__FILE__, config).source).to eq('http://example.com/foo-1.0.tar.gz')
331
+ expect(recipe_klass.source).to eq('http://example.com/foo-1.0.tar.gz')
332
+ expect(recipe_klass.new.source).to eq('http://example.com/foo-1.0.tar.gz')
269
333
  end
270
334
 
271
335
  describe "with specs" do
272
336
  it "sets specs" do
273
- klass.class_eval do
337
+ recipe_klass.class_eval do
274
338
  url 'http://example.com/foo-1.0.tar.gz', :foo => 'bar'
275
339
  end
276
340
 
277
- expect(klass.spec).to eq({:foo => 'bar'})
278
- expect(klass.new(__FILE__, config).spec).to eq({:foo => 'bar'})
341
+ expect(recipe_klass.spec).to eq({:foo => 'bar'})
342
+ expect(recipe_klass.new.spec).to eq({:foo => 'bar'})
279
343
  end
280
344
  end
281
345
  end
@@ -293,18 +357,18 @@ describe "Recipe" do
293
357
 
294
358
  describe "#local_path" do
295
359
  it "returns the path to the local source file" do
296
- klass.class_eval do
360
+ recipe_klass.class_eval do
297
361
  source 'http://example.com/foo-1.0.tar.gz'
298
362
  end
299
363
 
300
- expect(File.basename(klass.new(__FILE__, config).local_path.to_s)).to eq('foo-1.0.tar.gz')
364
+ expect(File.basename(recipe_klass.new.local_path.to_s)).to eq('foo-1.0.tar.gz')
301
365
  end
302
366
  end
303
367
 
304
368
  describe ".platforms" do
305
369
  describe "with a list of platforms" do
306
370
  it "allows platform specific settings" do
307
- klass.class_eval do
371
+ recipe_klass.class_eval do
308
372
  def self.platform; :ubuntu; end
309
373
 
310
374
  vendor 'a'
@@ -314,13 +378,13 @@ describe "Recipe" do
314
378
  end
315
379
  end
316
380
 
317
- expect(klass.new(__FILE__, config).vendor).to eq('b')
381
+ expect(recipe_klass.new.vendor).to eq('b')
318
382
  end
319
383
  end
320
384
 
321
385
  describe "with a single platform" do
322
386
  it "allows platform specific settings" do
323
- klass.class_eval do
387
+ recipe_klass.class_eval do
324
388
  def self.platform; :ubuntu; end
325
389
 
326
390
  vendor 'a'
@@ -330,13 +394,13 @@ describe "Recipe" do
330
394
  end
331
395
  end
332
396
 
333
- expect(klass.new(__FILE__, config).vendor).to eq('b')
397
+ expect(recipe_klass.new.vendor).to eq('b')
334
398
  end
335
399
  end
336
400
 
337
401
  describe "without a matching platform" do
338
402
  it "does not set platform specific stuff" do
339
- klass.class_eval do
403
+ recipe_klass.class_eval do
340
404
  def self.platform; :centos; end
341
405
 
342
406
  vendor 'a'
@@ -346,7 +410,7 @@ describe "Recipe" do
346
410
  end
347
411
  end
348
412
 
349
- expect(klass.new(__FILE__, config).vendor).to eq('a')
413
+ expect(recipe_klass.new.vendor).to eq('a')
350
414
  end
351
415
  end
352
416
  end
@@ -360,7 +424,7 @@ describe "Recipe" do
360
424
 
361
425
  describe "with a list of architectures" do
362
426
  it "allows arch specific settings" do
363
- klass.class_eval do
427
+ recipe_klass.class_eval do
364
428
  vendor 'a'
365
429
 
366
430
  architectures [:i386, :x86_64] do
@@ -368,13 +432,13 @@ describe "Recipe" do
368
432
  end
369
433
  end
370
434
 
371
- expect(klass.new(__FILE__, config).vendor).to eq('b')
435
+ expect(recipe_klass.new.vendor).to eq('b')
372
436
  end
373
437
  end
374
438
 
375
439
  describe "with a single architecture" do
376
440
  it "allows arch specific settings" do
377
- klass.class_eval do
441
+ recipe_klass.class_eval do
378
442
  vendor 'a'
379
443
 
380
444
  architectures :x86_64 do
@@ -382,13 +446,13 @@ describe "Recipe" do
382
446
  end
383
447
  end
384
448
 
385
- expect(klass.new(__FILE__, config).vendor).to eq('b')
449
+ expect(recipe_klass.new.vendor).to eq('b')
386
450
  end
387
451
  end
388
452
 
389
453
  describe "without a matching architecture" do
390
454
  it "does not set arch specific settings" do
391
- klass.class_eval do
455
+ recipe_klass.class_eval do
392
456
  vendor 'a'
393
457
 
394
458
  architectures :i386 do
@@ -396,11 +460,16 @@ describe "Recipe" do
396
460
  end
397
461
  end
398
462
 
399
- expect(klass.new(__FILE__, config).vendor).to eq('a')
463
+ expect(recipe_klass.new.vendor).to eq('a')
400
464
  end
401
465
  end
402
466
  end
403
467
 
468
+ describe ".platform" do
469
+ it 'matches the current platform from FPM::Cookery::Facts' do
470
+ expect(recipe_klass.platform).to eq(FPM::Cookery::Facts.platform)
471
+ end
472
+ end
404
473
 
405
474
  #############################################################################
406
475
  # Directories
@@ -531,25 +600,25 @@ describe "Recipe" do
531
600
 
532
601
  describe "#depends_all" do
533
602
  it "returns build_depends and depends package names" do
534
- klass.depends [:pkg1, :pkg2]
535
- klass.build_depends [:pkg3, :pkg4]
603
+ recipe_klass.depends [:pkg1, :pkg2]
604
+ recipe_klass.build_depends [:pkg3, :pkg4]
536
605
 
537
606
  expect([:pkg1, :pkg2, :pkg3, :pkg4].all? { |i|
538
- klass.depends_all.member?(i) && recipe.depends_all.member?(i)
607
+ recipe_klass.depends_all.member?(i) && recipe.depends_all.member?(i)
539
608
  }).to eq(true)
540
609
  end
541
610
  end
542
611
 
543
612
  describe ".fpm_attributes" do
544
613
  it "returns hash object as default" do
545
- expect(klass.fpm_attributes).to be_a(Hash)
614
+ expect(recipe_klass.fpm_attributes).to be_a(Hash)
546
615
  end
547
616
 
548
617
  it "returns same value from instance method with hash assignment" do
549
618
  expect(recipe.fpm_attributes).to include({})
550
619
 
551
- klass.fpm_attributes[:rpm_user] = 'httpd'
552
- klass.fpm_attributes[:deb_user] = 'apache'
620
+ recipe_klass.fpm_attributes[:rpm_user] = 'httpd'
621
+ recipe_klass.fpm_attributes[:deb_user] = 'apache'
553
622
 
554
623
  expect(recipe.fpm_attributes).to include({:rpm_user=>'httpd', :deb_user=>'apache'})
555
624
  end
@@ -557,9 +626,45 @@ describe "Recipe" do
557
626
  it "returns same value from instance method with argument assignment" do
558
627
  expect(recipe.fpm_attributes).to include({})
559
628
 
560
- klass.fpm_attributes :rpm_user => 'httpd', :deb_user => 'apache'
629
+ recipe_klass.fpm_attributes :rpm_user => 'httpd', :deb_user => 'apache'
561
630
 
562
631
  expect(recipe.fpm_attributes).to include({:rpm_user=>'httpd', :deb_user=>'apache'})
563
632
  end
564
633
  end
634
+
635
+ describe "#to_json" do
636
+ it "returns a terse JSON-formatted string with recipe attributes" do
637
+ json = recipe.to_json
638
+ expect { JSON.load(json) }.not_to raise_error
639
+ expect(json.lines.to_a.length).to be == 1
640
+ expect(json).to match(/"name"/)
641
+ end
642
+ end
643
+
644
+ describe "#to_pretty_json" do
645
+ it "returns a multiline JSON-formatted string with recipe attributes" do
646
+ json = recipe.to_pretty_json
647
+ expect { JSON.load(json) }.not_to raise_error
648
+ expect(json.lines.to_a.length).to be > 1
649
+ expect(json).to match(/"name"/)
650
+ end
651
+ end
652
+
653
+ describe "#template" do
654
+ context "given an ERB template containing valid recipe attributes" do
655
+ it "formats the string with recipe attributes" do
656
+ formatted = recipe.template("<%= name %>")
657
+ expect(formatted).to be == recipe.name
658
+ end
659
+ end
660
+
661
+ context "given an ERB template containing invalid recipe attributes" do
662
+ it "raises an error" do
663
+ expect { recipe.template("<%= notarecipeattr %>") }.to raise_error do |error|
664
+ expect(error).to be_an(FPM::Cookery::Error::ExecutionFailure)
665
+ expect(error.message).to match(/no attribute.*notarecipeattr/)
666
+ end
667
+ end
668
+ end
669
+ end
565
670
  end
@@ -3,14 +3,15 @@ require 'fpm/cookery/source_integrity_check'
3
3
  require 'fpm/cookery/recipe'
4
4
 
5
5
  describe "SourceIntegrityCheck" do
6
- let(:recipe_class) do
7
- Class.new(FPM::Cookery::Recipe) {
8
- source 'http://example.com/foo.tar.gz'
9
- }
6
+ config_options = { :hiera_config => nil }
7
+ include_context "recipe class", __FILE__, config_options do
8
+ let(:klass) do
9
+ Class.new(FPM::Cookery::Recipe) {
10
+ source 'http://example.com/foo.tar.gz'
11
+ }
12
+ end
10
13
  end
11
14
 
12
- let(:config) { double('Config').as_null_object }
13
- let(:recipe) { recipe_class.new(__FILE__, config) }
14
15
  let(:check) { FPM::Cookery::SourceIntegrityCheck.new(recipe) }
15
16
 
16
17
  before do