aerosol 1.8.0 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 8800e253ba3945d047a0186145a0aa58bc2d1ea2374ca6cc3e8f9e102a8ad510
4
- data.tar.gz: 9748857dd6d759985cf7e1071364474dbb013b2eff7eae3c86616e0eebcc1879
2
+ SHA1:
3
+ metadata.gz: 8743c2772be4ced4908e79d58b495dc040f9a00a
4
+ data.tar.gz: 381431aa30ee0f28ed00baf8bcc17da47de3f824
5
5
  SHA512:
6
- metadata.gz: 143084a0ffcd6ed83e36bea8a106e88776cc146adacc7d55b3976f52fd0ce80d6e0c7543311c5d82110900ce927cca5ca29bb4a06903131497d88ae7ed02a7f6
7
- data.tar.gz: 4ea6f13e4f3464f04ab05bd33d504f586c5206cbf13bfbdeed63411ad2a2170b812631d8185c18981a8c46dfc55d5f18d189a59826b04129aca4c14d034148a3
6
+ metadata.gz: bb227893206994a0a1df737c0c08a9f77353df2773c2a7e2b18e3eb04175ea95e32d567729a2d02fecf60b78eb07652d3faff6528a378f22801db3d796a8a800
7
+ data.tar.gz: 444a844c3f2038b8fe2ad382b88c227802937c02bf92f53dcaa72b01023d9b5ff67aa6a177510dca94613b99a33c654d9dbe403c69683c79f97daa4f468b51c7
@@ -0,0 +1,31 @@
1
+ name: Unit Tests
2
+ on: [push, pull_request]
3
+ jobs:
4
+ docker-rspec:
5
+ runs-on:
6
+ - ubuntu-18.04
7
+ strategy:
8
+ matrix:
9
+ ruby:
10
+ - 2.7
11
+ - 2.2
12
+ - 2.1
13
+ fail-fast: true
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ with:
17
+ fetch-depth: 0
18
+ - uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: ${{ matrix.ruby }}
21
+ - name: install bundler
22
+ run: |
23
+ gem install bundler -v '~> 1.17.3'
24
+ bundle update
25
+ - name: install rpm
26
+ run: |
27
+ set -x
28
+ sudo apt-get update -y
29
+ sudo apt-get install -y rpm
30
+ - name: spec tests
31
+ run: CI=true bundle exec rake
data/.gitignore CHANGED
@@ -12,4 +12,6 @@ Gemfile.lock
12
12
  build/
13
13
  dist/
14
14
 
15
+ .vscode
16
+
15
17
  docker-export-ubuntu-latest.tar.gz
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/aerosol.png)](http://badge.fury.io/rb/aerosol)
2
- [![Build Status](https://travis-ci.org/swipely/aerosol.png)](https://travis-ci.org/swipely/aerosol)
3
2
 
4
- ![Aerosol](https://raw.github.com/swipely/aerosol/master/img/aerosol.png)
3
+ ![Aerosol](https://raw.github.com/upserve/aerosol/master/img/aerosol.png)
5
4
  =========================================================================
6
5
 
7
6
  Aerosol orchestrates instance-based-deploys. Start new EC2 instances running your app every release, using auto scaling groups to orchestrate the startup of the new version and the graceful shutdown of the old version.
data/aerosol.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
6
6
  gem.email = %w{tomhulihan@swipely.com bright@swipely.com toddlunter@swipely.com}
7
7
  gem.description = %q{Instance-based deploys made easy}
8
8
  gem.summary = %q{Instance-based deploys made easy}
9
- gem.homepage = "https://github.com/swipely/aerosol"
9
+ gem.homepage = "https://github.com/upserve/aerosol"
10
10
  gem.license = 'MIT'
11
11
 
12
12
  gem.files = `git ls-files`.split($\)
@@ -16,7 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = %w{lib}
17
17
  gem.version = Aerosol::VERSION
18
18
  gem.add_dependency 'activerecord', '>= 3.2.0'
19
- gem.add_dependency 'clamp', '~> 0.6'
19
+ gem.add_dependency 'clamp', '~> 1'
20
20
  gem.add_dependency 'excon'
21
21
  gem.add_dependency 'aws-sdk-core', '~> 3'
22
22
  gem.add_dependency 'aws-sdk-s3', '~> 1'
@@ -29,7 +29,7 @@ Gem::Specification.new do |gem|
29
29
  gem.add_development_dependency 'cane'
30
30
  gem.add_development_dependency 'pry'
31
31
  gem.add_development_dependency 'rake', '~> 10.0'
32
- gem.add_development_dependency 'rspec', '< 3.0'
32
+ gem.add_development_dependency 'rspec', '~> 3'
33
33
  gem.add_development_dependency 'webmock'
34
34
  gem.add_development_dependency 'vcr'
35
35
  end
@@ -7,6 +7,12 @@ class Aerosol::AutoScaling
7
7
  :desired_capacity, :health_check_grace_period, :health_check_type, :load_balancer_names,
8
8
  :placement_group, :tags, :created_time, :vpc_zone_identifier, :target_group_arns
9
9
  aws_class_attribute :launch_configuration, Aerosol::LaunchConfiguration
10
+ aws_class_attribute(
11
+ :launch_template,
12
+ Aerosol::LaunchTemplate,
13
+ proc { |argument| argument.fetch(:launch_template, {})[:launch_template_name] }
14
+ )
15
+
10
16
  primary_key :auto_scaling_group_name
11
17
 
12
18
  def initialize(options={}, &block)
@@ -38,21 +44,30 @@ class Aerosol::AutoScaling
38
44
  end
39
45
 
40
46
  def create!
41
- ensure_present! :launch_configuration, :max_size, :min_size
47
+ ensure_present! :max_size, :min_size
42
48
  raise 'availability_zones or vpc_zone_identifier must be set' if [availability_zones, vpc_zone_identifier].none?
49
+ raise 'launch_configuration or launch_template must be set' unless launch_details
43
50
 
44
51
  info "creating auto scaling group"
45
- launch_configuration.create
52
+ launch_details = create_launch_details
53
+
46
54
  info self.inspect
47
55
 
48
56
  conn.create_auto_scaling_group({
49
57
  auto_scaling_group_name: auto_scaling_group_name,
50
58
  availability_zones: [*availability_zones],
51
- launch_configuration_name: launch_configuration.launch_configuration_name,
52
59
  max_size: max_size,
53
60
  min_size: min_size
54
- }.merge(create_options))
55
- sleep 10
61
+ }
62
+ .merge(create_options)
63
+ .merge(launch_details)
64
+ )
65
+
66
+ conn.wait_until(:group_in_service, auto_scaling_group_names: [auto_scaling_group_name]) do |waiter|
67
+ waiter.before_wait do |attempt_count, _response|
68
+ info "Wait count #{attempt_count} of #{waiter.max_attempts} for #{auto_scaling_group_name} to be in service"
69
+ end
70
+ end
56
71
  end
57
72
 
58
73
  def destroy!
@@ -60,9 +75,9 @@ class Aerosol::AutoScaling
60
75
  conn.delete_auto_scaling_group(auto_scaling_group_name: auto_scaling_group_name, force_delete: true)
61
76
  begin
62
77
  (0..2).each { break if deleting?; sleep 1 }
63
- launch_configuration.destroy
78
+ launch_details.destroy
64
79
  rescue => ex
65
- info "Launch Config: #{launch_configuration} for #{auto_scaling_group_name} was not deleted."
80
+ info "Launch Details: #{launch_details} for #{auto_scaling_group_name} were not deleted."
66
81
  info ex.message
67
82
  end
68
83
  end
@@ -78,7 +93,11 @@ class Aerosol::AutoScaling
78
93
  def all_instances
79
94
  conn.describe_auto_scaling_groups(auto_scaling_group_names: [*auto_scaling_group_name])
80
95
  .auto_scaling_groups.first
81
- .instances.map { |instance| Aerosol::Instance.from_hash(instance) }
96
+ .instances.map { |instance| Aerosol::Instance.from_hash(instance.to_hash) }
97
+ end
98
+
99
+ def launch_details
100
+ launch_configuration || launch_template
82
101
  end
83
102
 
84
103
  def tag(val)
@@ -147,6 +166,22 @@ private
147
166
  Aerosol::AWS.auto_scaling
148
167
  end
149
168
 
169
+ def create_launch_details
170
+ if launch_configuration
171
+ launch_configuration.create
172
+ { launch_configuration_name: launch_configuration.launch_configuration_name }
173
+ elsif launch_template
174
+ launch_template.create
175
+ {
176
+ launch_template:
177
+ {
178
+ launch_template_name: launch_template.launch_template_name,
179
+ version: '$Latest'
180
+ }
181
+ }
182
+ end
183
+ end
184
+
150
185
  def create_options
151
186
  {
152
187
  default_cooldown: default_cooldown,
@@ -47,13 +47,15 @@ module Aerosol::AWSModel
47
47
  aws_attributes.merge(attrs)
48
48
  end
49
49
 
50
- def aws_class_attribute(name, klass)
50
+ def aws_class_attribute(name, klass, primary_key_proc = nil)
51
51
  unless klass.ancestors.include?(Aerosol::AWSModel) || (klass == self)
52
52
  raise '.aws_class_attribute requires a Aerosol::AWSModel that is not the current class.'
53
53
  end
54
54
 
55
+ primary_key_proc ||= proc { |hash| hash[klass.primary_key] }
56
+
55
57
  dsl_class_attribute(name, klass)
56
- aws_class_attributes.merge!({ name => klass })
58
+ aws_class_attributes.merge!({ name => [klass, primary_key_proc] })
57
59
  end
58
60
 
59
61
  def exists?(key)
@@ -67,9 +69,9 @@ module Aerosol::AWSModel
67
69
 
68
70
  def from_hash(hash)
69
71
  raise 'To use .from_hash, you must specify a primary_key' if primary_key.nil?
70
- refs = Hash[aws_class_attributes.map do |name, klass|
72
+ refs = Hash[aws_class_attributes.map do |name, (klass, primary_key_proc)|
71
73
  value = klass.instances.values.find do |inst|
72
- inst.send(klass.primary_key).to_s == hash[klass.primary_key].to_s unless inst.send(klass.primary_key).nil?
74
+ inst.send(klass.primary_key).to_s == primary_key_proc.call(hash).to_s unless inst.send(klass.primary_key).nil?
73
75
  end
74
76
  [name, value]
75
77
  end].reject { |k, v| v.nil? }
@@ -5,6 +5,11 @@ class Aerosol::Instance
5
5
 
6
6
  aws_attribute :availability_zone, :health_status, :instance_id, :lifecycle_state
7
7
  aws_class_attribute :launch_configuration, Aerosol::LaunchConfiguration
8
+ aws_class_attribute(
9
+ :launch_template,
10
+ Aerosol::LaunchTemplate,
11
+ proc { |argument| argument.fetch(:launch_template, {})[:launch_template_name] }
12
+ )
8
13
  primary_key :instance_id
9
14
 
10
15
  def live?
@@ -0,0 +1,127 @@
1
+ class Aerosol::LaunchTemplate
2
+ include Aerosol::AWSModel
3
+ include Dockly::Util::Logger::Mixin
4
+
5
+ logger_prefix '[aerosol launch_template]'
6
+ aws_attribute :launch_template_name, :launch_template_id, :latest_version_number,
7
+ :image_id, :instance_type, :security_groups, :user_data,
8
+ :iam_instance_profile, :kernel_id, :key_name, :spot_price, :created_time,
9
+ :network_interfaces, :block_device_mappings, :ebs_optimized
10
+ dsl_attribute :meta_data
11
+
12
+ primary_key :launch_template_name
13
+ default_value(:security_groups) { [] }
14
+ default_value(:meta_data) { {} }
15
+
16
+ def launch_template_name(arg = nil)
17
+ if arg
18
+ raise "You cannot set the launch_template_name directly" unless from_aws
19
+ @launch_template_name = arg
20
+ else
21
+ @launch_template_name || default_identifier
22
+ end
23
+ end
24
+
25
+ def security_group(group)
26
+ security_groups << group
27
+ end
28
+
29
+ def create!
30
+ ensure_present! :image_id, :instance_type
31
+
32
+ info "creating launch template"
33
+ conn.create_launch_template(
34
+ launch_template_name: launch_template_name,
35
+ launch_template_data: {
36
+ image_id: image_id,
37
+ instance_type: instance_type,
38
+ monitoring: {
39
+ enabled: true
40
+ },
41
+ }.merge(create_options)
42
+ )
43
+
44
+ debug self.inspect
45
+ end
46
+
47
+ def destroy!
48
+ info self.to_s
49
+ raise StandardError.new('No launch_template_name found') unless launch_template_name
50
+ conn.delete_launch_template(launch_template_name: launch_template_name)
51
+ end
52
+
53
+ def all_instances
54
+ Aerosol::Instance.all.select { |instance|
55
+ !instance.launch_template.nil? &&
56
+ (instance.launch_template.launch_template_name == launch_template_name)
57
+ }.each(&:description)
58
+ end
59
+
60
+ def self.request_all_for_token(next_token)
61
+ options = next_token.nil? ? {} : { next_token: next_token }
62
+ Aerosol::AWS.compute.describe_launch_templates(options)
63
+ end
64
+
65
+ def self.request_all
66
+ next_token = nil
67
+ lts = []
68
+
69
+ begin
70
+ new_lts = request_all_for_token(next_token)
71
+ lts.concat(new_lts.launch_templates)
72
+ next_token = new_lts.next_token
73
+ end until next_token.nil?
74
+ lts
75
+ end
76
+
77
+ def to_s
78
+ %{Aerosol::LaunchTemplate { \
79
+ "launch_template_name" => "#{launch_template_name}", \
80
+ "launch_template_id" => "#{launch_template_id}", \
81
+ "latest_version_number" => "#{latest_version_number}", \
82
+ "image_id" => "#{image_id}", \
83
+ "instance_type" => "#{instance_type}", \
84
+ "security_group_ids" => #{security_groups.to_s}, \
85
+ "user_data" => "#{user_data}", \
86
+ "iam_instance_profile" => "#{iam_instance_profile}", \
87
+ "kernel_id" => "#{kernel_id}", \
88
+ "key_name" => "#{key_name}", \
89
+ "spot_price" => "#{spot_price}", \
90
+ "created_time" => "#{created_time}", \
91
+ "block_device_mappings" => #{block_device_mappings}", \
92
+ "ebs_optimized" => #{ebs_optimized} \
93
+ }}
94
+ end
95
+
96
+ def corrected_user_data
97
+ realized_user_data = user_data.is_a?(Proc) ? user_data.call : user_data
98
+
99
+ Base64.encode64(Aerosol::Util.strip_heredoc(realized_user_data || ''))
100
+ end
101
+
102
+ private
103
+ def create_options
104
+ instance_market_options = {
105
+ market_type: 'spot',
106
+ spot_options: {
107
+ max_price: spot_price
108
+ }
109
+ } if spot_price
110
+
111
+ {
112
+ iam_instance_profile: { name: iam_instance_profile },
113
+ kernel_id: kernel_id,
114
+ key_name: key_name,
115
+ security_group_ids: security_groups,
116
+ instance_market_options: instance_market_options,
117
+ user_data: corrected_user_data,
118
+ network_interfaces: network_interfaces,
119
+ block_device_mappings: block_device_mappings,
120
+ ebs_optimized: ebs_optimized,
121
+ }.reject { |k, v| v.nil? }
122
+ end
123
+
124
+ def conn
125
+ Aerosol::AWS.compute
126
+ end
127
+ end
@@ -229,7 +229,7 @@ class Aerosol::Runner
229
229
 
230
230
  def old_instances
231
231
  require_deploy!
232
- old_auto_scaling_groups.map(&:launch_configuration).compact.map(&:all_instances).flatten.compact
232
+ old_auto_scaling_groups.map(&:launch_details).compact.map(&:all_instances).flatten.compact
233
233
  end
234
234
 
235
235
  def old_auto_scaling_groups
@@ -243,6 +243,7 @@ class Aerosol::Runner
243
243
  def select_auto_scaling_groups(&block)
244
244
  require_deploy!
245
245
  Aerosol::LaunchConfiguration.all # load all of the launch configurations first
246
+ Aerosol::LaunchTemplate.all
246
247
  Aerosol::AutoScaling.all.select { |asg|
247
248
  (asg.tags['Deploy'].to_s == auto_scaling.tags['Deploy']) &&
248
249
  (block.nil? ? true : block.call(asg))
@@ -251,11 +252,13 @@ class Aerosol::Runner
251
252
 
252
253
  def new_instances
253
254
  require_deploy!
254
- while launch_configuration.all_instances.length < auto_scaling.min_size
255
+
256
+ while launch_details.all_instances.length < auto_scaling.min_size
255
257
  info "Waiting for instances to come up"
256
258
  sleep 10
257
259
  end
258
- launch_configuration.all_instances
260
+
261
+ launch_details.all_instances
259
262
  end
260
263
 
261
264
  def with_deploy(name)
@@ -280,7 +283,7 @@ class Aerosol::Runner
280
283
  :live_check, :db_config_path, :instance_live_grace_period,
281
284
  :app_port, :continue_if_stop_app_fails, :stop_app_retries,
282
285
  :is_alive?, :log_files, :tail_logs, :to => :deploy
283
- delegate :launch_configuration, :to => :auto_scaling
286
+ delegate :launch_details, :to => :auto_scaling
284
287
 
285
288
  private
286
289
 
@@ -1,5 +1,5 @@
1
1
  # Copyright Swipely, Inc. All rights reserved.
2
2
 
3
3
  module Aerosol
4
- VERSION = '1.8.0'
4
+ VERSION = '1.10.0'
5
5
  end
data/lib/aerosol.rb CHANGED
@@ -13,6 +13,7 @@ module Aerosol
13
13
  require 'aerosol/util'
14
14
  require 'aerosol/aws_model'
15
15
  require 'aerosol/launch_configuration'
16
+ require 'aerosol/launch_template'
16
17
  require 'aerosol/auto_scaling'
17
18
  require 'aerosol/instance'
18
19
  require 'aerosol/connection'
@@ -61,6 +62,7 @@ module Aerosol
61
62
  :auto_scalings => Aerosol::AutoScaling.instances,
62
63
  :deploys => Aerosol::Deploy.instances,
63
64
  :launch_configurations => Aerosol::LaunchConfiguration.instances,
65
+ :launch_templates => Aerosol::LaunchTemplate.instances,
64
66
  :sshs => Aerosol::Connection.instances,
65
67
  :envs => Aerosol::Env.instances
66
68
  }
@@ -70,6 +72,7 @@ module Aerosol
70
72
  :auto_scaling => Aerosol::AutoScaling,
71
73
  :deploy => Aerosol::Deploy,
72
74
  :launch_configuration => Aerosol::LaunchConfiguration,
75
+ :launch_template => Aerosol::LaunchTemplate,
73
76
  :ssh => Aerosol::Connection,
74
77
  :env => Aerosol::Env
75
78
  }.each do |method, klass|
@@ -82,15 +85,15 @@ module Aerosol
82
85
  end
83
86
  end
84
87
 
85
- [:auto_scalings, :deploys, :launch_configurations, :sshs, :envs].each do |method|
88
+ [:auto_scalings, :deploys, :launch_configurations, :launch_templates, :sshs, :envs].each do |method|
86
89
  define_method(method) do
87
90
  inst[method]
88
91
  end
89
92
  end
90
93
 
91
94
  module_function :inst, :load_inst, :setup, :load_file, :load_file=,
92
- :auto_scaling, :launch_configuration, :deploy, :ssh, :git_sha,
93
- :auto_scalings, :launch_configurations, :deploys, :sshs,
95
+ :auto_scaling, :launch_configuration, :launch_template, :deploy, :ssh, :git_sha,
96
+ :auto_scalings, :launch_configurations, :launch_templates, :deploys, :sshs,
94
97
  :namespace, :env, :envs, :region
95
98
  end
96
99
 
@@ -212,7 +212,7 @@ describe Aerosol::AutoScaling do
212
212
  }],
213
213
  next_token: nil
214
214
  })
215
- subject.exists?('test').should be_true
215
+ subject.exists?('test').should be true
216
216
  end
217
217
  end
218
218
 
@@ -226,7 +226,7 @@ describe Aerosol::AutoScaling do
226
226
  end
227
227
 
228
228
  it 'returns false' do
229
- subject.exists?('does-not-exist').should be_false
229
+ subject.exists?('does-not-exist').should be false
230
230
  end
231
231
  end
232
232
  end
@@ -303,7 +303,9 @@ describe Aerosol::AutoScaling do
303
303
  { auto_scaling_groups: [], next_token: nil }
304
304
  ])
305
305
  end
306
- its(:all) { should be_empty }
306
+ it 'is empty' do
307
+ expect(subject.all).to be_empty
308
+ end
307
309
  end
308
310
 
309
311
  context 'when there are auto scaling groups' do
@@ -4,7 +4,7 @@ describe "Aerosol CLI" do
4
4
  describe "running the most basic command" do
5
5
  let(:command) { "./bin/aerosol" }
6
6
  it "should exit with 0" do
7
- expect(system(command)).to be_true
7
+ expect(system(command)).to be true
8
8
  end
9
9
  end
10
10
  end
@@ -10,13 +10,16 @@ describe Aerosol::Deploy do
10
10
 
11
11
  describe '#migration' do
12
12
  context 'by default' do
13
- its(:migrate?) { should be_true }
13
+ it 'is true' do
14
+ expect(subject.migrate?).to be true
15
+ end
14
16
  end
15
17
 
16
18
  context 'when do_not_migrate! has been called' do
17
19
  before { subject.do_not_migrate! }
18
-
19
- its(:migrate?) { should be_false }
20
+ it 'is false' do
21
+ expect(subject.migrate?).to be false
22
+ end
20
23
  end
21
24
  end
22
25
 
@@ -80,7 +83,7 @@ describe Aerosol::Deploy do
80
83
  end
81
84
 
82
85
  it "returns true" do
83
- expect(subject.run_post_deploy).to be_true
86
+ expect(subject.run_post_deploy).to be true
84
87
  end
85
88
  end
86
89
 
@@ -98,7 +101,9 @@ describe Aerosol::Deploy do
98
101
 
99
102
  describe '#local_ssh_ref' do
100
103
  context 'when there is no local_ssh' do
101
- its(:local_ssh_ref) { should eq(ssh) }
104
+ it 'is ssh' do
105
+ expect(subject.local_ssh_ref).to eq(ssh)
106
+ end
102
107
  end
103
108
 
104
109
  context 'when there is a local_ssh' do
@@ -107,7 +112,9 @@ describe Aerosol::Deploy do
107
112
  subject.stub(:local_ssh).and_return(local_ssh)
108
113
  end
109
114
 
110
- its(:local_ssh_ref) { should eq(local_ssh) }
115
+ it 'is ssh' do
116
+ expect(subject.local_ssh_ref).to eq(local_ssh)
117
+ end
111
118
  end
112
119
  end
113
120
 
@@ -242,7 +242,7 @@ describe Aerosol::LaunchConfiguration do
242
242
  }],
243
243
  next_token: nil
244
244
  })
245
- subject.exists?(instance.launch_configuration_name).should be_true
245
+ subject.exists?(instance.launch_configuration_name).should be true
246
246
  end
247
247
  end
248
248
 
@@ -254,7 +254,7 @@ describe Aerosol::LaunchConfiguration do
254
254
  launch_configurations: [],
255
255
  next_token: nil
256
256
  })
257
- subject.exists?(instance.launch_configuration_name).should be_false
257
+ subject.exists?(instance.launch_configuration_name).should be false
258
258
  end
259
259
  end
260
260
  end
@@ -310,7 +310,9 @@ describe Aerosol::LaunchConfiguration do
310
310
  { launch_configurations: [], next_token: nil }
311
311
  ])
312
312
  end
313
- its(:all) { should be_empty }
313
+ it "is empty" do
314
+ expect(subject.all).to be_empty
315
+ end
314
316
  end
315
317
 
316
318
  context 'when there are launch configurations' do
@@ -0,0 +1,404 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aerosol::LaunchTemplate do
4
+ subject do
5
+ described_class.new do
6
+ name :my_launch_template
7
+ image_id '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 "#launch_template_name" do
18
+ context "with no namespace set" do
19
+ let(:identifier) { "my_launch_template-#{Aerosol::Util.git_sha}" }
20
+ it "returns a normal identifier" do
21
+ expect(subject.launch_template_name).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_template-#{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.launch_template_name).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(:@image_id, 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 template already exists' do
64
+ it 'raises an error' do
65
+ Aerosol::AWS.compute.stub_responses(
66
+ :create_launch_template,
67
+ Aws::EC2::Errors::AlreadyExists
68
+ )
69
+ expect { subject.create! }.to raise_error
70
+ end
71
+ end
72
+
73
+ context 'and the launch template does not exist yet' do
74
+ after { subject.destroy! rescue nil }
75
+
76
+ it 'creates the launch template group' do
77
+ Aerosol::AWS.compute.stub_responses(:create_launch_template, [])
78
+ expect { subject.create! }.to_not raise_error
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ describe '#destroy!' do
85
+ context 'when the launch_template_name is nil' do
86
+
87
+ it 'raises an error' do
88
+ allow(subject).to receive(:launch_template_name).and_return(nil)
89
+ Aerosol::AWS.compute.stub_responses(:delete_launch_template, [])
90
+ expect { subject.destroy! }.to raise_error
91
+ end
92
+ end
93
+
94
+ context 'when the launch_template_name is present' do
95
+ context 'but the launch template does not exist' do
96
+ it 'raises an error' do
97
+ Aerosol::AWS.compute.stub_responses(
98
+ :delete_launch_template,
99
+ Aws::EC2::Errors::ValidationError
100
+ )
101
+ expect { subject.destroy! }.to raise_error
102
+ end
103
+ end
104
+
105
+ context 'and the launch template exists' do
106
+ it 'deletes the launch template' do
107
+ Aerosol::AWS.compute.stub_responses(:delete_launch_template, [])
108
+ expect { subject.destroy! }.to_not raise_error
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ describe '#create' do
115
+ context 'when the launch_template_name is nil' do
116
+ subject do
117
+ described_class.new! do
118
+ name :random_test_name
119
+ image_id 'test-ami-who-even-cares-really'
120
+ instance_type 'm1.large'
121
+ end
122
+ end
123
+
124
+ it 'raises an error' do
125
+ allow(subject).to receive(:launch_template_name).and_return(nil)
126
+ Aerosol::AWS.compute.stub_responses(:create_launch_template, [])
127
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, {
128
+ launch_templates: [], next_token: nil
129
+ })
130
+ expect { subject.create }.to raise_error
131
+ end
132
+ end
133
+
134
+ context 'when the launch_template_name is present' do
135
+ subject do
136
+ described_class.new! do
137
+ name :random_test_name_2
138
+ image_id 'test-ami-who-even-cares-really'
139
+ instance_type 'm1.large'
140
+ end
141
+ end
142
+
143
+ context 'but the launch template already exists' do
144
+ it 'does not call #create!' do
145
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, {
146
+ launch_templates: [{
147
+ launch_template_name: subject.launch_template_name,
148
+ }],
149
+ next_token: nil
150
+ })
151
+ expect(subject).to_not receive(:create!)
152
+ subject.create
153
+ end
154
+ end
155
+
156
+ context 'and the launch template does not yet exist' do
157
+ it 'creates it' do
158
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, {
159
+ launch_templates: [],
160
+ next_token: nil
161
+ })
162
+ subject.should_receive(:create!)
163
+ subject.create
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ describe '#destroy' do
170
+ subject do
171
+ described_class.new! do
172
+ name :random_test_name_3
173
+ image_id 'awesome-ami'
174
+ instance_type 'm1.large'
175
+ end
176
+ end
177
+
178
+ context 'when the launch_template_name is nil' do
179
+ it 'raises an error' do
180
+ allow(subject).to receive(:launch_template_name).and_return(nil)
181
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, {
182
+ launch_templates: [],
183
+ next_token: nil
184
+ })
185
+ expect { subject.create }.to raise_error(ArgumentError)
186
+ end
187
+ end
188
+
189
+ context 'when the launch_template_name is present' do
190
+ context 'and the launch template already exists' do
191
+
192
+ it 'calls #destroy!' do
193
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, {
194
+ launch_templates: [{
195
+ launch_template_name: subject.launch_template_name
196
+ }],
197
+ next_token: nil
198
+ })
199
+ subject.should_receive(:destroy!)
200
+ subject.destroy
201
+ end
202
+ end
203
+
204
+ context 'but the launch template does not yet exist' do
205
+ it 'does not call #destroy!' do
206
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, {
207
+ launch_templates: [],
208
+ next_token: nil
209
+ })
210
+ subject.should_not_receive(:destroy!)
211
+ subject.destroy
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ describe '.exists?' do
218
+ subject { described_class }
219
+ let(:instance) do
220
+ subject.new! do
221
+ name :exists_test_name
222
+ image_id 'ami123'
223
+ instance_type 'm1.large'
224
+ stub(:sleep)
225
+ end
226
+ end
227
+
228
+ context 'when the argument exists' do
229
+ it 'returns true' do
230
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, {
231
+ launch_templates: [{
232
+ launch_template_name: instance.launch_template_name,
233
+ }],
234
+ next_token: nil
235
+ })
236
+ subject.exists?(instance.launch_template_name).should be true
237
+ end
238
+ end
239
+
240
+ context 'when the argument does not exist' do
241
+ let(:instance) { described_class.new! }
242
+
243
+ it 'returns false' do
244
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, {
245
+ launch_templates: [],
246
+ next_token: nil
247
+ })
248
+ subject.exists?(instance.launch_template_name).should be false
249
+ end
250
+ end
251
+ end
252
+
253
+ describe '.request_all' do
254
+ describe 'repeats until no NextToken' do
255
+ it 'should include both autoscaling groups lists' do
256
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, [
257
+ {
258
+ launch_templates: [
259
+ { launch_template_name: '1' },
260
+ { launch_template_name: '4' }
261
+ ],
262
+ next_token: 'yes'
263
+ },
264
+ {
265
+ launch_templates: [
266
+ { launch_template_name: '2' },
267
+ { launch_template_name: '3' }
268
+ ],
269
+ next_token: nil
270
+ }
271
+ ])
272
+ expect(Aerosol::LaunchTemplate.request_all.map(&:launch_template_name)).to eq(['1','4','2','3'])
273
+ end
274
+ end
275
+ end
276
+
277
+ describe '.all' do
278
+ subject { described_class }
279
+
280
+ context 'when there are no launch templates' do
281
+ before do
282
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, [
283
+ { launch_templates: [], next_token: nil }
284
+ ])
285
+ end
286
+ it 'is empty' do
287
+ expect(subject.all).to be_empty
288
+ end
289
+ end
290
+
291
+ context 'when there are launch templates' do
292
+ let(:insts) {
293
+ [
294
+ { launch_template_name: 'test' },
295
+ { launch_template_name: 'test2' }
296
+ ]
297
+ }
298
+
299
+ it 'returns each of them' do
300
+ Aerosol::AWS.compute.stub_responses(:describe_launch_templates, {
301
+ launch_templates: insts,
302
+ next_token: nil
303
+ })
304
+ subject.all.map(&:launch_template_name).should == %w[test test2]
305
+ end
306
+ end
307
+ end
308
+
309
+ describe '.from_hash' do
310
+ context 'when the launch template has not been initialized' do
311
+ subject { described_class.from_hash(hash) }
312
+ let(:hash) do
313
+ {
314
+ launch_template_name: '~test-launch-config~',
315
+ image_id: 'ami-123',
316
+ instance_type: 'm1.large',
317
+ security_groups: [],
318
+ user_data: 'echo hi',
319
+ iam_instance_profile: nil,
320
+ kernel_id: 'kernel-id',
321
+ key_name: 'key-name',
322
+ spot_price: '0.04',
323
+ }
324
+ end
325
+
326
+ it 'creates a new launch template with the specified values' do
327
+ subject.launch_template_name.should == '~test-launch-config~'
328
+ subject.image_id.should == 'ami-123'
329
+ subject.instance_type.should == 'm1.large'
330
+ subject.security_groups.should be_empty
331
+ subject.user_data.should == 'echo hi'
332
+ subject.iam_instance_profile.should be_nil
333
+ subject.kernel_id.should == 'kernel-id'
334
+ subject.spot_price.should == '0.04'
335
+ subject.from_aws = true
336
+ end
337
+
338
+ it 'generates a name' do
339
+ subject.name.to_s.should start_with 'LaunchTemplate_'
340
+ end
341
+ end
342
+
343
+ context 'when the launch template has already been initialized' do
344
+ let(:old_hash) do
345
+ {
346
+ launch_template_name: 'this-aws-id-abc-123',
347
+ image_id: 'ami-456',
348
+ }
349
+ end
350
+ let(:new_hash) { old_hash.merge(instance_type: 'm1.large') }
351
+ let!(:existing) { described_class.from_hash(old_hash) }
352
+ let(:new) { described_class.from_hash(new_hash) }
353
+
354
+ it 'makes a new instance' do
355
+ expect { new }.to change { described_class.instances.length }.by(1)
356
+ new.launch_template_name.should == 'this-aws-id-abc-123'
357
+ new.image_id.should == 'ami-456'
358
+ end
359
+ end
360
+ end
361
+
362
+ describe '#corrected_user_data' do
363
+ let(:encoded_user_data_string) { Base64.encode64('test') }
364
+
365
+ context 'when the user_data is a String' do
366
+ subject do
367
+ described_class.new do
368
+ name :corrected_user_data
369
+ user_data 'test'
370
+ end
371
+ end
372
+
373
+ it 'correctly encodes to base64' do
374
+ expect(subject.corrected_user_data).to eq(encoded_user_data_string)
375
+ end
376
+ end
377
+
378
+ context 'when the user_data is a Proc' do
379
+ subject do
380
+ described_class.new do
381
+ name :corrected_user_data_2
382
+ user_data { 'test' }
383
+ end
384
+ end
385
+
386
+ it 'correctly encodes to base64' do
387
+ expect(subject.corrected_user_data).to eq(encoded_user_data_string)
388
+ end
389
+ end
390
+ end
391
+
392
+ describe '#meta_data' do
393
+ subject do
394
+ described_class.new do
395
+ name :my_launch_template
396
+ meta_data('Test' => '1')
397
+ end
398
+ end
399
+
400
+ it 'returns the hash' do
401
+ expect(subject.meta_data['Test']).to eq('1')
402
+ end
403
+ end
404
+ end
@@ -90,7 +90,7 @@ describe Aerosol::Runner do
90
90
  ERB.stub_chain(:new, :result)
91
91
  YAML.stub(:load).and_return(conf)
92
92
  deploy.stub_chain(:migration_ssh, :with_connection).and_yield(session)
93
- Process.stub(:waitpid).and_return { 1 }
93
+ Process.stub(:waitpid).and_return(1)
94
94
  Process::Status.any_instance.stub(:exitstatus) { 0 }
95
95
  session.stub(:loop).and_yield
96
96
  end
@@ -253,6 +253,57 @@ describe Aerosol::Runner do
253
253
  end
254
254
 
255
255
  describe '#new_instances' do
256
+ context 'With a launch template' do
257
+ let!(:lt) do
258
+ Aerosol::LaunchTemplate.new! do
259
+ name :lt
260
+ image_id 'fake-ami-how-scandalous'
261
+ instance_type 'm1.large'
262
+ end
263
+ end
264
+ let!(:asg_lt) do
265
+ Aerosol::AutoScaling.new! do
266
+ name :asg_lt
267
+ availability_zones 'us-east-1'
268
+ min_size 0
269
+ max_size 3
270
+ launch_template :lt
271
+ stub(:sleep)
272
+ end
273
+ end
274
+ let!(:instance1) do
275
+ Aerosol::Instance.from_hash(
276
+ {
277
+ instance_id: 'z0',
278
+ launch_template: { launch_template_name: lt.launch_template_name }
279
+ }
280
+ )
281
+ end
282
+ let!(:instance2) do
283
+ double(
284
+ :launch_template => double(:launch_template_name => 'lc7-8891022'),
285
+ :launch_configuration => nil
286
+ )
287
+ end
288
+ let!(:instance3) do
289
+ double(
290
+ :launch_template => nil,
291
+ :launch_configuration => double(:launch_configuration_name => 'lc0-8891022')
292
+ )
293
+ end
294
+
295
+ before do
296
+ Aerosol::Instance.stub(:all).and_return([instance1, instance2, instance3])
297
+ end
298
+
299
+ it 'returns each instance that is a member of the current launch template' do
300
+ deploy = Aerosol::Deploy.new!(name: :lt_deploy, auto_scaling: :asg_lt)
301
+ subject.with_deploy(:lt_deploy) do
302
+ subject.new_instances.should == [instance1]
303
+ end
304
+ end
305
+ end
306
+
256
307
  let!(:lc7) do
257
308
  Aerosol::LaunchConfiguration.new! do
258
309
  name :lc7
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aerosol
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Swipely, Inc.
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-29 00:00:00.000000000 Z
11
+ date: 2022-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.6'
33
+ version: '1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.6'
40
+ version: '1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: excon
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -210,16 +210,16 @@ dependencies:
210
210
  name: rspec
211
211
  requirement: !ruby/object:Gem::Requirement
212
212
  requirements:
213
- - - "<"
213
+ - - "~>"
214
214
  - !ruby/object:Gem::Version
215
- version: '3.0'
215
+ version: '3'
216
216
  type: :development
217
217
  prerelease: false
218
218
  version_requirements: !ruby/object:Gem::Requirement
219
219
  requirements:
220
- - - "<"
220
+ - - "~>"
221
221
  - !ruby/object:Gem::Version
222
- version: '3.0'
222
+ version: '3'
223
223
  - !ruby/object:Gem::Dependency
224
224
  name: webmock
225
225
  requirement: !ruby/object:Gem::Requirement
@@ -259,9 +259,9 @@ extensions: []
259
259
  extra_rdoc_files: []
260
260
  files:
261
261
  - ".cane"
262
+ - ".github/workflows/unit_test.yml"
262
263
  - ".gitignore"
263
264
  - ".rspec"
264
- - ".travis.yml"
265
265
  - Gemfile
266
266
  - LICENSE.txt
267
267
  - README.md
@@ -280,6 +280,7 @@ files:
280
280
  - lib/aerosol/env.rb
281
281
  - lib/aerosol/instance.rb
282
282
  - lib/aerosol/launch_configuration.rb
283
+ - lib/aerosol/launch_template.rb
283
284
  - lib/aerosol/rake_task.rb
284
285
  - lib/aerosol/runner.rb
285
286
  - lib/aerosol/util.rb
@@ -292,16 +293,17 @@ files:
292
293
  - spec/aerosol/env_spec.rb
293
294
  - spec/aerosol/instance_spec.rb
294
295
  - spec/aerosol/launch_configuration_spec.rb
296
+ - spec/aerosol/launch_template_spec.rb
295
297
  - spec/aerosol/rake_task_spec.rb
296
298
  - spec/aerosol/runner_spec.rb
297
299
  - spec/aerosol_spec.rb
298
300
  - spec/spec_helper.rb
299
301
  - spec/support/vcr.rb
300
- homepage: https://github.com/swipely/aerosol
302
+ homepage: https://github.com/upserve/aerosol
301
303
  licenses:
302
304
  - MIT
303
305
  metadata: {}
304
- post_install_message:
306
+ post_install_message:
305
307
  rdoc_options: []
306
308
  require_paths:
307
309
  - lib
@@ -316,8 +318,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
316
318
  - !ruby/object:Gem::Version
317
319
  version: '0'
318
320
  requirements: []
319
- rubygems_version: 3.0.3
320
- signing_key:
321
+ rubyforge_project:
322
+ rubygems_version: 2.6.11
323
+ signing_key:
321
324
  specification_version: 4
322
325
  summary: Instance-based deploys made easy
323
326
  test_files:
@@ -329,8 +332,10 @@ test_files:
329
332
  - spec/aerosol/env_spec.rb
330
333
  - spec/aerosol/instance_spec.rb
331
334
  - spec/aerosol/launch_configuration_spec.rb
335
+ - spec/aerosol/launch_template_spec.rb
332
336
  - spec/aerosol/rake_task_spec.rb
333
337
  - spec/aerosol/runner_spec.rb
334
338
  - spec/aerosol_spec.rb
335
339
  - spec/spec_helper.rb
336
340
  - spec/support/vcr.rb
341
+ has_rdoc:
data/.travis.yml DELETED
@@ -1,10 +0,0 @@
1
- sudo: false
2
- dist: trusty
3
- language: ruby
4
- rvm:
5
- - 2.0
6
- - 2.1
7
- - 2.2
8
- cache: bundler
9
- before_install: gem install bundler -v 1.17.3
10
- script: CI=true bundle exec rake