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
@@ -0,0 +1 @@
1
+ post_install: '%{scope("workdir")}/fix_ldconfig.sh'
@@ -0,0 +1,12 @@
1
+ name: fake_package
2
+ description: >
3
+ For testing purposes on %{scope("platform")} only - you've been warned ;)
4
+ version: 1.0.2
5
+ source:
6
+ - http://www.dummy-url.com/archive/%{hiera("name")}-%{hiera("version")}.tar.gz
7
+ - :with: :git
8
+
9
+ environment:
10
+ PREFIX: '/opt'
11
+
12
+ post_install: '%{scope("workdir")}/default.sh'
@@ -0,0 +1,3 @@
1
+ environment:
2
+ PREFIX: '/usr/local'
3
+ AUTOMATED_TESTING: 1
@@ -0,0 +1,12 @@
1
+ depends:
2
+ - on
3
+ - some
4
+ - rhel
5
+ - specific
6
+ - packages
7
+
8
+ build_depends:
9
+ - on
10
+ - having
11
+ - devel-prefixed
12
+ - libraries
@@ -0,0 +1,158 @@
1
+ require 'spec_helper'
2
+ require 'fpm/cookery/book'
3
+ require 'fpm/cookery/book_hook'
4
+ require 'fpm/cookery/facts'
5
+ require 'fpm/cookery/hiera'
6
+ require 'fpm/cookery/recipe'
7
+ require 'hiera/fpm_cookery_logger'
8
+ require 'facter'
9
+
10
+ describe 'Hiera' do
11
+ describe 'Defaults' do
12
+ subject { FPM::Cookery::Hiera::Defaults }
13
+
14
+ describe '.hiera_logger' do
15
+ it 'provides a default logger key for Hiera' do
16
+ expect(subject.hiera_logger).to eq 'fpm_cookery'
17
+ end
18
+ end
19
+
20
+ describe '.hiera_hierarchy' do
21
+ it 'provides a default lookup hierarchy for Hiera' do
22
+ expect(subject.hiera_hierarchy).to contain_exactly('common')
23
+ end
24
+ end
25
+
26
+ describe '.hiera_backends' do
27
+ it 'provides a default backend list for Hiera' do
28
+ expect(subject.hiera_backends).to contain_exactly(:yaml, :json)
29
+ end
30
+ end
31
+ end
32
+
33
+ describe 'Fpm_cookery_logger' do
34
+ it 'aliases Hiera::Fpm_cookery_logger to FPM::Cookery::Log::Hiera' do
35
+ expect(Hiera.const_get("Fpm_cookery_logger")).to be (FPM::Cookery::Log::Hiera)
36
+ end
37
+ end
38
+
39
+ describe 'Scope' do
40
+ let(:scope) { FPM::Cookery::Hiera::Scope.new(recipe) }
41
+ include_context "recipe class", __FILE__, :platform => nil, :hiera_config => nil,
42
+ :data_dir => fixture_path('hiera_config')
43
+
44
+ describe '[]' do
45
+ context 'given an argument corresponding to a recipe class method' do
46
+ it 'returns the value returned by the class method' do
47
+ expect(scope['workdir']).to eq(recipe.workdir)
48
+ end
49
+ end
50
+
51
+ context 'given an argument corresponding to an FPM::Cookery::Fact class method' do
52
+ it 'returns the value returned by the class method' do
53
+ expect(scope['osmajorrelease']).to eq(FPM::Cookery::Facts.osmajorrelease)
54
+ end
55
+ end
56
+
57
+ context 'given an otherwise unresolvable argument' do
58
+ it 'consults Facter' do
59
+ expect(scope['processorcount']).to eq(Facter['processorcount'].value)
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '#lookup' do
66
+ include_context "recipe class", __FILE__, :platform => nil, :hiera_config => nil,
67
+ :data_dir => fixture_path('hiera_config')
68
+
69
+ context "given default options and unset `platform' fact" do
70
+ it "returns values from `common.yaml' only" do
71
+ expect(recipe.lookup('environment')).to eq('PREFIX' => '/opt')
72
+ expect(recipe.lookup('version')).to eq('1.0.2')
73
+ expect(recipe.lookup('post_install')).to eq((recipe.workdir / 'default.sh').to_s)
74
+ end
75
+ end
76
+
77
+ context 'given a platform' do
78
+ before(:each) { allow(config).to receive(:platform).and_return('CentOS') }
79
+
80
+ it "prefers values from `\#{platform}.yaml'" do
81
+ expect(recipe.lookup('post_install')).to eq((recipe.workdir / 'fix_ldconfig.sh').to_s)
82
+ end
83
+ end
84
+
85
+ context 'given a value for ENV["FPM_HIERARCHY"]' do
86
+ around(:each) do |example|
87
+ ENV['FPM_HIERARCHY'] = env
88
+ example.run
89
+ ENV.delete('FPM_HIERARCHY')
90
+ end
91
+
92
+ context 'that does not correspond to an existing data file' do
93
+ let(:env) { 'fake' }
94
+
95
+ it 'uses data from the default data file' do
96
+ expect(recipe.lookup('environment')).to eq('PREFIX' => '/opt')
97
+ end
98
+ end
99
+
100
+ context 'that corresponds to an existing data file' do
101
+ let(:env) { 'custom' }
102
+
103
+ it 'prefers data from that file' do
104
+ expect(recipe.lookup('environment')).to eq('PREFIX' => '/usr/local', 'AUTOMATED_TESTING' => 1)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ describe "#new" do
111
+ context "given a string as the option to `:config'" do
112
+ let(:hiera) { FPM::Cookery::Hiera::Instance.new(recipe, :config => filename) }
113
+
114
+ let(:recipe) do
115
+ double('Recipe').as_null_object
116
+ end
117
+
118
+ context "that does not refer to an existing hiera.yaml file" do
119
+ let(:filename) { "/probably/does/not/exist/pretty/sure/anyway" }
120
+
121
+ it "raises an error when Hiera config file does not exist" do
122
+ expect { hiera }.to raise_error(RuntimeError)
123
+ end
124
+ end
125
+
126
+ context "that refers to an existing hiera.yaml file" do
127
+ let(:filename) { 'hiera.yaml' }
128
+
129
+ around(:each) do |example|
130
+ Dir.mktmpdir do |dir|
131
+ Dir.chdir(dir) do
132
+ File.open(filename, 'w') do |config_file|
133
+ config_file.print <<-CONFIG_FILE
134
+ :backends:
135
+ - json
136
+ :json:
137
+ :datadir: '/hey/i/am/a/datadir'
138
+ :hierarchy:
139
+ - yakko
140
+ - wakko
141
+ - dot
142
+ CONFIG_FILE
143
+ end
144
+
145
+ example.run
146
+ end
147
+ end
148
+ end
149
+
150
+ it "loads settings from that file" do
151
+ expect(hiera.config[:backends]).to contain_exactly('json')
152
+ expect(hiera.config[:json]).to eq(:datadir => '/hey/i/am/a/datadir')
153
+ expect(hiera.config[:hierarchy]).to contain_exactly(*%w{yakko wakko dot})
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,202 @@
1
+ require 'spec_helper'
2
+ require 'fpm/cookery/inheritable_attr'
3
+ require 'fpm/cookery/path'
4
+
5
+ def dsl_klass(name = nil)
6
+ (klass = Class.new).send(:extend, FPM::Cookery::InheritableAttr)
7
+ klass.class_eval(&Proc.new) if block_given?
8
+ klass
9
+ end
10
+
11
+ shared_examples 'attribute registration' do |attr_method, registry_method|
12
+ context "given an attribute created with .#{attr_method}" do
13
+ it "registers the attribute in the .#{registry_method} list" do
14
+ klass = Class.new do
15
+ extend FPM::Cookery::InheritableAttr
16
+
17
+ instance_eval %Q{
18
+ #{attr_method} :dummy_attr
19
+ }
20
+ end
21
+
22
+ expect(klass).to respond_to(registry_method)
23
+ expect(klass.send(registry_method)).to include(:dummy_attr)
24
+ end
25
+ end
26
+ end
27
+
28
+ shared_context 'class inheritance' do
29
+ let(:superklass) { dsl_klass }
30
+ let(:subklass) { Class.new(superklass) }
31
+ end
32
+
33
+ shared_examples 'attribute inheritance' do |attr_method, default_value, attr_name = :dummy_attr|
34
+ # A default implementation. Useful for +.attr_rw+, but should probably be
35
+ # overridden for the other DSL methods.
36
+ let(:attr_setter) {
37
+ Class.new do
38
+ def self.call(k, m, v)
39
+ k.send(m, v)
40
+ end
41
+ end
42
+ }
43
+
44
+ describe 'created attribute' do
45
+ it 'is defined on class' do
46
+ expect(superklass).to respond_to(attr_name)
47
+ end
48
+
49
+ it "is #{default_value.inspect} by default" do
50
+ expect(superklass.send(attr_name)).to eq(default_value)
51
+ end
52
+
53
+ it 'sets the attribute' do
54
+ attr_setter.(superklass, attr_name, value)
55
+ expect(superklass.send(attr_name)).to eq value
56
+ end
57
+ end
58
+
59
+ describe 'child class' do
60
+ describe 'inherited attribute' do
61
+ it 'inherits its value from the superclass' do
62
+ attr_setter.(superklass, attr_name, value)
63
+ expect(subklass.send(attr_name)).to eq value
64
+ end
65
+
66
+ context 'when altered' do
67
+ it 'does not propagate to the superclass' do
68
+ attr_setter.(superklass, attr_name, value)
69
+ attr_setter.(subklass, attr_name, child_value)
70
+ expect(superklass.send(attr_name)).to eq value
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ shared_context 'inheritable attributes' do |attr_method, default_value, attr_name = :dummy_attr|
78
+ include_context 'class inheritance'
79
+ include_examples 'attribute inheritance', attr_method, default_value
80
+
81
+ before(:example) do
82
+ missing = [:value, :child_value].reject { |m| respond_to?(m) }
83
+ raise "Missing required methods: #{missing.join(', ')}" unless missing.empty?
84
+
85
+ superklass.instance_eval do
86
+ send(attr_method, attr_name)
87
+ end
88
+ end
89
+ end
90
+
91
+ describe FPM::Cookery::InheritableAttr do
92
+ describe '.register_attrs' do
93
+ describe '.attr_rw' do
94
+ include_examples 'attribute registration', 'attr_rw', 'scalar_attrs'
95
+ end
96
+
97
+ describe '.attr_rw_list' do
98
+ include_examples 'attribute registration', 'attr_rw_list', 'list_attrs'
99
+ end
100
+
101
+ describe '.attr_rw_hash' do
102
+ include_examples 'attribute registration', 'attr_rw_hash', 'hash_attrs'
103
+ end
104
+
105
+ describe '.attr_rw_path' do
106
+ include_examples 'attribute registration', 'attr_rw_path', 'path_attrs'
107
+ end
108
+ end
109
+
110
+ describe '.attr_rw' do
111
+ include_context 'inheritable attributes', 'attr_rw', nil do
112
+ let(:value) { 'that' }
113
+ let(:child_value) { 'the other' }
114
+ end
115
+ end
116
+
117
+ describe '.attr_rw_list' do
118
+ include_context 'inheritable attributes', 'attr_rw_list', [] do
119
+ let(:attr_setter) {
120
+ Class.new do
121
+ def self.call(k, m, v)
122
+ k.send(m, *v)
123
+ end
124
+ end
125
+ }
126
+
127
+ let(:value) { %w{so la ti do} }
128
+ let(:child_value) { %w{fee fi fo fum} }
129
+ end
130
+ end
131
+
132
+ describe '.attr_rw_hash' do
133
+ include_context 'inheritable attributes', 'attr_rw_hash', {} do
134
+ let(:value) { {:name => 'mike', :rank => 'colonel' } }
135
+ let(:child_value) { {:name => 'mike', :favorite_color => 'puce' } }
136
+ end
137
+
138
+ describe 'created attribute' do
139
+ before do
140
+ superklass.instance_eval do
141
+ attr_rw_hash :metadata
142
+ end
143
+
144
+ superklass.metadata({ :radius => 4.172, :weight => 4 })
145
+ end
146
+
147
+ it 'merges its argument into the existing attribute value' do
148
+ superklass.metadata({ :weight => 7, :height => 12.3 })
149
+ expect(superklass.metadata).to eq({
150
+ :height => 12.3,
151
+ :weight => 7,
152
+ :radius => 4.172
153
+ })
154
+ end
155
+
156
+ it 'supports []= assignment' do
157
+ expect { superklass.metadata[:age] = 10000 }.not_to raise_error
158
+ expect(superklass.metadata).to include(:age => 10000)
159
+ end
160
+
161
+ context 'from child class' do
162
+ it 'does not propagate changes to the parent class' do
163
+ superklass.metadata[:tags] = %w{a b c}
164
+ subklass.metadata[:tags] << 'd'
165
+ expect(superklass.metadata[:tags]).not_to include('d')
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ describe '.attr_rw_path' do
172
+ include_context 'inheritable attributes', 'attr_rw_path', nil do
173
+ let(:attr_setter) {
174
+ Class.new do
175
+ def self.call(k, m, v)
176
+ k.send(:"#{m}=", v)
177
+ end
178
+ end
179
+ }
180
+
181
+ let(:value) { FPM::Cookery::Path.new('/var/spool') }
182
+ let(:child_value) { FPM::Cookery::Path.new('/proc/self') }
183
+ end
184
+
185
+ it 'returns an FPM::Cookery::Path object' do
186
+ superklass.attr_rw_path :home
187
+ superklass.home = "/where/the/heart/is"
188
+ expect(superklass.home).to be_an(FPM::Cookery::Path)
189
+ end
190
+ end
191
+
192
+ describe '.inherit_for' do
193
+ include_context 'class inheritance'
194
+
195
+ context 'given a method name that the superclass does not respond to' do
196
+ it 'returns nil' do
197
+ expect(superklass).not_to respond_to(:blech)
198
+ expect(FPM::Cookery::InheritableAttr.inherit_for(subklass, :blech)).to be nil
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'fpm/cookery/package/dir'
3
+ require 'fpm/cookery/recipe'
4
+
5
+ describe FPM::Cookery::Package::Dir do
6
+ let(:config) { { :input => input } }
7
+ let(:input) { %w{foo bar baz} }
8
+
9
+ let(:recipe) do
10
+ Class.new(FPM::Cookery::Recipe) do
11
+ description 'a test package'
12
+ name 'boo'
13
+ version '8.5'
14
+ end
15
+ end
16
+
17
+ let(:package) do
18
+ described_class.new(recipe, config)
19
+ end
20
+
21
+ let(:fpm) do
22
+ double('FPM').as_null_object
23
+ end
24
+
25
+ before do
26
+ allow(recipe).to receive(:config).and_return(double('Config').as_null_object)
27
+ allow(FPM::Package::Dir).to receive(:new).and_return(fpm)
28
+ end
29
+
30
+ context '#package_input' do
31
+ it 'calls the fpm package creation with a clean environment' do
32
+ expect(fpm).to receive(:input).exactly(input.length).times
33
+
34
+ package # Trigger object creation and package_input call.
35
+ end
36
+ end
37
+ end
@@ -58,7 +58,10 @@ describe 'Maintainer' do
58
58
 
59
59
  context 'without valid git data' do
60
60
  before do
61
- allow(FPM::Cookery::Shellout).to receive(:git_config_get).and_return(nil)
61
+ allow(FPM::Cookery::Shellout).to receive(:git_config_get).and_raise(
62
+ FPM::Cookery::Shellout::CommandFailed, 'whoops!'
63
+ )
64
+
62
65
  allow(Socket).to receive(:gethostname).and_return('hostname')
63
66
  end
64
67
 
@@ -140,4 +140,54 @@ describe 'Version' do
140
140
  expect(version.to_str).to eq('1.3-1.foo')
141
141
  end
142
142
  end
143
+
144
+ describe '#version' do
145
+ context 'given a colon-delimited string in recipe.version' do
146
+ context 'where version and epoch are defined' do
147
+ before(:each) { recipe.version = '8675309:3.1.1'}
148
+
149
+ it 'returns the version and epoch' do
150
+ expect(version.version).to eq('3.1.1')
151
+ expect(version.epoch).to eq('8675309')
152
+ end
153
+ end
154
+
155
+ context 'where only version is defined' do
156
+ before(:each) { recipe.version = ':2.1' }
157
+
158
+ it 'returns the version and sets epoch to nil' do
159
+ expect(version.version).to eq('2.1')
160
+ expect(version.epoch).to be nil
161
+ end
162
+ end
163
+
164
+ context 'where only epoch is defined' do
165
+ before(:each) { recipe.version = '12345:' }
166
+
167
+ it 'returns the epoch as version and sets epoch to nil' do
168
+ expect(version.version).to eq('12345')
169
+ expect(version.epoch).to be nil
170
+ end
171
+ end
172
+
173
+ end
174
+
175
+ context 'given a nil recipe.version' do
176
+ before(:each) { recipe.version = nil }
177
+
178
+ it 'returns the default version' do
179
+ expect(version.version).to eq(klass::DEFAULT_VERSION)
180
+ expect(version.epoch).to be nil
181
+ end
182
+ end
183
+
184
+ context 'given a recipe.version containing the empty string' do
185
+ before(:each) { recipe.version = '' }
186
+
187
+ it 'returns the default version' do
188
+ expect(version.version).to eq(klass::DEFAULT_VERSION)
189
+ expect(version.epoch).to be nil
190
+ end
191
+ end
192
+ end
143
193
  end