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,192 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aerosol::Deploy do
4
+ let!(:ssh) { double(:ssh) }
5
+ subject { described_class.new(:name => :test) }
6
+
7
+ before do
8
+ subject.stub(:ssh).and_return(ssh)
9
+ end
10
+
11
+ describe '#migration' do
12
+ context 'by default' do
13
+ its(:migrate?) { should be_true }
14
+ end
15
+
16
+ context 'when do_not_migrate! has been called' do
17
+ before { subject.do_not_migrate! }
18
+
19
+ its(:migrate?) { should be_false }
20
+ end
21
+ end
22
+
23
+ describe '#run_post_deploy' do
24
+ context 'with no post_deploy_command' do
25
+ before do
26
+ subject.stub(:post_deploy_command)
27
+ end
28
+
29
+ it "doesn't raises an error" do
30
+ expect { subject.run_post_deploy }.to_not raise_error
31
+ end
32
+
33
+ it "returns nil" do
34
+ expect(subject.run_post_deploy).to be_nil
35
+ end
36
+ end
37
+
38
+ context 'with post_deploy_command' do
39
+ context 'and post_deploy_command runs correctly' do
40
+ before do
41
+ subject.stub(:post_deploy_command).and_return('true')
42
+ end
43
+
44
+ it "doesn't raises an error" do
45
+ expect { subject.run_post_deploy }.to_not raise_error
46
+ end
47
+
48
+ it "returns true" do
49
+ expect(subject.run_post_deploy).to be_true
50
+ end
51
+ end
52
+
53
+ context 'and post_deploy_command runs incorrectly' do
54
+ before do
55
+ subject.stub(:post_deploy_command).and_return('false')
56
+ end
57
+
58
+ it 'raises an error' do
59
+ expect { subject.run_post_deploy }.to raise_error
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '#local_ssh_ref' do
66
+ context 'when there is no local_ssh' do
67
+ its(:local_ssh_ref) { should eq(ssh) }
68
+ end
69
+
70
+ context 'when there is a local_ssh' do
71
+ let!(:local_ssh) { double(:local_ssh) }
72
+ before do
73
+ subject.stub(:local_ssh).and_return(local_ssh)
74
+ end
75
+
76
+ its(:local_ssh_ref) { should eq(local_ssh) }
77
+ end
78
+ end
79
+
80
+ describe '#generate_ssh_command' do
81
+ let(:ssh_ref) { double(:ssh_ref) }
82
+ let(:instance) { double(:instance) }
83
+ let(:ssh_command) { subject.generate_ssh_command(instance) }
84
+
85
+ before do
86
+ instance.stub(:public_hostname).and_return('hostname.com')
87
+ subject.stub(:local_ssh_ref).and_return(ssh_ref)
88
+ end
89
+
90
+ context 'with a user' do
91
+ before do
92
+ ssh_ref.stub(:user).and_return('ubuntu')
93
+ end
94
+
95
+ context 'without a jump server' do
96
+ before do
97
+ ssh_ref.stub(:jump)
98
+ end
99
+
100
+ it 'responds with no jump server' do
101
+ expect(ssh_command).to be =~ /ssh .* ubuntu@hostname.com/
102
+ end
103
+ end
104
+
105
+ context 'with a jump server' do
106
+ before do
107
+ ssh_ref.stub(:jump).and_return(:user => 'candle', :host => 'example.org')
108
+ end
109
+
110
+ it 'responds with a jump server' do
111
+ expect(ssh_command).to be =~ /ssh .* -o 'ProxyCommand=ssh -W %h:%p candle@example\.org' ubuntu@hostname\.com/
112
+ end
113
+ end
114
+ end
115
+
116
+ context 'without a user' do
117
+ before do
118
+ ssh_ref.stub(:user)
119
+ end
120
+
121
+ context 'without a jump server' do
122
+ before do
123
+ ssh_ref.stub(:jump)
124
+ end
125
+
126
+ it 'responds with no user and no jump' do
127
+ expect(ssh_command).to be =~ /ssh .* hostname.com/
128
+ end
129
+ end
130
+
131
+ context 'with a jump server' do
132
+ before do
133
+ ssh_ref.stub(:jump).and_return(:user => 'candle', :host => 'example.org')
134
+ end
135
+
136
+ it 'responds with no user and a jump server' do
137
+ expect(ssh_command).to be =~ /ssh .* -o 'ProxyCommand=ssh -W %h:%p candle@example\.org' hostname\.com/
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ describe '#live_check_url' do
144
+ context 'when SSL is not enabled' do
145
+ subject {
146
+ Aerosol::Deploy.new do
147
+ app_port 5000
148
+ live_check '/test'
149
+ end
150
+ }
151
+
152
+ it 'returns an http url' do
153
+ expect(subject.live_check_url).to eq('http://localhost:5000/test')
154
+ end
155
+ end
156
+
157
+ context 'when SSL is enabled' do
158
+ subject {
159
+ Aerosol::Deploy.new do
160
+ app_port 4000
161
+ live_check 'check'
162
+ ssl true
163
+ end
164
+ }
165
+
166
+ it 'returns an https url' do
167
+ expect(subject.live_check_url).to eq('https://localhost:4000/check')
168
+ end
169
+ end
170
+ end
171
+
172
+ describe '#is_alive?' do
173
+ let(:check) { proc { true } }
174
+
175
+ context 'when no argument is given' do
176
+ before { subject.is_alive?(&check) }
177
+
178
+ it 'returns the current value of is_alive?' do
179
+ expect(subject.is_alive?).to eq(check)
180
+ end
181
+ end
182
+
183
+ context 'when an argument is given' do
184
+ it 'sets is_alive? to that value' do
185
+ expect { subject.is_alive?(&check) }
186
+ .to change { subject.is_alive? }
187
+ .from(nil)
188
+ .to(check)
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aerosol::Env do
4
+ describe '#deploy' do
5
+ let(:name) { "unique_name_#{Time.now.to_i}".to_sym }
6
+ let!(:deploy) { Aerosol.deploy(name) { } }
7
+
8
+
9
+ it 'adds a deploy to the list of deploys' do
10
+ expect { subject.deploy(name) }
11
+ .to change { subject.deploy }
12
+ .from(nil)
13
+ .to([deploy])
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aerosol::Instance do
4
+ let!(:launch_config) do
5
+ Aerosol::LaunchConfiguration.new! do
6
+ name :launch_config_for_instances
7
+ ami 'ami-123-abc'
8
+ instance_type 'm1.large'
9
+ stub :sleep
10
+ end
11
+ end
12
+
13
+ let!(:auto_scaling) do
14
+ Aerosol::AutoScaling.new! do
15
+ name :as_group_for_instances
16
+ availability_zones 'us-east-2'
17
+ launch_configuration :launch_config_for_instances
18
+ min_size 10
19
+ max_size 10
20
+ stub :sleep
21
+ end
22
+ end
23
+
24
+ describe '.all' do
25
+ subject { described_class.all }
26
+
27
+ context 'when there are no instances' do
28
+ it { should be_empty }
29
+ end
30
+
31
+ context 'when there are instances' do
32
+ before { launch_config.create; auto_scaling.create }
33
+ after { launch_config.destroy; auto_scaling.destroy }
34
+
35
+ it 'materializes each of them into an object' do
36
+ subject.length.should == 10
37
+ subject.should be_all { |inst| inst.launch_configuration == launch_config }
38
+ subject.should be_all { |inst| inst.availability_zone == 'us-east-2' }
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '.description' do
44
+ before { auto_scaling.create }
45
+ after { auto_scaling.destroy }
46
+
47
+ subject { described_class.all.first }
48
+
49
+ it 'returns additional information about the instance' do
50
+ subject.description['imageId'].should == launch_config.ami
51
+ subject.description['instanceType'].should == launch_config.instance_type
52
+ end
53
+
54
+ its(:public_hostname) { should_not be_nil }
55
+ its(:private_ip_address) { should_not be_nil }
56
+ end
57
+ end
@@ -0,0 +1,328 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aerosol::LaunchConfiguration do
4
+ subject do
5
+ described_class.new do
6
+ name :my_launch_config
7
+ ami 'ami-123'
8
+ instance_type 'super-cool-instance-type'
9
+ user_data <<-END_OF_STRING
10
+ #!/bin/bash
11
+ rm -rf /
12
+ END_OF_STRING
13
+ end
14
+ end
15
+ before { subject.stub(:sleep) }
16
+
17
+ describe "#aws_identifier" do
18
+ context "with no namespace set" do
19
+ let(:identifier) { "my_launch_config-#{Aerosol::Util.git_sha}" }
20
+ it "returns a normal identifier" do
21
+ expect(subject.aws_identifier).to eq(identifier)
22
+ end
23
+ end
24
+
25
+ context "with a namespace set" do
26
+ let(:namespace) { "test" }
27
+ let(:identifier) { "#{namespace}-my_launch_config-#{Aerosol::Util.git_sha}" }
28
+
29
+ before { Aerosol.namespace namespace }
30
+ after { Aerosol.instance_variable_set(:"@namespace", nil) }
31
+
32
+ it "returns a namespaced identifier" do
33
+ expect(subject.aws_identifier).to eq(identifier)
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '#security_group' do
39
+ subject { described_class.new!(:name => 'conf-conf-conf') }
40
+
41
+ it 'adds the argument to the list of security groups' do
42
+ expect { subject.security_group 'my group' }
43
+ .to change { subject.security_groups.length }
44
+ .by 1
45
+ end
46
+
47
+ it 'does not the default security group' do
48
+ expect { subject.security_group 'other test' }
49
+ .to_not change { described_class.default_values[:security_groups] }
50
+ end
51
+ end
52
+
53
+ describe '#create!' do
54
+ context 'when some required fields are nil' do
55
+ before { subject.instance_variable_set(:@ami, nil) }
56
+
57
+ it 'raises an error' do
58
+ expect { subject.create! }.to raise_error
59
+ end
60
+ end
61
+
62
+ context 'when everything is present' do
63
+ context 'and the launch configuration already exists' do
64
+ before { subject.create! rescue nil }
65
+ after { subject.destroy! rescue nil }
66
+
67
+ it 'raises an error' do
68
+ expect { subject.create! }.to raise_error
69
+ end
70
+ end
71
+
72
+ context 'and the launch configuration does not exist yet' do
73
+ after { subject.destroy! rescue nil }
74
+
75
+ it 'creates the launch configuration group' do
76
+ expect { subject.create! }.to_not raise_error
77
+ end
78
+
79
+ it 'fixes the user data spacing' do
80
+ subject.send(:conn).should_receive(:create_launch_configuration)
81
+ .with('ami-123', 'super-cool-instance-type',
82
+ subject.aws_identifier,
83
+ 'SecurityGroups' => [],
84
+ 'UserData' => "#!/bin/bash\nrm -rf /\n")
85
+ subject.create!
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ describe '#destroy!' do
92
+ context 'when the aws_identifier is nil' do
93
+ before { subject.instance_variable_set(:@aws_identifier, nil) }
94
+
95
+ it 'raises an error' do
96
+ expect { subject.destroy! }.to raise_error
97
+ end
98
+ end
99
+
100
+ context 'when the aws_identifier is present' do
101
+ context 'but the launch configuration does not exist' do
102
+ it 'raises an error' do
103
+ expect { subject.destroy! }.to raise_error
104
+ end
105
+ end
106
+
107
+ context 'and the launch configuration exists' do
108
+ before { subject.create! }
109
+
110
+ it 'deletes the launch configuration' do
111
+ expect { subject.destroy! }.to_not raise_error
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ describe '#create' do
118
+ context 'when the aws_identifier is nil' do
119
+ subject { described_class.new!(:name => :random_test_name) }
120
+
121
+ it 'raises an error' do
122
+ expect { subject.create }.to raise_error
123
+ end
124
+ end
125
+
126
+ context 'when the aws_identifier is present' do
127
+ subject do
128
+ described_class.new! do
129
+ name :random_test_name_2
130
+ ami 'test-ami-who-even-cares-really'
131
+ instance_type 'm1.large'
132
+ end
133
+ end
134
+
135
+ context 'but the launch configuration already exists' do
136
+ before { subject.create! }
137
+
138
+ it 'does not call #create!' do
139
+ subject.should_not_receive(:create!)
140
+ subject.create
141
+ end
142
+ end
143
+
144
+ context 'and the launch configuration does not yet exist' do
145
+ before { subject.destroy }
146
+
147
+ it 'creates it' do
148
+ subject.should_receive(:create!)
149
+ subject.create
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ describe '#destroy' do
156
+ context 'when the aws_identifier is nil' do
157
+ subject { described_class.new!(:name => :random_test_name_3) }
158
+
159
+ it 'raises an error' do
160
+ expect { subject.create }.to raise_error
161
+ end
162
+ end
163
+
164
+ context 'when the aws_identifier is present' do
165
+ subject do
166
+ described_class.new! do
167
+ name :random_test_name_4
168
+ ami 'awesome-ami'
169
+ instance_type 'm1.large'
170
+ end
171
+ end
172
+
173
+ context 'and the launch configuration already exists' do
174
+ before { subject.create! }
175
+
176
+ it 'calls #destroy!' do
177
+ subject.should_receive(:destroy!)
178
+ subject.destroy
179
+ end
180
+ end
181
+
182
+ context 'but the launch configuration does not yet exist' do
183
+ before { subject.destroy! rescue nil }
184
+
185
+ it 'does not call #destroy!' do
186
+ subject.should_not_receive(:destroy!)
187
+ subject.destroy
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ describe '.exists?' do
194
+ subject { described_class }
195
+
196
+ context 'when the argument exists' do
197
+ let(:instance) do
198
+ subject.new! do
199
+ name :exists_test_name
200
+ ami 'ami123'
201
+ instance_type 'm1.large'
202
+ stub(:sleep)
203
+ end.tap(&:create!)
204
+ end
205
+
206
+ it 'returns true' do
207
+ subject.exists?(instance.aws_identifier).should be_true
208
+ end
209
+ end
210
+
211
+ context 'when the argument does not exist' do
212
+ let(:instance) { described_class.new! }
213
+
214
+ it 'returns false' do
215
+ subject.exists?(instance.aws_identifier).should be_false
216
+ end
217
+ end
218
+ end
219
+
220
+ describe '.request_all' do
221
+ describe 'repeats until no NextToken' do
222
+ before do
223
+ allow(Aerosol::LaunchConfiguration).to receive(:request_all_for_token).with(nil) do
224
+ { 'LaunchConfigurations' => [1, 4], 'NextToken' => 'token' }
225
+ end
226
+
227
+ allow(Aerosol::LaunchConfiguration).to receive(:request_all_for_token).with('token') do
228
+ { 'LaunchConfigurations' => [2, 3], 'NextToken' => nil }
229
+ end
230
+ end
231
+
232
+ it 'should include both autoscaling groups lists' do
233
+ expect(Aerosol::LaunchConfiguration.request_all).to eq([1,4,2,3])
234
+ end
235
+ end
236
+ end
237
+
238
+ describe '.all' do
239
+ subject { described_class }
240
+
241
+ def destroy_all
242
+ Aerosol::LaunchConfiguration.instances.values.each do |instance|
243
+ instance.destroy! rescue nil
244
+ end
245
+ end
246
+
247
+ after { destroy_all }
248
+
249
+ context 'when there are no launch configurations' do
250
+ before { destroy_all }
251
+
252
+ its(:all) { should be_empty }
253
+ end
254
+
255
+ context 'when there are launch configurations' do
256
+
257
+ before do
258
+ [
259
+ {
260
+ :ami => 'ami1',
261
+ :instance_type => 'm1.large'
262
+ },
263
+ {
264
+ :ami => 'ami2',
265
+ :instance_type => 'm1.large'
266
+ }
267
+ ].each { |hash| subject.new!(hash).tap { |inst| inst.stub(:sleep) }.create! }
268
+ end
269
+
270
+ it 'returns each of them' do
271
+ subject.all.map(&:ami).should == %w[ami1 ami2]
272
+ subject.all.map(&:instance_type).should == %w[m1.large m1.large]
273
+ end
274
+ end
275
+ end
276
+
277
+ describe '.from_hash' do
278
+ context 'when the launch configuration has not been initialized' do
279
+ subject { described_class.from_hash(hash) }
280
+ let(:hash) do
281
+ {
282
+ 'LaunchConfigurationName' => '~test-launch-config~',
283
+ 'ImageId' => 'ami-123',
284
+ 'InstanceType' => 'm1.large',
285
+ 'SecurityGroups' => [],
286
+ 'UserData' => 'echo hi',
287
+ 'IamInstanceProfile' => nil,
288
+ 'KernelId' => 'kernel-id',
289
+ 'KeyName' => 'key-name',
290
+ 'SpotPrice' => '0.04',
291
+ }
292
+ end
293
+
294
+ it 'creates a new launch configuration with the specified values' do
295
+ subject.aws_identifier.should == '~test-launch-config~'
296
+ subject.ami.should == 'ami-123'
297
+ subject.instance_type.should == 'm1.large'
298
+ subject.security_groups.should be_empty
299
+ subject.user_data.should == 'echo hi'
300
+ subject.iam_role.should be_nil
301
+ subject.kernel_id.should == 'kernel-id'
302
+ subject.spot_price.should == '0.04'
303
+ end
304
+
305
+ it 'generates a name' do
306
+ subject.name.to_s.should start_with 'LaunchConfiguration_'
307
+ end
308
+ end
309
+
310
+ context 'when the launch configuration has already been initialized' do
311
+ let(:old_hash) do
312
+ {
313
+ 'LaunchConfigurationName' => 'this-aws-id-abc-123',
314
+ 'ImageId' => 'ami-456',
315
+ }
316
+ end
317
+ let(:new_hash) { old_hash.merge('InstanceType' => 'm1.large') }
318
+ let!(:existing) { described_class.from_hash(old_hash) }
319
+ let(:new) { described_class.from_hash(new_hash) }
320
+
321
+ it 'updates its values' do
322
+ expect { new }.to change { described_class.instances.length }.by(1)
323
+ new.aws_identifier.should == 'this-aws-id-abc-123'
324
+ new.ami.should == 'ami-456'
325
+ end
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Rake' do
4
+ describe 'load' do
5
+ before do
6
+ Aerosol::Util.stub(:git_sha)
7
+ end
8
+
9
+ context 'when the aerosol.rb file does not exist' do
10
+ before do
11
+ File.stub(:exist?).and_return(false)
12
+ end
13
+
14
+ it 'raises an error' do
15
+ lambda { Rake::Task['aerosol:load'].invoke }.should raise_error(RuntimeError, 'No aerosol.rb found!')
16
+ end
17
+ end
18
+ end
19
+ end