aerosol 1.8.0 → 1.10.0

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