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,482 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aerosol::Runner do
4
+ describe '#with_deploy' do
5
+ before { subject.instance_variable_set(:@deploy, :original_deploy) }
6
+
7
+ context 'when the name is not one of the listed deploys' do
8
+ it 'raises an error and does not change the @deploy variable' do
9
+ subject.deploy.should == :original_deploy
10
+ expect { subject.with_deploy(:not_a_real_deploy) {} }.to raise_error
11
+ subject.deploy.should == :original_deploy
12
+ end
13
+ end
14
+
15
+ context 'when the name is a valid deploy' do
16
+ before do
17
+ Aerosol::Deploy.new!(:name => :my_deploy)
18
+ end
19
+
20
+ it 'sets @deploy to that deploy' do
21
+ subject.with_deploy(:my_deploy) do
22
+ subject.deploy.should be_a Aerosol::Deploy
23
+ subject.deploy.name.should == :my_deploy
24
+ end
25
+ end
26
+
27
+ it 'changes @deploy back after' do
28
+ expect { subject.with_deploy(:my_deploy) {} }.to_not change { subject.deploy }
29
+ end
30
+ end
31
+ end
32
+
33
+ describe '#run_migration' do
34
+ let(:db_conn) { double(:db_conn) }
35
+
36
+ before do
37
+ ENV['RAILS_ENV'] = 'production'
38
+ subject.stub(:db_conn).and_return(db_conn)
39
+ end
40
+
41
+ context 'when the deploy is nil' do
42
+ before { subject.instance_variable_set(:@deploy, nil) }
43
+
44
+ it 'raises an error' do
45
+ expect { subject.run_migration }.to raise_error
46
+ end
47
+ end
48
+
49
+ context 'context when the deploy is present' do
50
+ let!(:connection) { Aerosol::Connection.new!(:name => :run_migration_conn) }
51
+ let!(:deploy) { Aerosol::Deploy.new!(:name => :run_migration_deploy, :ssh => :run_migration_conn) }
52
+
53
+ before { subject.instance_variable_set(:@deploy, deploy) }
54
+
55
+ context 'and #do_not_migrate! has been called on it' do
56
+ before { subject.deploy.do_not_migrate! }
57
+
58
+ it 'does nothing' do
59
+ connection.should_not_receive(:with_connection)
60
+ subject.run_migration
61
+ end
62
+ end
63
+
64
+ context 'and #do_not_migrate! has not been called on it' do
65
+ context 'but the rails env is nil' do
66
+ before { ENV['RAILS_ENV'] = nil }
67
+
68
+ it 'raises an error' do
69
+ expect { subject.run_migration }.to raise_error
70
+ end
71
+ end
72
+
73
+ context 'and the rails env is set' do
74
+ let(:session) { double(:session) }
75
+ let(:port) { 50127 }
76
+ let(:conf) do
77
+ {
78
+ ENV['RAILS_ENV'] =>
79
+ {
80
+ 'database' => 'daddybase',
81
+ 'host' => 'http://www.geocities.com/spunk1111/',
82
+ 'port' => 8675309
83
+ }
84
+ }
85
+ end
86
+
87
+ before do
88
+ subject.stub(:random_open_port).and_return(port)
89
+ File.stub(:read)
90
+ ERB.stub_chain(:new, :result)
91
+ YAML.stub(:load).and_return(conf)
92
+ deploy.stub_chain(:migration_ssh, :with_connection).and_yield(session)
93
+ Process.stub(:waitpid).and_return { 1 }
94
+ Process::Status.any_instance.stub(:exitstatus) { 0 }
95
+ session.stub(:loop).and_yield
96
+ end
97
+
98
+ it 'forwards the database connection and runs the migration' do
99
+ session.stub_chain(:forward, :local)
100
+ .with(port,
101
+ conf['production']['host'],
102
+ conf['production']['port'])
103
+ ActiveRecord::Base.stub(:establish_connection)
104
+ ActiveRecord::Migrator.stub(:migrate)
105
+ .with(%w[db/migrate])
106
+ subject.run_migration
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#old_instances' do
114
+ let!(:asg1) do
115
+ Aerosol::AutoScaling.new! do
116
+ name :old_instances_asg_1
117
+ availability_zones 'us-east-1'
118
+ min_size 5
119
+ max_size 5
120
+ tag 'Deploy' => 'old_instances_deploy', 'GitSha' => 1
121
+ launch_configuration do
122
+ ami 'fake-ami'
123
+ instance_type 'm1.large'
124
+ stub(:sleep)
125
+ end
126
+ stub(:sleep)
127
+ end.tap(&:create)
128
+ end
129
+ let!(:asg2) do
130
+ Aerosol::AutoScaling.new! do
131
+ name :old_instances_asg_2
132
+ availability_zones 'us-east-1'
133
+ min_size 5
134
+ max_size 5
135
+ launch_configuration do
136
+ ami 'fake-ami'
137
+ instance_type 'm1.large'
138
+ stub(:sleep)
139
+ end
140
+ tag 'Deploy' => 'old_instances_deploy', 'GitSha' => 2
141
+ stub(:sleep)
142
+ end.tap(&:create)
143
+ end
144
+ let!(:asg3) do
145
+ Aerosol::AutoScaling.new! do
146
+ name :old_instances_asg_3
147
+ availability_zones 'us-east-1'
148
+ min_size 5
149
+ max_size 5
150
+ tag 'Deploy' => 'old_instances_deploy', 'GitSha' => 3
151
+ launch_configuration do
152
+ ami 'fake-ami'
153
+ instance_type 'm1.large'
154
+ stub(:sleep)
155
+ end
156
+ stub(:sleep)
157
+ end.tap(&:create)
158
+ end
159
+
160
+ let!(:deploy) do
161
+ Aerosol::Deploy.new! do
162
+ name :old_instances_deploy
163
+ auto_scaling :old_instances_asg_1
164
+ end
165
+ end
166
+
167
+ before(:all) { Aerosol::AutoScaling.all.map(&:destroy) }
168
+
169
+ it 'returns each instance that is not a member of the current auto scaling group' do
170
+ subject.with_deploy :old_instances_deploy do
171
+ subject.old_instances.map(&:id).sort.should ==
172
+ (asg2.launch_configuration.all_instances + asg3.launch_configuration.all_instances).map(&:id).sort
173
+ subject.old_instances.length.should == 10
174
+ end
175
+ end
176
+
177
+ it 'does not include any of the current auto scaling group\'s instances' do
178
+ subject.with_deploy :old_instances_deploy do
179
+ asg1.launch_configuration.all_instances.should be_none { |inst|
180
+ subject.old_instances.map(&:id).include?(inst.id)
181
+ }
182
+ end
183
+ end
184
+
185
+ it 'does not modify the existing instances' do
186
+ Aerosol::Instance.all.map(&:id).sort.should ==
187
+ [asg1, asg2, asg3].map(&:launch_configuration).map(&:all_instances).flatten.map(&:id).sort
188
+ subject.with_deploy :old_instances_deploy do
189
+ subject.new_instances.map(&:id).sort.should == asg1.all_instances.map(&:id).sort
190
+ end
191
+ end
192
+ end
193
+
194
+ describe '#new_instances' do
195
+ let!(:lc7) do
196
+ Aerosol::LaunchConfiguration.new! do
197
+ name :lc7
198
+ ami 'fake-ami-how-scandalous'
199
+ instance_type 'm1.large'
200
+ stub(:sleep)
201
+ end.tap(&:create)
202
+ end
203
+ let!(:asg7) do
204
+ Aerosol::AutoScaling.new! do
205
+ name :asg7
206
+ availability_zones 'us-east-1'
207
+ min_size 0
208
+ max_size 3
209
+ launch_configuration :lc7
210
+ stub(:sleep)
211
+ end.tap(&:create)
212
+ end
213
+ let!(:instance1) do
214
+ Aerosol::Instance.from_hash(
215
+ {
216
+ 'InstanceId' => 'z0',
217
+ 'LaunchConfigurationName' => lc7.aws_identifier
218
+ }
219
+ )
220
+ end
221
+ let!(:instance2) do
222
+ double(:launch_configuration => double(:aws_identifier => 'lc7-8891022'))
223
+ end
224
+ let!(:instance3) do
225
+ double(:launch_configuration => double(:aws_identifier => 'lc0-8891022'))
226
+ end
227
+
228
+ let!(:deploy) do
229
+ Aerosol::Deploy.new! do
230
+ name :new_instances_deploy
231
+ auto_scaling :asg7
232
+ end
233
+ end
234
+
235
+ before do
236
+ Aerosol::Instance.stub(:all).and_return([instance1, instance2, instance3])
237
+ end
238
+ it 'returns each instance that is a member of the current launch config' do
239
+ subject.with_deploy :new_instances_deploy do
240
+ subject.new_instances.should == [instance1]
241
+ end
242
+ end
243
+ end
244
+
245
+ describe '#wait_for_new_instances' do
246
+ let(:instances) do
247
+ 3.times.map do |i|
248
+ double(:instance,
249
+ :public_hostname => 'not-a-real-hostname',
250
+ :id => "test#{i}")
251
+ end
252
+ end
253
+ let(:timeout_length) { 0.01 }
254
+ let!(:deploy) do
255
+ timeout = timeout_length
256
+ Aerosol::Deploy.new! do
257
+ name :wait_for_new_instances_deploy
258
+ is_alive? { is_site_live }
259
+ instance_live_grace_period timeout
260
+ stub(:sleep)
261
+ end
262
+ end
263
+ let(:action) do
264
+ subject.with_deploy(:wait_for_new_instances_deploy) { subject.wait_for_new_instances }
265
+ end
266
+
267
+ before do
268
+ subject.stub(:healthy?).and_return(healthy)
269
+ subject.stub(:sleep)
270
+ subject.stub(:new_instances).and_return(instances)
271
+ end
272
+
273
+ context 'when all of the new instances eventually return a 200' do
274
+ let(:timeout_length) { 1 }
275
+ let(:healthy) { true }
276
+ let(:is_site_live) { true }
277
+
278
+ it 'does nothing' do
279
+ expect { action }.to_not raise_error
280
+ end
281
+ end
282
+
283
+ context 'when at least one of the instances never returns a 200' do
284
+ let(:healthy) { false }
285
+ let(:is_site_live) { false }
286
+
287
+ it 'raises an error' do
288
+ expect { action }.to raise_error
289
+ end
290
+ end
291
+
292
+ context 'when getting new instances takes too long' do
293
+ let(:healthy) { true }
294
+ let(:is_site_live) { false }
295
+ before do
296
+ allow(subject).to receive(:new_instances) { sleep 10 }
297
+ end
298
+
299
+ it 'raises an error' do
300
+ expect { action }.to raise_error
301
+ end
302
+ end
303
+ end
304
+
305
+ describe '#start_tailing_logs' do
306
+ let(:ssh) { double(:ssh) }
307
+ let(:instance) { double(:instance, id: '2') }
308
+ let(:command) { 'sudo tail -f /var/log/syslog' }
309
+ let(:tail_logs) { true }
310
+ let(:log_files) { ['/var/log/syslog'] }
311
+
312
+ before do
313
+ allow(subject).to receive(:tail_logs).and_return(tail_logs)
314
+ allow(subject).to receive(:log_files).and_return(log_files)
315
+ end
316
+
317
+ context 'when there are log_files' do
318
+ context 'when a log fork is already made' do
319
+ let(:old_log_fork) { double(:old_log_fork) }
320
+
321
+ it 'keeps the old one' do
322
+ subject.log_pids[instance.id] = old_log_fork
323
+ expect(subject.start_tailing_logs(ssh, instance)).to be(old_log_fork)
324
+ end
325
+ end
326
+
327
+ context 'when no log fork exists' do
328
+ let(:new_log_fork) { double(:new_log_fork) }
329
+
330
+ it 'makes a new one' do
331
+ expect(subject).to receive(:ssh_fork).with(command, ssh, instance) {
332
+ new_log_fork
333
+ }
334
+ expect(subject.start_tailing_logs(ssh, instance)).to be(new_log_fork)
335
+ end
336
+ end
337
+ end
338
+
339
+ context 'when there is no log_files' do
340
+ let(:log_files) { [] }
341
+
342
+ it 'does not call ssh_fork' do
343
+ expect(subject).to_not receive(:ssh_fork)
344
+ end
345
+ end
346
+
347
+ context 'when tail_logs is false' do
348
+ let(:tail_logs) { false }
349
+
350
+ it 'does not call ssh_fork' do
351
+ expect(subject).to_not receive(:ssh_fork)
352
+ end
353
+ end
354
+ end
355
+
356
+ describe '#ssh_fork', :local do
357
+ let(:ssh) { Aerosol::Connection.new(user: `whoami`.strip, host: 'www.doesntusethis.com') }
358
+ let(:instance) { double(:instance, id: '1', public_hostname: 'localhost') }
359
+ let(:ssh_fork) {
360
+ subject.ssh_fork(command, ssh, instance)
361
+ }
362
+ context 'when no error is raised' do
363
+ let(:command) { 'echo "hello"; echo "bye"' }
364
+
365
+ it 'should make a new fork that SSHs and runs a command' do
366
+ expect(subject).to receive(:fork).and_yield do |&block|
367
+ expect(subject).to receive(:debug).exactly(5).times
368
+ block.call
369
+ end
370
+ ssh_fork
371
+ end
372
+ end
373
+
374
+ context 'when an error is raised' do
375
+ let(:command) { ['test','ing'] }
376
+
377
+ it 'logs the errors' do
378
+ expect(subject).to receive(:fork).and_yield do |&block|
379
+ expect(subject).to receive(:error).twice
380
+ block.call
381
+ end
382
+ ssh_fork
383
+ end
384
+ end
385
+ end
386
+
387
+ describe '#stop_app' do
388
+ let!(:lc) do
389
+ Aerosol::LaunchConfiguration.new! do
390
+ name :stop_app_launch_config
391
+ ami 'stop-app-ami-123'
392
+ instance_type 'm1.large'
393
+ stub(:sleep)
394
+ end.tap(&:create)
395
+ end
396
+ let!(:asg) do
397
+ Aerosol::AutoScaling.new! do
398
+ name :stop_app_auto_scaling_group
399
+ availability_zones 'us-east-1'
400
+ min_size 5
401
+ max_size 5
402
+ launch_configuration :stop_app_launch_config
403
+ stub(:sleep)
404
+ end.tap(&:create)
405
+ end
406
+ let!(:instances) { Aerosol::Instance.all.select { |instance| instance.ami == lc.ami } }
407
+ let!(:session) { double(:session) }
408
+ let!(:deploy) do
409
+ s = session
410
+ Aerosol::Deploy.new! do
411
+ name :stop_app_deploy
412
+ ssh :stop_app_ssh do
413
+ user 'dad'
414
+ stub(:with_connection).and_yield(s)
415
+ end
416
+ stop_command 'mkdir lol'
417
+ end
418
+ end
419
+
420
+ it 'sshs into each old instance and calls the stop command' do
421
+ session.should_receive(:exec!).with(deploy.stop_command).exactly(5).times
422
+ session.should_receive(:loop).exactly(5).times
423
+ subject.stub(:old_instances).and_return(instances)
424
+ subject.with_deploy :stop_app_deploy do
425
+ subject.stop_app
426
+ end
427
+ end
428
+ end
429
+
430
+ describe '#old_auto_scaling_groups/#new_auto_scaling_groups' do
431
+ let!(:asg1) do
432
+ Aerosol::AutoScaling.new! do
433
+ name :destroy_old_asgs_auto_scaling_group_1
434
+ availability_zones 'us-east-1'
435
+ min_size 0
436
+ max_size 3
437
+ tag 'Deploy' => 'destroy_old_asgs_deploy', 'GitSha' => '1e7b3cd'
438
+ stub(:sleep)
439
+ stub(:aws_identifier).and_return(1)
440
+ end
441
+ end
442
+ let!(:asg2) do
443
+ Aerosol::AutoScaling.new! do
444
+ name :destroy_old_asgs_auto_scaling_group_2
445
+ availability_zones 'us-east-1'
446
+ min_size 0
447
+ max_size 3
448
+ tag 'Deploy' => 'destroy_old_asgs_deploy', 'GitSha' => '1234567'
449
+ stub(:sleep)
450
+ stub(:aws_identifier).and_return(2)
451
+ end
452
+ end
453
+ let!(:asg3) do
454
+ Aerosol::AutoScaling.new! do
455
+ name :destroy_old_asgs_auto_scaling_group_3
456
+ availability_zones 'us-east-1'
457
+ min_size 0
458
+ max_size 5
459
+ tag 'Deploy' => 'not-part-of-this-app', 'GitSha' => '1e7b3cd'
460
+ stub(:sleep)
461
+ stub(:aws_identifier).and_return(3)
462
+ end
463
+ end
464
+
465
+ let!(:deploy) do
466
+ Aerosol::Deploy.new! do
467
+ name :destroy_old_asgs_deploy
468
+ auto_scaling :destroy_old_asgs_auto_scaling_group_1
469
+ end
470
+ end
471
+
472
+ before do
473
+ subject.instance_variable_set(:@deploy, deploy)
474
+ Aerosol::AutoScaling.stub(:all).and_return([asg1, asg2, asg3])
475
+ end
476
+
477
+ it 'returns the old and new groups from this app' do
478
+ subject.old_auto_scaling_groups.should == [asg2]
479
+ subject.new_auto_scaling_groups.should == [asg1]
480
+ end
481
+ end
482
+ end
@@ -0,0 +1,41 @@
1
+ # Copyright Swipely, Inc. All rights reserved.
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Aerosol do
6
+ subject { Aerosol }
7
+
8
+ {
9
+ :auto_scaling => Aerosol::AutoScaling,
10
+ :deploy => Aerosol::Deploy,
11
+ :launch_configuration => Aerosol::LaunchConfiguration,
12
+ :ssh => Aerosol::Connection
13
+ }.each do |name, klass|
14
+ describe ".#{name}" do
15
+ before { subject.send(name, :"runner_test_#{name}") { } }
16
+
17
+ it "creates a new #{klass}" do
18
+ expect(klass.instances.keys).to include(:"runner_test_#{name}")
19
+ end
20
+
21
+ it "accessible via #{klass} without a block " do
22
+ expect(subject.send("#{name}s").keys).to include(:"runner_test_#{name}")
23
+ end
24
+ end
25
+ end
26
+
27
+ it { should be_an_instance_of(Module) }
28
+
29
+ describe ".namespace" do
30
+ let(:namespace) { "test" }
31
+ before { subject.namespace namespace }
32
+
33
+ it "sets the namespace" do
34
+ expect(subject.instance_variable_get(:"@namespace")).to eq(namespace)
35
+ end
36
+
37
+ it "returns the namespace after being set" do
38
+ expect(subject.namespace).to eq(namespace)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1 @@
1
+ web: start_my_server
@@ -0,0 +1,17 @@
1
+ # Copyright Swipely, Inc. All rights reserved.
2
+
3
+ $LOAD_PATH.unshift( File.join( File.dirname(__FILE__), 'lib' ) )
4
+
5
+ require 'rake'
6
+ require 'rspec/core/rake_task'
7
+ require 'cane/rake_task'
8
+
9
+ task :default => [:spec, :quality]
10
+
11
+ RSpec::Core::RakeTask.new do |t|
12
+ t.pattern = 'spec/**/*_spec.rb'
13
+ end
14
+
15
+ Cane::RakeTask.new(:quality) do |cane|
16
+ cane.canefile = '.cane'
17
+ end
@@ -0,0 +1 @@
1
+ ~~~ me neither :) :) :) ~~~
@@ -0,0 +1 @@
1
+ ~~~ no tarz here ~~~
Binary file
Binary file
Binary file
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'rspec'
5
+ require 'pry'
6
+ require 'aerosol'
7
+ # Requires supporting files with custom matchers and macros, etc, in ./support/
8
+ # and its subdirectories.
9
+ #Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
10
+
11
+ Fog.mock!
12
+
13
+ Aerosol::AWS.aws_access_key_id = 'MOCK_KEY'
14
+ Aerosol::AWS.aws_secret_access_key = 'MOCK_SECRET'
15
+ Dockly::Util::Logger.disable! unless ENV['ENABLE_LOGGER'] == 'true'
16
+
17
+ RSpec.configure do |config|
18
+ config.mock_with :rspec
19
+ config.treat_symbols_as_metadata_keys_with_true_values = true
20
+ config.tty = true
21
+ config.filter_run_excluding local: true if ENV['CI']
22
+ end
@@ -0,0 +1,11 @@
1
+ require 'webmock'
2
+ require 'vcr'
3
+
4
+ WebMock.disable_net_connect!
5
+
6
+ VCR.configure do |c|
7
+ c.allow_http_connections_when_no_cassette = true
8
+ c.hook_into :webmock
9
+ c.cassette_library_dir = File.join(File.dirname(File.dirname(__FILE__)), 'vcr')
10
+ c.configure_rspec_metadata!
11
+ end