aerosol 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.cane +3 -0
  3. data/.gitignore +15 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.md +399 -0
  9. data/Rakefile +18 -0
  10. data/aerosol.gemspec +32 -0
  11. data/bin/aerosol +8 -0
  12. data/img/aerosol.pdf +3898 -11
  13. data/img/aerosol.png +0 -0
  14. data/lib/aerosol/auto_scaling.rb +240 -0
  15. data/lib/aerosol/aws.rb +62 -0
  16. data/lib/aerosol/aws_model.rb +93 -0
  17. data/lib/aerosol/cli.rb +41 -0
  18. data/lib/aerosol/connection.rb +39 -0
  19. data/lib/aerosol/deploy.rb +105 -0
  20. data/lib/aerosol/env.rb +6 -0
  21. data/lib/aerosol/instance.rb +55 -0
  22. data/lib/aerosol/launch_configuration.rb +106 -0
  23. data/lib/aerosol/rake_task.rb +141 -0
  24. data/lib/aerosol/runner.rb +329 -0
  25. data/lib/aerosol/util.rb +41 -0
  26. data/lib/aerosol/version.rb +5 -0
  27. data/lib/aerosol.rb +83 -0
  28. data/spec/aerosol/auto_scaling_spec.rb +420 -0
  29. data/spec/aerosol/aws_spec.rb +24 -0
  30. data/spec/aerosol/cli_spec.rb +10 -0
  31. data/spec/aerosol/connection_spec.rb +94 -0
  32. data/spec/aerosol/deploy_spec.rb +192 -0
  33. data/spec/aerosol/env_spec.rb +16 -0
  34. data/spec/aerosol/instance_spec.rb +57 -0
  35. data/spec/aerosol/launch_configuration_spec.rb +328 -0
  36. data/spec/aerosol/rake_task_spec.rb +19 -0
  37. data/spec/aerosol/runner_spec.rb +482 -0
  38. data/spec/aerosol_spec.rb +41 -0
  39. data/spec/fixtures/Procfile +1 -0
  40. data/spec/fixtures/Rakefile +17 -0
  41. data/spec/fixtures/not_a_tar-2.txt +1 -0
  42. data/spec/fixtures/not_a_tar.txt +1 -0
  43. data/spec/fixtures/tar-2.tar +0 -0
  44. data/spec/fixtures/test-1.tar +0 -0
  45. data/spec/fixtures/test-2.tar.gz +0 -0
  46. data/spec/spec_helper.rb +22 -0
  47. data/spec/support/vcr.rb +11 -0
  48. data/spec/vcr/Deployz_Docker/_fetch_import/when_both_import_and_name_are_present_present/and_it_points_to_a_non-S3_url/pulls_the_file.yml +214 -0
  49. metadata +312 -0
@@ -0,0 +1,420 @@
1
+ require 'spec_helper'
2
+ require 'fog/aws'
3
+
4
+ describe Aerosol::AutoScaling do
5
+ let!(:launch_config) do
6
+ Aerosol::LaunchConfiguration.new! do
7
+ name :my_launch_config_for_auto_scaling
8
+ ami 'ami :) :) :)'
9
+ instance_type 'm1.large'
10
+ stub(:sleep)
11
+ end.tap(&:create)
12
+ end
13
+
14
+ subject { described_class.new! }
15
+ before { subject.stub(:sleep) }
16
+
17
+ describe "#aws_identifier" do
18
+ before do
19
+ subject.instance_eval do
20
+ name :my_auto_scaling
21
+ end
22
+ end
23
+
24
+ context "with no namespace set" do
25
+ let(:identifier) { "my_auto_scaling-#{Aerosol::Util.git_sha}" }
26
+ it "returns a normal identifier" do
27
+ expect(subject.aws_identifier).to eq(identifier)
28
+ end
29
+ end
30
+
31
+ context "with a namespace set" do
32
+ let(:namespace) { "test" }
33
+ let(:identifier) { "#{namespace}-my_auto_scaling-#{Aerosol::Util.git_sha}" }
34
+
35
+ before { Aerosol.namespace namespace }
36
+ after { Aerosol.instance_variable_set(:"@namespace", nil) }
37
+
38
+ it "returns a namespaced identifier" do
39
+ expect(subject.aws_identifier).to eq(identifier)
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '#create!' do
45
+ context 'when none of the required options are set' do
46
+ it 'raises an error' do
47
+ expect { subject.create! }.to raise_error
48
+ end
49
+ end
50
+
51
+ context 'when some of the required options are set' do
52
+ before { subject.max_size 101 }
53
+
54
+ it 'raises an error' do
55
+ expect { subject.create! }.to raise_error
56
+ end
57
+ end
58
+
59
+ context 'when all of the required options are set' do
60
+ let!(:launch_configuration) do
61
+ Aerosol::LaunchConfiguration.new! do
62
+ ami 'fake_ami'
63
+ instance_type 'm1.large'
64
+ stub(:sleep)
65
+ end
66
+ end
67
+ let(:availability_zone) { 'US' }
68
+ let(:min_size) { 1 }
69
+ let(:max_size) { 10 }
70
+ let(:options) {
71
+ { :name => :my_group,
72
+ :launch_configuration => launch_configuration.name,
73
+ :availability_zones => [availability_zone],
74
+ :min_size => 1,
75
+ :max_size => 10,
76
+ :vpc_zone_identifier => 'subnet-deadbeef,subnet-00112233'
77
+ }
78
+ }
79
+
80
+ subject { Aerosol::AutoScaling.new!(options) }
81
+ before { subject.tag :my_group => '1' }
82
+
83
+ context 'when the launch configuration is not known' do
84
+ before { subject.instance_variable_set(:@launch_configuration, nil) }
85
+ it 'raises an error' do
86
+ expect { subject.create! }.to raise_error
87
+ end
88
+ end
89
+
90
+ context 'when the launch configuration is known' do
91
+ before do
92
+ launch_configuration.create!
93
+ end
94
+
95
+ it 'creates an auto-scaling group' do
96
+ expect(subject.tags).to include('Deploy' => 'my_group')
97
+ expect { subject.create! }
98
+ .to change { subject.send(:conn).data[:auto_scaling_groups][subject.aws_identifier].class.to_s }
99
+ .from('NilClass')
100
+ .to('Hash')
101
+ end
102
+
103
+ context "when there is a namespace" do
104
+ subject do
105
+ Aerosol.namespace "tags"
106
+ Aerosol::AutoScaling.new!(options)
107
+ end
108
+
109
+ after { Aerosol.instance_variable_set(:"@namespace", nil) }
110
+
111
+ it "includes the namespace" do
112
+ expect(subject.tags).to include('Deploy' => 'tags-my_group')
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ describe '#destroy!' do
120
+ let(:aws_identifier) { subject.aws_identifier }
121
+ subject { Aerosol::AutoScaling.new }
122
+
123
+ context 'when there is no such auto-scaling group' do
124
+ it 'raises an error' do
125
+ expect { subject.destroy! }
126
+ .to raise_error(Fog::AWS::AutoScaling::ValidationError)
127
+ end
128
+ end
129
+
130
+ context 'when the auto-scaling group exists' do
131
+ before { subject.send(:conn).data[:auto_scaling_groups][aws_identifier] = aws_identifier }
132
+
133
+ it 'deletes the auto-scaling group' do
134
+ expect { subject.destroy! }
135
+ .to change { subject.send(:conn).data[:auto_scaling_groups][aws_identifier] }
136
+ .from(aws_identifier)
137
+ .to(nil)
138
+ end
139
+ end
140
+ end
141
+
142
+ describe '#create' do
143
+ context 'when the aws_identifier is nil' do
144
+ subject { described_class.new!(:name => 'nonsense') }
145
+
146
+ it 'raises an error' do
147
+ expect { subject.create }.to raise_error
148
+ end
149
+ end
150
+
151
+ context 'when the aws_identifier is present' do
152
+ subject { described_class.new!(:name => 'nonsense2') }
153
+
154
+ context 'when the model already exists' do
155
+ before { described_class.stub(:exists?).and_return(true) }
156
+
157
+ it 'does not create it' do
158
+ subject.should_not_receive(:create!)
159
+ subject.create
160
+ end
161
+ end
162
+
163
+ context 'when the model does not already exist' do
164
+ before { described_class.stub(:exists?).and_return(false) }
165
+
166
+ it 'creates it' do
167
+ subject.should_receive(:create!)
168
+ subject.create
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ describe '#destroy' do
175
+ subject { described_class.new!(:name => 'nonsense2') }
176
+
177
+ context 'when the model already exists' do
178
+ before { described_class.stub(:exists?).and_return(true) }
179
+
180
+ it 'destroys it' do
181
+ subject.should_receive(:destroy!)
182
+ subject.destroy
183
+ end
184
+ end
185
+
186
+ context 'when the model does not exist' do
187
+ before { described_class.stub(:exists?).and_return(false) }
188
+
189
+ it 'does not destroy it' do
190
+ subject.should_not_receive(:destroy!)
191
+ subject.destroy
192
+ end
193
+ end
194
+ end
195
+
196
+ describe '.exists?' do
197
+ subject { described_class }
198
+
199
+ context 'when the argument exists' do
200
+ let!(:existing) {
201
+ conf = launch_config
202
+ subject.new! do
203
+ min_size 1
204
+ max_size 3
205
+ availability_zones 'us-east-1'
206
+ launch_configuration conf.name
207
+ stub(:sleep)
208
+ end.tap(&:create)
209
+ }
210
+
211
+ it 'returns true' do
212
+ subject.exists?(existing.aws_identifier).should be_true
213
+ end
214
+ end
215
+
216
+ context 'when the argument does not exist' do
217
+ before do
218
+ described_class.new! do
219
+ name :exists_test_name
220
+ aws_identifier 'does-not-exist'
221
+ stub(:sleep)
222
+ end.destroy! rescue nil
223
+ end
224
+
225
+ it 'returns false' do
226
+ subject.exists?('does-not-exist').should be_false
227
+ end
228
+ end
229
+ end
230
+
231
+ describe '.request_all' do
232
+ describe 'repeats until no NextToken' do
233
+ before do
234
+ allow(Aerosol::AutoScaling).to receive(:request_all_for_token).with(nil) do
235
+ { 'AutoScalingGroups' => [1, 4], 'NextToken' => 'token' }
236
+ end
237
+
238
+ allow(Aerosol::AutoScaling).to receive(:request_all_for_token).with('token') do
239
+ { 'AutoScalingGroups' => [2, 3], 'NextToken' => nil }
240
+ end
241
+ end
242
+
243
+ it 'should include both autoscaling groups lists' do
244
+ expect(Aerosol::AutoScaling.request_all).to eq([1,4,2,3])
245
+ end
246
+ end
247
+ end
248
+
249
+ describe '.all' do
250
+ subject { described_class }
251
+
252
+ def destroy_all
253
+ Aerosol::AutoScaling.instances.values.each do |instance|
254
+ instance.destroy! rescue nil
255
+ end
256
+ end
257
+
258
+ after { destroy_all }
259
+
260
+ context 'when there are no auto scaling groups' do
261
+ before { destroy_all }
262
+
263
+ its(:all) { should be_empty }
264
+ end
265
+
266
+ context 'when there are auto scaling groups' do
267
+
268
+ let!(:insts) do
269
+ [
270
+ {
271
+ :min_size => 1,
272
+ :max_size => 3,
273
+ :availability_zones => 'us-east-1',
274
+ :launch_configuration => launch_config.name
275
+ },
276
+ {
277
+ :min_size => 2,
278
+ :max_size => 4,
279
+ :availability_zones => 'us-east-2',
280
+ :launch_configuration => launch_config.name,
281
+ :tag => { :my_tag => :is_sweet }
282
+ }
283
+ ].map { |hash| subject.new!(hash).tap { |inst| inst.stub(:sleep); inst.create! } }
284
+ end
285
+
286
+ it 'returns each of them' do
287
+ subject.all.map(&:min_size).should == [1, 2]
288
+ subject.all.map(&:max_size).should == [3, 4]
289
+ subject.all.map(&:tags).should == [{
290
+ "GitSha" => Aerosol::Util.git_sha,
291
+ "Deploy" => insts.first.name.to_s
292
+ },
293
+ {
294
+ "GitSha" => Aerosol::Util.git_sha,
295
+ "Deploy" => insts.last.name.to_s,
296
+ :my_tag => :is_sweet
297
+ }
298
+ ]
299
+ end
300
+ end
301
+ end
302
+
303
+ describe '.from_hash' do
304
+ context 'when the auto scaling group has not been initialized' do
305
+ let(:auto_scaling) { described_class.from_hash(hash) }
306
+
307
+ let(:hash) do
308
+ {
309
+ 'AutoScalingGroupName' => 'test-auto-scaling',
310
+ 'AvailabilityZones' => 'us-east-1',
311
+ 'LaunchConfigurationName' => launch_config.aws_identifier,
312
+ 'MinSize' => 1,
313
+ 'MaxSize' => 2
314
+ }
315
+ end
316
+
317
+ it 'creates a new auto scaling group with the specified values' do
318
+ auto_scaling.aws_identifier.should == 'test-auto-scaling'
319
+ auto_scaling.availability_zones.should == 'us-east-1'
320
+ auto_scaling.launch_configuration.should == launch_config
321
+ auto_scaling.min_size.should == 1
322
+ auto_scaling.max_size.should == 2
323
+ end
324
+
325
+ it 'generates a name' do
326
+ auto_scaling.name.to_s.should start_with 'AutoScaling_'
327
+ end
328
+ end
329
+
330
+ context 'when the auto scaling group has already been initialized' do
331
+ let(:old_hash) do
332
+ {
333
+ 'AutoScalingGroupName' => 'this-aws-id-abc-123',
334
+ 'MinSize' => 16
335
+ }
336
+ end
337
+ let(:new_hash) { old_hash.merge('MaxSize' => 40) }
338
+ let!(:existing) { described_class.from_hash(old_hash) }
339
+ let(:new) { described_class.from_hash(new_hash) }
340
+
341
+ it 'updates its values' do
342
+ expect { new }.to change { described_class.instances.length }.by(1)
343
+ new.aws_identifier.should == 'this-aws-id-abc-123'
344
+ new.min_size.should == 16
345
+ new.max_size.should == 40
346
+ end
347
+ end
348
+ end
349
+
350
+ describe '.latest_for_tag' do
351
+ subject { Aerosol::AutoScaling }
352
+
353
+ before { subject.stub(:all).and_return(groups) }
354
+
355
+ context 'when there are no groups' do
356
+ let(:groups) { [] }
357
+
358
+ it 'returns nil' do
359
+ subject.latest_for_tag('Deploy', 'my-deploy').should be_nil
360
+ end
361
+ end
362
+
363
+ context 'when there is at least one group' do
364
+ context 'but none of the groups satisfy the query' do
365
+ let(:group1) { double(:tags => { 'Deploy' => 'not-the-correct-deploy' }) }
366
+ let(:group2) { double(:tags => {}) }
367
+ let(:group3) { double(:tags => { 'deploy' => 'my-deploy' }) }
368
+ let(:groups) { [group1, group2, group3] }
369
+
370
+ it 'returns nil' do
371
+ subject.latest_for_tag('Deploy', 'my-deploy').should be_nil
372
+ end
373
+ end
374
+
375
+ context 'and one group satisfies the query' do
376
+ let(:group1) { double(:tags => { 'Deploy' => 'my-deploy' },
377
+ :created_time => Time.parse('01-01-2013')) }
378
+ let(:group2) { double(:tags => { 'Non' => 'Sense'}) }
379
+ let(:groups) { [group1, group2] }
380
+
381
+ it 'returns that group' do
382
+ subject.latest_for_tag('Deploy', 'my-deploy').should == group1
383
+ end
384
+ end
385
+
386
+ context 'and many groups satisfy the query' do
387
+ let(:group1) { double(:tags => { 'Deploy' => 'my-deploy' },
388
+ :created_time => Time.parse('01-01-2013')) }
389
+ let(:group2) { double(:tags => { 'Deploy' => 'my-deploy' },
390
+ :created_time => Time.parse('02-01-2013')) }
391
+ let(:group3) { double(:tags => { 'Non' => 'Sense'}) }
392
+ let(:groups) { [group1, group2, group3] }
393
+
394
+ it 'returns the group that was created last' do
395
+ subject.latest_for_tag('Deploy', 'my-deploy').should == group2
396
+ end
397
+ end
398
+ end
399
+ end
400
+
401
+ describe '#all_instances' do
402
+ let(:auto_scaling) {
403
+ described_class.new(
404
+ :name => :all_instances_asg,
405
+ :availability_zones => [],
406
+ :max_size => 10,
407
+ :min_size => 4,
408
+ :launch_configuration => launch_config.name
409
+ )
410
+ }
411
+
412
+ before { auto_scaling.stub(:sleep); auto_scaling.create! }
413
+ after { auto_scaling.destroy! }
414
+
415
+ it 'returns a list of instances associated with the group' do
416
+ auto_scaling.all_instances.length.should == 4
417
+ auto_scaling.all_instances.should be_all { |inst| inst.is_a?(Aerosol::Instance) }
418
+ end
419
+ end
420
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aerosol::AWS do
4
+ subject { Aerosol::AWS }
5
+
6
+ describe '#reset_cache!' do
7
+ before do
8
+ subject.instance_variable_set(:@auto_scaling, double)
9
+ subject.instance_variable_set(:@compute, double)
10
+ end
11
+
12
+ it 'sets @auto_scaling to nil' do
13
+ expect { subject.reset_cache! }
14
+ .to change { subject.instance_variable_get(:@auto_scaling) }
15
+ .to(nil)
16
+ end
17
+
18
+ it 'sets @compute to nil' do
19
+ expect { subject.reset_cache! }
20
+ .to change { subject.instance_variable_get(:@compute) }
21
+ .to(nil)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Aerosol CLI" do
4
+ describe "running the most basic command" do
5
+ let(:command) { "./bin/aerosol" }
6
+ it "should exit with 0" do
7
+ expect(system(command)).to be_true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aerosol::Connection do
4
+ describe '#with_connection' do
5
+ context 'when at least one of the required fields is missing' do
6
+ it 'raises an error' do
7
+ expect { subject.with_connection }.to raise_error
8
+ end
9
+ end
10
+
11
+ context 'when all of the required fields are present' do
12
+ subject do
13
+ Aerosol::Connection.new do
14
+ name :lil_joey_pumpkins
15
+ host 'www.aol.com'
16
+ user 'steve_case'
17
+ end
18
+ end
19
+
20
+ context 'when the jump host is nil' do
21
+ it 'logs in directly' do
22
+ Net::SSH.should_receive(:start)
23
+ subject.with_connection
24
+ end
25
+ end
26
+
27
+ context 'when the jump host is present' do
28
+ let(:gateway) { double(:gateway) }
29
+ before do
30
+ subject.jump :host => 'my-jump-host', :user => 'my-user'
31
+ end
32
+
33
+ it 'goes through the jump host' do
34
+ Net::SSH::Gateway.stub(:new).and_return(gateway)
35
+ gateway.should_receive(:ssh)
36
+ gateway.should_receive(:shutdown!)
37
+ subject.with_connection
38
+ end
39
+ end
40
+
41
+ context 'when the host is an instance' do
42
+ let(:public_hostname) { nil }
43
+ subject do
44
+ Aerosol::Connection.new.tap do |inst|
45
+ inst.name :connection_for_tim_horton
46
+ inst.user 'tim_horton'
47
+ inst.host double(:host, :public_hostname => public_hostname, :private_ip_address => '152.60.94.125')
48
+ end
49
+ end
50
+
51
+ context 'when the public_hostname is present' do
52
+ let(:public_hostname) { 'example.com' }
53
+ it 'returns the public_hostname' do
54
+ Net::SSH.should_receive(:start).with(public_hostname, 'tim_horton')
55
+ subject.with_connection
56
+ end
57
+ end
58
+
59
+ context 'when the public_hostname is nil' do
60
+ it 'returns the private_ip_address' do
61
+ Net::SSH.should_receive(:start).with('152.60.94.125', 'tim_horton')
62
+ subject.with_connection
63
+ end
64
+ end
65
+ end
66
+
67
+ context 'when the host is passed into with_connection' do
68
+ let(:public_hostname) { nil }
69
+ let(:host) { double(:host, :public_hostname => public_hostname, :private_ip_address => '152.60.94.125') }
70
+ subject do
71
+ Aerosol::Connection.new.tap do |inst|
72
+ inst.name :connection_for_tim_horton
73
+ inst.user 'tim_horton'
74
+ end
75
+ end
76
+
77
+ context 'when the public_hostname is present' do
78
+ let(:public_hostname) { 'example.com' }
79
+ it 'returns the public_hostname' do
80
+ Net::SSH.should_receive(:start).with(public_hostname, 'tim_horton')
81
+ subject.with_connection(host)
82
+ end
83
+ end
84
+
85
+ context 'when the public_hostname is nil' do
86
+ it 'returns the private_ip_address' do
87
+ Net::SSH.should_receive(:start).with('152.60.94.125', 'tim_horton')
88
+ subject.with_connection(host)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end