stemcell 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +18 -0
- data/lib/stemcell/command_line.rb +2 -2
- data/lib/stemcell/errors.rb +3 -2
- data/lib/stemcell/metadata_source/chef_repository.rb +55 -0
- data/lib/stemcell/metadata_source/configuration.rb +77 -0
- data/lib/stemcell/metadata_source.rb +51 -98
- data/lib/stemcell/version.rb +1 -1
- data/lib/stemcell.rb +0 -2
- data/spec/fixtures/chef_repo/roles/unit-inherit-base.rb +20 -0
- data/spec/fixtures/chef_repo/roles/unit-inherit-both.rb +36 -0
- data/spec/fixtures/chef_repo/roles/unit-inherit-default.rb +21 -0
- data/spec/fixtures/chef_repo/roles/unit-inherit-none.rb +6 -0
- data/spec/fixtures/chef_repo/roles/unit-inherit-override.rb +21 -0
- data/spec/fixtures/chef_repo/roles/unit-simple-both.rb +29 -0
- data/spec/fixtures/chef_repo/roles/unit-simple-default.rb +16 -0
- data/spec/fixtures/chef_repo/roles/unit-simple-none.rb +2 -0
- data/spec/fixtures/chef_repo/roles/unit-simple-override.rb +16 -0
- data/spec/fixtures/chef_repo/roles-expected-metadata/unit-inherit-both.json +16 -0
- data/spec/fixtures/chef_repo/roles-expected-metadata/unit-inherit-default.json +14 -0
- data/spec/fixtures/chef_repo/roles-expected-metadata/unit-inherit-none.json +11 -0
- data/spec/fixtures/chef_repo/roles-expected-metadata/unit-inherit-override.json +14 -0
- data/spec/fixtures/chef_repo/roles-expected-metadata/unit-simple-both.json +13 -0
- data/spec/fixtures/chef_repo/roles-expected-metadata/unit-simple-default.json +11 -0
- data/spec/fixtures/chef_repo/roles-expected-metadata/unit-simple-override.json +11 -0
- data/spec/fixtures/chef_repo/stemcell-azs-missing.json +10 -0
- data/spec/fixtures/chef_repo/stemcell-backing-store-empty.json +9 -0
- data/spec/fixtures/chef_repo/stemcell-backing-store-missing.json +8 -0
- data/spec/fixtures/chef_repo/stemcell-defaults-missing.json +10 -0
- data/spec/fixtures/chef_repo/stemcell.json +13 -0
- data/spec/lib/stemcell/metadata_source/chef_repository_spec.rb +107 -0
- data/spec/lib/stemcell/metadata_source/configuration_spec.rb +117 -0
- data/spec/lib/stemcell/metadata_source_spec.rb +250 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/fixture_helper.rb +14 -0
- metadata +60 -24
- data/lib/stemcell/utility/deep_merge.rb +0 -13
- data/lib/stemcell/utility.rb +0 -1
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Stemcell::MetadataSource::ChefRepository do
|
4
|
+
|
5
|
+
let(:chef_root) { FixtureHelper.chef_repo_fixture_path }
|
6
|
+
let(:chef_repo) { Stemcell::MetadataSource::ChefRepository.new(chef_root) }
|
7
|
+
|
8
|
+
describe '#initialize' do
|
9
|
+
|
10
|
+
it "sets chef_root" do
|
11
|
+
expect(chef_repo.chef_root).to eql(chef_root)
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with a mocked chef root" do
|
15
|
+
let(:chef_root) { '/path/to/chef' }
|
16
|
+
|
17
|
+
it "configures the cookbooks path" do
|
18
|
+
chef_repo
|
19
|
+
expect(Chef::Config[:cookbook_path]).to eql('/path/to/chef/cookbooks')
|
20
|
+
end
|
21
|
+
it "configures the data_bags path" do
|
22
|
+
chef_repo
|
23
|
+
expect(Chef::Config[:data_bag_path]).to eql('/path/to/chef/data_bags')
|
24
|
+
end
|
25
|
+
it "configures the roles path" do
|
26
|
+
chef_repo
|
27
|
+
expect(Chef::Config[:role_path]).to eql('/path/to/chef/roles')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#metadata_for_role' do
|
34
|
+
|
35
|
+
let(:expected_metadata) { FixtureHelper.expected_metadata_for_role(role) }
|
36
|
+
let(:result_metadata) { chef_repo.metadata_for_role(role, environment) }
|
37
|
+
|
38
|
+
let(:environment) { 'production' }
|
39
|
+
let(:role) { nil }
|
40
|
+
|
41
|
+
context "for a role with no inheritence" do
|
42
|
+
|
43
|
+
context "and no attributes" do
|
44
|
+
let(:role) { 'unit-simple-none' }
|
45
|
+
it "returns nil" do
|
46
|
+
expect(result_metadata).to eql(nil)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "and default attributes" do
|
51
|
+
let(:role) { 'unit-simple-default' }
|
52
|
+
it "returns the expected metdata" do
|
53
|
+
expect(result_metadata).to eql(expected_metadata)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "and override attributes" do
|
58
|
+
let(:role) { 'unit-simple-override' }
|
59
|
+
it "returns the expected metdata" do
|
60
|
+
expect(result_metadata).to eql(expected_metadata)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "and both default and override attributes" do
|
65
|
+
let(:role) { 'unit-simple-both' }
|
66
|
+
it "returns the expected metdata" do
|
67
|
+
expect(result_metadata).to eql(expected_metadata)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
context "for a role with inheritence" do
|
74
|
+
|
75
|
+
context "and no attributes" do
|
76
|
+
let(:role) { 'unit-inherit-none' }
|
77
|
+
it "returns the expected metdata" do
|
78
|
+
expect(result_metadata).to eql(expected_metadata)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "and default attributes" do
|
83
|
+
let(:role) { 'unit-inherit-default' }
|
84
|
+
it "returns the expected metdata" do
|
85
|
+
expect(result_metadata).to eql(expected_metadata)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "and override attributes" do
|
90
|
+
let(:role) { 'unit-inherit-override' }
|
91
|
+
it "returns the expected metdata" do
|
92
|
+
expect(result_metadata).to eql(expected_metadata)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "and both default and override attributes" do
|
97
|
+
let(:role) { 'unit-inherit-both' }
|
98
|
+
it "returns the expected metdata" do
|
99
|
+
expect(result_metadata).to eql(expected_metadata)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Stemcell::MetadataSource::Configuration do
|
4
|
+
|
5
|
+
let(:config_filename) { 'stemcell.json' }
|
6
|
+
|
7
|
+
let(:chef_root) { FixtureHelper.chef_repo_fixture_path }
|
8
|
+
let(:path) { File.join(chef_root, config_filename) }
|
9
|
+
let(:config) { Stemcell::MetadataSource::Configuration.new(path) }
|
10
|
+
|
11
|
+
describe '#initialize' do
|
12
|
+
|
13
|
+
it "sets config_path" do
|
14
|
+
expect(config.config_path).to eql path
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets all_options" do
|
18
|
+
expect(config.all_options.keys).to eql([
|
19
|
+
'defaults',
|
20
|
+
'backing_store',
|
21
|
+
'availability_zones'
|
22
|
+
])
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when the required options are present" do
|
26
|
+
it "sets default_options" do
|
27
|
+
expect(config.default_options).to eql({
|
28
|
+
'instance_type' => 'm1.small'
|
29
|
+
})
|
30
|
+
end
|
31
|
+
|
32
|
+
it "sets backing_store_options" do
|
33
|
+
expect(config.backing_store_options).to eql({
|
34
|
+
'instance_store' => {
|
35
|
+
'image_id' => 'ami-d9d6a6b0'
|
36
|
+
}
|
37
|
+
})
|
38
|
+
end
|
39
|
+
|
40
|
+
it "sets availability_zones" do
|
41
|
+
expect(config.availability_zones).to eql({
|
42
|
+
'us-east-1' => ['us-east-1a']
|
43
|
+
})
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when defaults are not specified" do
|
48
|
+
let(:config_filename) { 'stemcell-defaults-missing.json' }
|
49
|
+
it "raises" do
|
50
|
+
expect { config }.to raise_error(Stemcell::MetadataConfigParseError)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when backing store options are not specified" do
|
55
|
+
let(:config_filename) { 'stemcell-backing-store-missing.json' }
|
56
|
+
it "raises" do
|
57
|
+
expect { config }.to raise_error(Stemcell::MetadataConfigParseError)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when availability zones are empty" do
|
62
|
+
let(:config_filename) { 'stemcell-backing-store-empty.json' }
|
63
|
+
it "raises" do
|
64
|
+
expect { config }.to raise_error(Stemcell::MetadataConfigParseError)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when availability zones are not specified" do
|
69
|
+
let(:config_filename) { 'stemcell-azs-missing.json' }
|
70
|
+
it "raises" do
|
71
|
+
expect { config }.to raise_error(Stemcell::MetadataConfigParseError)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#options_for_backing_store' do
|
78
|
+
let(:backing_store) { 'instance_store' }
|
79
|
+
|
80
|
+
context "when the backing store definition exists" do
|
81
|
+
it "returns the options" do
|
82
|
+
expect(config.options_for_backing_store(backing_store)).to eql({
|
83
|
+
'image_id' => 'ami-d9d6a6b0'
|
84
|
+
})
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when the backing store isn't defined" do
|
89
|
+
let(:backing_store) { 'nyanstore' }
|
90
|
+
it "raises" do
|
91
|
+
expect { config.options_for_backing_store(backing_store) }.to raise_error(
|
92
|
+
Stemcell::UnknownBackingStoreError
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#random_az_in_region' do
|
100
|
+
let(:region) { 'us-east-1' }
|
101
|
+
|
102
|
+
context "when availability zones are defined for the region" do
|
103
|
+
it "returns an az" do
|
104
|
+
expect(config.random_az_for_region(region)).to eql('us-east-1a')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "when availability zone aren't defined for the region" do
|
109
|
+
let(:region) { 'nyancat' }
|
110
|
+
it "returns nil" do
|
111
|
+
expect(config.random_az_for_region(region)).to be_nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Stemcell::MetadataSource do
|
4
|
+
|
5
|
+
let(:chef_root) { FixtureHelper.chef_repo_fixture_path }
|
6
|
+
let(:config_filename) { 'stemcell.json' }
|
7
|
+
|
8
|
+
let(:metadata_source) do
|
9
|
+
Stemcell::MetadataSource.new(chef_root, config_filename)
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:config) { metadata_source.config }
|
13
|
+
let(:chef_repo) { metadata_source.chef_repo }
|
14
|
+
|
15
|
+
describe '#initialize' do
|
16
|
+
|
17
|
+
context "when the arguments are valid" do
|
18
|
+
it "sets chef_root" do
|
19
|
+
expect(metadata_source.chef_root).to eql chef_root
|
20
|
+
end
|
21
|
+
it "sets config_filename" do
|
22
|
+
expect(metadata_source.config_filename).to eql config_filename
|
23
|
+
end
|
24
|
+
|
25
|
+
it "constructs a configuration object" do
|
26
|
+
expect(metadata_source.config).to be_an_instance_of(
|
27
|
+
Stemcell::MetadataSource::Configuration)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "uses the correct path for the configuration object" do
|
31
|
+
expect(metadata_source.config.config_path).to eql(
|
32
|
+
File.join(chef_root, config_filename))
|
33
|
+
end
|
34
|
+
|
35
|
+
it "constructs a chef repository object" do
|
36
|
+
expect(metadata_source.chef_repo).to be_an_instance_of(
|
37
|
+
Stemcell::MetadataSource::ChefRepository)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "uses the correct path for the chef repository object" do
|
41
|
+
expect(metadata_source.chef_repo.chef_root).to eql(chef_root)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when the chef root is nil" do
|
46
|
+
let(:chef_root) { nil }
|
47
|
+
it "raises an ArgumentError" do
|
48
|
+
expect { metadata_source }.to raise_error(ArgumentError)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "when the configuration file name is nil" do
|
53
|
+
let(:config_filename) { nil }
|
54
|
+
it "raise an ArgumentError" do
|
55
|
+
expect { metadata_source }.to raise_error(ArgumentError)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#expand_role' do
|
62
|
+
|
63
|
+
let(:default_options) { Hash.new }
|
64
|
+
let(:backing_options) { Hash.new }
|
65
|
+
let(:availability_zones) { Hash.new }
|
66
|
+
let(:role_metadata) { Hash.new }
|
67
|
+
let(:override_options) { Hash.new }
|
68
|
+
let(:expand_options) { Hash.new }
|
69
|
+
|
70
|
+
before do
|
71
|
+
config.stub(:default_options) { default_options }
|
72
|
+
config.stub(:availability_zones) { availability_zones }
|
73
|
+
config.stub(:options_for_backing_store) { backing_options }
|
74
|
+
chef_repo.stub(:metadata_for_role) { role_metadata }
|
75
|
+
end
|
76
|
+
|
77
|
+
let(:role) { 'role' }
|
78
|
+
let(:environment) { 'production' }
|
79
|
+
|
80
|
+
let(:expansion) do
|
81
|
+
metadata_source.expand_role(
|
82
|
+
role,
|
83
|
+
environment,
|
84
|
+
override_options,
|
85
|
+
expand_options)
|
86
|
+
end
|
87
|
+
|
88
|
+
context "when arguments are valid" do
|
89
|
+
|
90
|
+
before { role_metadata.merge!('instance_type' => 'c1.xlarge') }
|
91
|
+
|
92
|
+
describe "backing store" do
|
93
|
+
|
94
|
+
context "when backing store is not explicitly set" do
|
95
|
+
it "uses instance_store" do
|
96
|
+
expect(expansion['backing_store']).to eql 'instance_store'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when the override options specify a backing store" do
|
101
|
+
before { override_options.merge!('backing_store' => 'from_override') }
|
102
|
+
|
103
|
+
it "is the value in the override options" do
|
104
|
+
expect(expansion['backing_store']).to eql 'from_override'
|
105
|
+
end
|
106
|
+
|
107
|
+
it "overrides the backing store set in the role" do
|
108
|
+
role_metadata.merge!('backing_store' => 'from_role')
|
109
|
+
expect(expansion['backing_store']).to eql 'from_override'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when the role metadata specifies a backing store" do
|
114
|
+
before { role_metadata.merge!('backing_store' => 'from_role') }
|
115
|
+
|
116
|
+
it "is the value in the role metadata" do
|
117
|
+
expect(expansion['backing_store']).to eql 'from_role'
|
118
|
+
end
|
119
|
+
|
120
|
+
it "overrides the value given in the configuration" do
|
121
|
+
default_options.merge!('backing_store' => 'from_defaults')
|
122
|
+
expect(expansion['backing_store']).to eql 'from_role'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "when the default options specify a backing store" do
|
127
|
+
before { default_options.merge!('backing_store' => 'from_defaults') }
|
128
|
+
|
129
|
+
it "is the value in the default options" do
|
130
|
+
expect(expansion['backing_store']).to eql 'from_defaults'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
describe 'expansion' do
|
137
|
+
|
138
|
+
context "when no options are explicitly set" do
|
139
|
+
it "contains the defaults" do
|
140
|
+
# This assumes that the environment is set to the default
|
141
|
+
Stemcell::MetadataSource::DEFAULT_OPTIONS.each_pair do |key, value|
|
142
|
+
expect(expansion[key]).to eql value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "when the role and environment are not the default" do
|
148
|
+
let(:role) { 'not_default_role' }
|
149
|
+
let(:environment) { 'not_default_environmnet' }
|
150
|
+
|
151
|
+
it "contains the role" do
|
152
|
+
expect(expansion['chef_role']).to eql role
|
153
|
+
end
|
154
|
+
it "contains the environment" do
|
155
|
+
expect(expansion['chef_environment']).to eql environment
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
it "calls the config object to retrieve the backing store options" do
|
160
|
+
backing_options.merge!('image_id' => 'ami-nyancat')
|
161
|
+
override_options.merge!('backing_store' => 'ebs')
|
162
|
+
config.should_receive(:options_for_backing_store).with('ebs') { backing_options }
|
163
|
+
expect(expansion['image_id']).to eql 'ami-nyancat'
|
164
|
+
end
|
165
|
+
|
166
|
+
it "calls the repository object to determine the role metadata" do
|
167
|
+
role_metadata.merge!('image_id' => 'ami-nyancat')
|
168
|
+
chef_repo.should_receive(:metadata_for_role).with(role, environment) { role_metadata }
|
169
|
+
expect(expansion['image_id']).to eql 'ami-nyancat'
|
170
|
+
end
|
171
|
+
|
172
|
+
context "when a config default overrides a built-in default" do
|
173
|
+
before { default_options.merge!('git_branch' => 'from_default') }
|
174
|
+
|
175
|
+
it "returns the value from the config defaults" do
|
176
|
+
expect(expansion['git_branch']).to eql 'from_default'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "when role metadata overrides a config default" do
|
181
|
+
before { default_options.merge!('option' => 'from_default') }
|
182
|
+
before { role_metadata.merge!('option' => 'from_role') }
|
183
|
+
|
184
|
+
it "returns the value from the role metadata" do
|
185
|
+
expect(expansion['option']).to eql 'from_role'
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "when an override option overrides the role metadata" do
|
190
|
+
before { role_metadata.merge!('option' => 'from_role') }
|
191
|
+
before { override_options.merge!('option' => 'from_override') }
|
192
|
+
|
193
|
+
it "returns the value from the override options" do
|
194
|
+
expect(expansion['option']).to eql 'from_override'
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context "when a region was specified but no availability zone" do
|
199
|
+
|
200
|
+
let(:availability_zones) { { 'us-east-1' => ['us-east-1a'] } }
|
201
|
+
|
202
|
+
before do
|
203
|
+
override_options.merge!('region' => 'us-east-1')
|
204
|
+
override_options.merge!('availability_zone' => nil)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "substitutes an availability zone from the config" do
|
208
|
+
expect(expansion['availability_zone']).to eql 'us-east-1a'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
context "when the role metadata isn't present" do
|
217
|
+
let(:role_metadata) { nil }
|
218
|
+
|
219
|
+
context "when allowing empty roles" do
|
220
|
+
before { expand_options[:allow_empty_roles] = true }
|
221
|
+
it "returns successfully" do
|
222
|
+
expect(expansion).to_not be_nil
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
context "when not allowing empty roles" do
|
227
|
+
before { expand_options[:allow_empty_roles] = false }
|
228
|
+
it "raises" do
|
229
|
+
expect { expansion }.to raise_error(Stemcell::EmptyRoleError)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
context "when role is nil" do
|
235
|
+
let(:role) { nil }
|
236
|
+
it "raises" do
|
237
|
+
expect { expansion }.to raise_error(ArgumentError)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
context "when environment is nil" do
|
242
|
+
let(:environment) { nil }
|
243
|
+
it "raises" do
|
244
|
+
expect { expansion }.to raise_error(ArgumentError)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module FixtureHelper
|
2
|
+
def self.chef_repo_fixture_path
|
3
|
+
File.expand_path("../../fixtures/chef_repo", __FILE__)
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.expected_metadata_path
|
7
|
+
File.join(chef_repo_fixture_path, 'roles-expected-metadata')
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.expected_metadata_for_role(role)
|
11
|
+
fixture_path = File.join(expected_metadata_path, "#{role}.json")
|
12
|
+
JSON.parse(File.read(fixture_path))
|
13
|
+
end
|
14
|
+
end
|