stemcell 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +6 -0
  3. data/Gemfile.lock +18 -0
  4. data/lib/stemcell/command_line.rb +2 -2
  5. data/lib/stemcell/errors.rb +3 -2
  6. data/lib/stemcell/metadata_source/chef_repository.rb +55 -0
  7. data/lib/stemcell/metadata_source/configuration.rb +77 -0
  8. data/lib/stemcell/metadata_source.rb +51 -98
  9. data/lib/stemcell/version.rb +1 -1
  10. data/lib/stemcell.rb +0 -2
  11. data/spec/fixtures/chef_repo/roles/unit-inherit-base.rb +20 -0
  12. data/spec/fixtures/chef_repo/roles/unit-inherit-both.rb +36 -0
  13. data/spec/fixtures/chef_repo/roles/unit-inherit-default.rb +21 -0
  14. data/spec/fixtures/chef_repo/roles/unit-inherit-none.rb +6 -0
  15. data/spec/fixtures/chef_repo/roles/unit-inherit-override.rb +21 -0
  16. data/spec/fixtures/chef_repo/roles/unit-simple-both.rb +29 -0
  17. data/spec/fixtures/chef_repo/roles/unit-simple-default.rb +16 -0
  18. data/spec/fixtures/chef_repo/roles/unit-simple-none.rb +2 -0
  19. data/spec/fixtures/chef_repo/roles/unit-simple-override.rb +16 -0
  20. data/spec/fixtures/chef_repo/roles-expected-metadata/unit-inherit-both.json +16 -0
  21. data/spec/fixtures/chef_repo/roles-expected-metadata/unit-inherit-default.json +14 -0
  22. data/spec/fixtures/chef_repo/roles-expected-metadata/unit-inherit-none.json +11 -0
  23. data/spec/fixtures/chef_repo/roles-expected-metadata/unit-inherit-override.json +14 -0
  24. data/spec/fixtures/chef_repo/roles-expected-metadata/unit-simple-both.json +13 -0
  25. data/spec/fixtures/chef_repo/roles-expected-metadata/unit-simple-default.json +11 -0
  26. data/spec/fixtures/chef_repo/roles-expected-metadata/unit-simple-override.json +11 -0
  27. data/spec/fixtures/chef_repo/stemcell-azs-missing.json +10 -0
  28. data/spec/fixtures/chef_repo/stemcell-backing-store-empty.json +9 -0
  29. data/spec/fixtures/chef_repo/stemcell-backing-store-missing.json +8 -0
  30. data/spec/fixtures/chef_repo/stemcell-defaults-missing.json +10 -0
  31. data/spec/fixtures/chef_repo/stemcell.json +13 -0
  32. data/spec/lib/stemcell/metadata_source/chef_repository_spec.rb +107 -0
  33. data/spec/lib/stemcell/metadata_source/configuration_spec.rb +117 -0
  34. data/spec/lib/stemcell/metadata_source_spec.rb +250 -0
  35. data/spec/spec_helper.rb +13 -0
  36. data/spec/support/fixture_helper.rb +14 -0
  37. metadata +60 -24
  38. data/lib/stemcell/utility/deep_merge.rb +0 -13
  39. data/lib/stemcell/utility.rb +0 -1
@@ -0,0 +1,13 @@
1
+ {
2
+ "instance_type": "m3.xlarge",
3
+ "security_groups": [
4
+ "all",
5
+ "default",
6
+ "override"
7
+ ],
8
+ "tags": {
9
+ "tag1": "tag1_value_override",
10
+ "tag2": "tag2_value",
11
+ "tag3": "tag3_value"
12
+ }
13
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "instance_type": "c1.xlarge",
3
+ "security_groups": [
4
+ "all",
5
+ "default"
6
+ ],
7
+ "tags": {
8
+ "tag1": "tag1_value_default",
9
+ "tag2": "tag2_value"
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "instance_type": "m3.xlarge",
3
+ "security_groups": [
4
+ "all",
5
+ "override"
6
+ ],
7
+ "tags": {
8
+ "tag1": "tag1_value_override",
9
+ "tag3": "tag3_value"
10
+ }
11
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "defaults": {
3
+ "instance_type": "m1.small"
4
+ },
5
+ "backing_store": {
6
+ "instance_store": {
7
+ "image_id": "ami-d9d6a6b0"
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "defaults": {
3
+ "instance_type": "m1.small"
4
+ },
5
+ "backing_store": {},
6
+ "availability_zones": {
7
+ "us-east-1": ["us-east-1a"]
8
+ }
9
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "defaults": {
3
+ "instance_type": "m1.small"
4
+ },
5
+ "availability_zones": {
6
+ "us-east-1": ["us-east-1a"]
7
+ }
8
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "backing_store": {
3
+ "instance_store": {
4
+ "baz": "woo"
5
+ }
6
+ },
7
+ "availability_zones": {
8
+ "us-east-1": ["us-east-1a"]
9
+ }
10
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "defaults": {
3
+ "instance_type": "m1.small"
4
+ },
5
+ "backing_store": {
6
+ "instance_store": {
7
+ "image_id": "ami-d9d6a6b0"
8
+ }
9
+ },
10
+ "availability_zones": {
11
+ "us-east-1": ["us-east-1a"]
12
+ }
13
+ }
@@ -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
@@ -0,0 +1,13 @@
1
+ require 'rspec/instafail'
2
+ require 'simplecov'
3
+
4
+ require 'stemcell'
5
+ require 'support/fixture_helper'
6
+
7
+ SimpleCov.start do
8
+ add_filter 'spec'
9
+ end
10
+
11
+ RSpec.configure do |config|
12
+ config.color_enabled = true
13
+ end
@@ -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