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