stemcell 0.5.0 → 0.6.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.
- 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
|