aerosol 0.5.1

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.
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