aerosol 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.cane +3 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +20 -0
- data/README.md +399 -0
- data/Rakefile +18 -0
- data/aerosol.gemspec +32 -0
- data/bin/aerosol +8 -0
- data/img/aerosol.pdf +3898 -11
- data/img/aerosol.png +0 -0
- data/lib/aerosol/auto_scaling.rb +240 -0
- data/lib/aerosol/aws.rb +62 -0
- data/lib/aerosol/aws_model.rb +93 -0
- data/lib/aerosol/cli.rb +41 -0
- data/lib/aerosol/connection.rb +39 -0
- data/lib/aerosol/deploy.rb +105 -0
- data/lib/aerosol/env.rb +6 -0
- data/lib/aerosol/instance.rb +55 -0
- data/lib/aerosol/launch_configuration.rb +106 -0
- data/lib/aerosol/rake_task.rb +141 -0
- data/lib/aerosol/runner.rb +329 -0
- data/lib/aerosol/util.rb +41 -0
- data/lib/aerosol/version.rb +5 -0
- data/lib/aerosol.rb +83 -0
- data/spec/aerosol/auto_scaling_spec.rb +420 -0
- data/spec/aerosol/aws_spec.rb +24 -0
- data/spec/aerosol/cli_spec.rb +10 -0
- data/spec/aerosol/connection_spec.rb +94 -0
- data/spec/aerosol/deploy_spec.rb +192 -0
- data/spec/aerosol/env_spec.rb +16 -0
- data/spec/aerosol/instance_spec.rb +57 -0
- data/spec/aerosol/launch_configuration_spec.rb +328 -0
- data/spec/aerosol/rake_task_spec.rb +19 -0
- data/spec/aerosol/runner_spec.rb +482 -0
- data/spec/aerosol_spec.rb +41 -0
- data/spec/fixtures/Procfile +1 -0
- data/spec/fixtures/Rakefile +17 -0
- data/spec/fixtures/not_a_tar-2.txt +1 -0
- data/spec/fixtures/not_a_tar.txt +1 -0
- data/spec/fixtures/tar-2.tar +0 -0
- data/spec/fixtures/test-1.tar +0 -0
- data/spec/fixtures/test-2.tar.gz +0 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/vcr.rb +11 -0
- 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
- metadata +312 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
class Aerosol::LaunchConfiguration
|
2
|
+
include Aerosol::AWSModel
|
3
|
+
include Dockly::Util::Logger::Mixin
|
4
|
+
|
5
|
+
logger_prefix '[aerosol launch_configuration]'
|
6
|
+
aws_attribute :aws_identifier => 'LaunchConfigurationName',
|
7
|
+
:ami => 'ImageId',
|
8
|
+
:instance_type => 'InstanceType',
|
9
|
+
:security_groups => 'SecurityGroups',
|
10
|
+
:user_data => 'UserData',
|
11
|
+
:iam_role => 'IamInstanceProfile',
|
12
|
+
:kernel_id => 'KernelId',
|
13
|
+
:key_name => 'KeyName',
|
14
|
+
:spot_price => 'SpotPrice',
|
15
|
+
:created_time => 'CreatedTime',
|
16
|
+
:associate_public_ip_address => 'AssociatePublicIpAddress'
|
17
|
+
|
18
|
+
primary_key :aws_identifier
|
19
|
+
default_value(:security_groups) { [] }
|
20
|
+
|
21
|
+
def aws_identifier(arg = nil)
|
22
|
+
if arg
|
23
|
+
raise "You cannot set the aws_identifer directly" unless from_aws
|
24
|
+
@aws_identifier = arg
|
25
|
+
else
|
26
|
+
@aws_identifier || default_identifier
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def security_group(group)
|
31
|
+
security_groups << group
|
32
|
+
end
|
33
|
+
|
34
|
+
def create!
|
35
|
+
ensure_present! :ami, :instance_type
|
36
|
+
|
37
|
+
info self.to_s
|
38
|
+
conn.create_launch_configuration(ami, instance_type, aws_identifier, create_options)
|
39
|
+
sleep 10 # TODO: switch to fog models and .wait_for { ready? }
|
40
|
+
end
|
41
|
+
|
42
|
+
def destroy!
|
43
|
+
info self.to_s
|
44
|
+
conn.delete_launch_configuration(aws_identifier)
|
45
|
+
end
|
46
|
+
|
47
|
+
def all_instances
|
48
|
+
Aerosol::Instance.all.select { |instance|
|
49
|
+
!instance.launch_configuration.nil? &&
|
50
|
+
(instance.launch_configuration.aws_identifier == self.aws_identifier)
|
51
|
+
}.each(&:description)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.request_all_for_token(next_token)
|
55
|
+
options = next_token.nil? ? {} : { 'NextToken' => next_token }
|
56
|
+
Aerosol::AWS.auto_scaling
|
57
|
+
.describe_launch_configurations(options)
|
58
|
+
.body
|
59
|
+
.[]('DescribeLaunchConfigurationsResult')
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.request_all
|
63
|
+
next_token = nil
|
64
|
+
lcs = []
|
65
|
+
|
66
|
+
begin
|
67
|
+
new_lcs = request_all_for_token(next_token)
|
68
|
+
lcs.concat(new_lcs['LaunchConfigurations'])
|
69
|
+
next_token = new_lcs['NextToken']
|
70
|
+
end while !next_token.nil?
|
71
|
+
|
72
|
+
lcs
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_s
|
76
|
+
%{Aerosol::LaunchConfiguration { \
|
77
|
+
"aws_identifier" => "#{aws_identifier}", \
|
78
|
+
"ami" => "#{ami}", \
|
79
|
+
"instance_type" => "#{instance_type}", \
|
80
|
+
"security_groups" => #{security_groups.to_s}, \
|
81
|
+
"user_data" => "#{user_data}", \
|
82
|
+
"iam_role" => "#{iam_role}", \
|
83
|
+
"kernel_id" => "#{kernel_id}", \
|
84
|
+
"key_name" => "#{key_name}", \
|
85
|
+
"spot_price" => "#{spot_price}", \
|
86
|
+
"created_time" => "#{created_time}" \
|
87
|
+
}}
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def create_options
|
92
|
+
{ # TODO Add dsl so that 'BlockDeviceMappings' may be specified
|
93
|
+
'IamInstanceProfile' => iam_role,
|
94
|
+
'KernelId' => kernel_id,
|
95
|
+
'KeyName' => key_name,
|
96
|
+
'SecurityGroups' => security_groups,
|
97
|
+
'SpotPrice' => spot_price,
|
98
|
+
'UserData' => Aerosol::Util.strip_heredoc(user_data || ''),
|
99
|
+
'AssociatePublicIpAddress' => associate_public_ip_address
|
100
|
+
}.reject { |k, v| v.nil? }
|
101
|
+
end
|
102
|
+
|
103
|
+
def conn
|
104
|
+
Aerosol::AWS.auto_scaling
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'aerosol'
|
3
|
+
|
4
|
+
$rake_task_logger = Dockly::Util::Logger.new('[aerosol rake_task]', STDOUT, false)
|
5
|
+
|
6
|
+
class Rake::AutoScalingTask < Rake::Task
|
7
|
+
def needed?
|
8
|
+
!auto_scaling.exists?
|
9
|
+
end
|
10
|
+
|
11
|
+
def auto_scaling
|
12
|
+
Aerosol.auto_scaling(name.split(':').last.to_sym)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Rake::DSL
|
17
|
+
def auto_scaling(*args, &block)
|
18
|
+
Rake::AutoScalingTask.define_task(*args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
namespace :aerosol do
|
23
|
+
desc "Verify an aerosol.rb file exists"
|
24
|
+
task :load do
|
25
|
+
raise "No aerosol.rb found!" unless File.exist?('aerosol.rb')
|
26
|
+
end
|
27
|
+
|
28
|
+
namespace :auto_scaling do
|
29
|
+
Aerosol.auto_scalings.values.reject(&:from_aws).each do |inst|
|
30
|
+
auto_scaling inst.name => 'aerosol:load' do |name|
|
31
|
+
Thread.current[:rake_task] = name
|
32
|
+
inst.create
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
namespace :ssh do
|
38
|
+
Aerosol.deploys.values.each do |inst|
|
39
|
+
desc "Prints out ssh command to all instances of the latest deploy of #{inst.name}"
|
40
|
+
task inst.name do |name|
|
41
|
+
Thread.current[:rake_task] = name
|
42
|
+
inst.generate_ssh_commands.each do |ssh_command|
|
43
|
+
puts ssh_command
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
all_deploy_tasks = []
|
50
|
+
all_asynch_deploy_tasks = []
|
51
|
+
|
52
|
+
namespace :env do
|
53
|
+
Aerosol.envs.values.each do |env|
|
54
|
+
desc "Run all of the deploys for #{env.name} in parallel"
|
55
|
+
multitask env.name => env.deploy.map { |dep| "aerosol:#{dep.name}:all" }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
Aerosol.deploys.values.each do |inst|
|
61
|
+
namespace :"#{inst.name}" do
|
62
|
+
desc "Runs the ActiveRecord migration through the SSH connection given"
|
63
|
+
task :run_migration => 'aerosol:load' do |name|
|
64
|
+
Thread.current[:rake_task] = name
|
65
|
+
Aerosol::Runner.new.with_deploy(inst.name) do |runner|
|
66
|
+
runner.run_migration
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "Creates a new auto scaling group for the current git hash"
|
71
|
+
task :create_auto_scaling_group => "aerosol:auto_scaling:#{inst.auto_scaling.name}"
|
72
|
+
|
73
|
+
desc "Waits for instances of the new autoscaling groups to start up"
|
74
|
+
task :wait_for_new_instances => 'aerosol:load' do |name|
|
75
|
+
Thread.current[:rake_task] = name
|
76
|
+
Aerosol::Runner.new.with_deploy(inst.name) do |runner|
|
77
|
+
runner.wait_for_new_instances
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "Runs command to shut down the application on the old instances instead of just terminating"
|
82
|
+
task :stop_old_app => 'aerosol:load' do |name|
|
83
|
+
Thread.current[:rake_task] = name
|
84
|
+
Aerosol::Runner.new.with_deploy(inst.name) do |runner|
|
85
|
+
runner.stop_app
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "Terminates instances with the current tag and different git hash"
|
90
|
+
task :destroy_old_auto_scaling_groups => 'aerosol:load' do |name|
|
91
|
+
Thread.current[:rake_task] = name
|
92
|
+
Aerosol::Runner.new.with_deploy(inst.name) do |runner|
|
93
|
+
runner.destroy_old_auto_scaling_groups
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "Terminates instances with the current tag and current git hash"
|
98
|
+
task :destroy_new_auto_scaling_groups => 'aerosol:load' do |name|
|
99
|
+
Thread.current[:rake_task] = name
|
100
|
+
Aerosol::Runner.new.with_deploy(inst.name) do |runner|
|
101
|
+
runner.destroy_new_auto_scaling_groups
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
desc "Runs a post deploy command"
|
106
|
+
task :run_post_deploy => 'aerosol:load' do |name|
|
107
|
+
Thread.current[:rake_task] = name
|
108
|
+
inst.run_post_deploy
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
|
113
|
+
desc "Runs migration and creates auto scaling groups"
|
114
|
+
task :all_prep => [:run_migration, :create_auto_scaling_group]
|
115
|
+
|
116
|
+
desc "Waits for new instances, stops old application, destroys old auto scaling groups "\
|
117
|
+
"and runs the post deploy command"
|
118
|
+
task :all_release => [:wait_for_new_instances, :stop_old_app, :destroy_old_auto_scaling_groups, :run_post_deploy]
|
119
|
+
|
120
|
+
desc "Run migration, create auto scaling group, wait for instances, stop old application, "\
|
121
|
+
"destroy old auto scaling groups and run the post deploy command"
|
122
|
+
task :all => [:all_prep, :all_release]
|
123
|
+
all_deploy_tasks << "aerosol:#{inst.name}:all"
|
124
|
+
|
125
|
+
##
|
126
|
+
|
127
|
+
desc "Runs migration and creates auto scaling groups in parallel"
|
128
|
+
multitask :all_asynch_prep => [:run_migration, :create_auto_scaling_group]
|
129
|
+
|
130
|
+
desc "Same as `all` but runs the migration and creates auto scaling groups in parallel"
|
131
|
+
task :all_asynch => [:all_asynch_prep, :all_release]
|
132
|
+
all_asynch_deploy_tasks << "aerosol:#{inst.name}:all_asynch"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
desc "Runs all the all deploy tasks in the aerosol.rb"
|
137
|
+
task :deploy_all => all_deploy_tasks
|
138
|
+
|
139
|
+
desc "Runs all the all deploy tasks in the aerosol.rb in parallel"
|
140
|
+
multitask :deploy_all_asynch => all_asynch_deploy_tasks
|
141
|
+
end
|
@@ -0,0 +1,329 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'active_record'
|
3
|
+
require 'grit'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
class Aerosol::Runner
|
7
|
+
extend Dockly::Util::Delegate
|
8
|
+
include Dockly::Util::Logger::Mixin
|
9
|
+
|
10
|
+
logger_prefix '[aerosol runner]'
|
11
|
+
attr_reader :deploy, :log_pids
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@log_pids = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_migration
|
18
|
+
require_deploy!
|
19
|
+
return unless deploy.migrate?
|
20
|
+
raise 'To run a migration, $RAILS_ENV must be set.' if ENV['RAILS_ENV'].nil?
|
21
|
+
|
22
|
+
info "running migration"
|
23
|
+
begin
|
24
|
+
info "loading config for env #{ENV['RAILS_ENV']}"
|
25
|
+
original_config = YAML.load(ERB.new(File.read(db_config_path)).result)[ENV['RAILS_ENV']]
|
26
|
+
debug "creating ssh tunnel"
|
27
|
+
migration_ssh.with_connection do |session|
|
28
|
+
# session.logger.sev_threshold=Logger::Severity::DEBUG
|
29
|
+
debug "finding free port"
|
30
|
+
port = random_open_port
|
31
|
+
db_port = original_config['port'] || 3306 # TODO: get default port from DB driver
|
32
|
+
host = original_config['host']
|
33
|
+
info "forwarding 127.0.0.1:#{port} --> #{host}:#{db_port}"
|
34
|
+
session.forward.local(port, host, db_port)
|
35
|
+
child = fork do
|
36
|
+
GC.disable
|
37
|
+
with_prefix('child:') do |logger|
|
38
|
+
logger.debug "establishing connection"
|
39
|
+
ActiveRecord::Base.establish_connection(original_config.merge(
|
40
|
+
'host' => '127.0.0.1',
|
41
|
+
'port' => port
|
42
|
+
))
|
43
|
+
logger.info "running migration"
|
44
|
+
ActiveRecord::Migrator.migrate(%w[db/migrate])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
debug "waiting for child"
|
48
|
+
exitstatus = nil
|
49
|
+
session.loop(0.1) do
|
50
|
+
pid = Process.waitpid(child, Process::WNOHANG)
|
51
|
+
exitstatus = $?.exitstatus if !pid.nil?
|
52
|
+
pid.nil?
|
53
|
+
end
|
54
|
+
raise "migration failed: #{exitstatus}" unless exitstatus == 0
|
55
|
+
end
|
56
|
+
info "complete"
|
57
|
+
ensure
|
58
|
+
ActiveRecord::Base.clear_all_connections!
|
59
|
+
end
|
60
|
+
info "migration ran"
|
61
|
+
end
|
62
|
+
|
63
|
+
def wait_for_new_instances
|
64
|
+
require_deploy!
|
65
|
+
info "waiting for new instances"
|
66
|
+
|
67
|
+
live_instances = []
|
68
|
+
Timeout.timeout(instance_live_grace_period) do
|
69
|
+
loop do
|
70
|
+
current_instances = new_instances
|
71
|
+
remaining_instances = current_instances - live_instances
|
72
|
+
info "waiting for instances to be live (#{remaining_instances.count} remaining)"
|
73
|
+
debug "current instances: #{current_instances.map(&:id)}"
|
74
|
+
debug "live instances: #{live_instances.map(&:id)}"
|
75
|
+
live_instances.concat(remaining_instances.select { |instance| healthy?(instance) })
|
76
|
+
break if (current_instances - live_instances).empty?
|
77
|
+
debug 'sleeping for 10 seconds'
|
78
|
+
sleep(10)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
info 'new instances are up'
|
83
|
+
rescue Timeout::Error
|
84
|
+
raise "[aerosol runner] site live check timed out after #{instance_live_grace_period} seconds"
|
85
|
+
ensure
|
86
|
+
log_pids.each do |instance_id, fork|
|
87
|
+
debug "Killing tailing for #{instance_id}: #{Time.now}"
|
88
|
+
Process.kill('HUP', fork)
|
89
|
+
debug "Killed process for #{instance_id}: #{Time.now}"
|
90
|
+
debug "Waiting for process to die"
|
91
|
+
Process.wait(fork)
|
92
|
+
debug "Process ended for #{instance_id}: #{Time.now}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def healthy?(instance)
|
97
|
+
debug "Checking if #{instance.id} is healthy"
|
98
|
+
|
99
|
+
unless instance.live?
|
100
|
+
debug "#{instance.id} is not live"
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
|
104
|
+
debug "trying to SSH to #{instance.id}"
|
105
|
+
success = false
|
106
|
+
ssh.with_connection(instance) do |session|
|
107
|
+
start_tailing_logs(ssh, instance) if log_pids[instance.id].nil?
|
108
|
+
debug "checking if #{instance.id} is healthy"
|
109
|
+
success = if is_alive?.nil?
|
110
|
+
debug 'Using default site live check'
|
111
|
+
check_site_live(session)
|
112
|
+
else
|
113
|
+
debug 'Using custom site live check'
|
114
|
+
is_alive?.call(session, self)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
if success
|
119
|
+
debug "#{instance.id} is healthy"
|
120
|
+
else
|
121
|
+
debug "#{instance.id} is not healthy"
|
122
|
+
end
|
123
|
+
success
|
124
|
+
rescue => ex
|
125
|
+
debug "#{instance.id} is not healthy: #{ex.message}"
|
126
|
+
false
|
127
|
+
end
|
128
|
+
|
129
|
+
def check_site_live(session)
|
130
|
+
command = [
|
131
|
+
'wget',
|
132
|
+
'-q',
|
133
|
+
# Since we're hitting localhost, the cert will always be invalid, so don't try to check it.
|
134
|
+
deploy.ssl ? '--no-check-certificate' : nil,
|
135
|
+
"'#{deploy.live_check_url}'",
|
136
|
+
'-O',
|
137
|
+
'/dev/null'
|
138
|
+
].compact.join(' ')
|
139
|
+
|
140
|
+
debug "running #{command}"
|
141
|
+
ret = ssh_exec!(session, command)
|
142
|
+
debug "finished running #{command}"
|
143
|
+
ret[:exit_status].zero?
|
144
|
+
end
|
145
|
+
|
146
|
+
def start_tailing_logs(ssh, instance)
|
147
|
+
if tail_logs && log_files.length > 0
|
148
|
+
command = [
|
149
|
+
'sudo', 'tail', '-f', *log_files
|
150
|
+
].join(' ')
|
151
|
+
|
152
|
+
log_pids[instance.id] ||= ssh_fork(command, ssh, instance)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def ssh_fork(command, ssh, instance)
|
157
|
+
debug 'starting ssh fork'
|
158
|
+
fork do
|
159
|
+
Signal.trap('HUP') do
|
160
|
+
debug 'Killing tailing session'
|
161
|
+
Process.exit!
|
162
|
+
end
|
163
|
+
debug 'starting tail'
|
164
|
+
begin
|
165
|
+
ssh.with_connection(instance) do |session|
|
166
|
+
debug 'tailing session connected'
|
167
|
+
buffer = ''
|
168
|
+
ssh_exec!(session, command) do |stream, data|
|
169
|
+
data.lines.each do |line|
|
170
|
+
if line.end_with?($/)
|
171
|
+
debug "[#{instance.id}] #{stream}: #{buffer + line}"
|
172
|
+
buffer = ''
|
173
|
+
else
|
174
|
+
buffer = line
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
rescue => ex
|
180
|
+
error "#{ex.class}: #{ex.message}"
|
181
|
+
error "#{ex.backtrace.join("\n")}"
|
182
|
+
ensure
|
183
|
+
debug 'finished'
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def stop_app
|
189
|
+
info "stopping old app"
|
190
|
+
to_stop = old_instances
|
191
|
+
|
192
|
+
info "starting with #{to_stop.length} instances to stop"
|
193
|
+
|
194
|
+
stop_app_retries.succ.times do |n|
|
195
|
+
break if to_stop.empty?
|
196
|
+
debug "stop app: #{to_stop.length} instances remaining"
|
197
|
+
to_stop.reject! { |instance| stop_one_app(instance) }
|
198
|
+
end
|
199
|
+
|
200
|
+
if to_stop.length.zero?
|
201
|
+
info "successfully stopped the app on each old instance"
|
202
|
+
elsif !continue_if_stop_app_fails
|
203
|
+
raise "Failed to stop app on #{to_stop.length} instances"
|
204
|
+
end
|
205
|
+
info "stopped old app"
|
206
|
+
end
|
207
|
+
|
208
|
+
def destroy_old_auto_scaling_groups
|
209
|
+
require_deploy!
|
210
|
+
info "destroying old autoscaling groups"
|
211
|
+
sleep deploy.sleep_before_termination
|
212
|
+
old_auto_scaling_groups.map(&:destroy)
|
213
|
+
info "destroyed old autoscaling groups"
|
214
|
+
end
|
215
|
+
|
216
|
+
def destroy_new_auto_scaling_groups
|
217
|
+
require_deploy!
|
218
|
+
info "destroying autoscaling groups created for this sha"
|
219
|
+
new_auto_scaling_groups.map(&:destroy)
|
220
|
+
info "destroyed new autoscaling groups"
|
221
|
+
end
|
222
|
+
|
223
|
+
def old_instances
|
224
|
+
require_deploy!
|
225
|
+
old_auto_scaling_groups.map(&:launch_configuration).compact.map(&:all_instances).flatten.compact
|
226
|
+
end
|
227
|
+
|
228
|
+
def old_auto_scaling_groups
|
229
|
+
select_auto_scaling_groups { |asg| asg.tags['GitSha'] != auto_scaling.tags['GitSha'] }
|
230
|
+
end
|
231
|
+
|
232
|
+
def new_auto_scaling_groups
|
233
|
+
select_auto_scaling_groups { |asg| asg.tags['GitSha'] == auto_scaling.tags['GitSha'] }
|
234
|
+
end
|
235
|
+
|
236
|
+
def select_auto_scaling_groups(&block)
|
237
|
+
require_deploy!
|
238
|
+
Aerosol::LaunchConfiguration.all # load all of the launch configurations first
|
239
|
+
Aerosol::AutoScaling.all.select { |asg|
|
240
|
+
(asg.tags['Deploy'].to_s == auto_scaling.tags['Deploy']) &&
|
241
|
+
(block.nil? ? true : block.call(asg))
|
242
|
+
}
|
243
|
+
end
|
244
|
+
|
245
|
+
def new_instances
|
246
|
+
require_deploy!
|
247
|
+
while launch_configuration.all_instances.length < auto_scaling.min_size
|
248
|
+
info "Waiting for instances to come up"
|
249
|
+
sleep 10
|
250
|
+
end
|
251
|
+
launch_configuration.all_instances
|
252
|
+
end
|
253
|
+
|
254
|
+
def with_deploy(name)
|
255
|
+
unless dep = Aerosol::Deploy[name]
|
256
|
+
raise "No deploy named '#{name}'"
|
257
|
+
end
|
258
|
+
original = @deploy
|
259
|
+
@deploy = dep
|
260
|
+
yield self
|
261
|
+
@deploy = original
|
262
|
+
end
|
263
|
+
|
264
|
+
def require_deploy!
|
265
|
+
raise "@deploy must be present" if deploy.nil?
|
266
|
+
end
|
267
|
+
|
268
|
+
def git_sha
|
269
|
+
@git_sha ||= Aerosol::Util.git_sha
|
270
|
+
end
|
271
|
+
|
272
|
+
delegate :ssh, :migration_ssh, :package, :auto_scaling, :stop_command,
|
273
|
+
:live_check, :db_config_path, :instance_live_grace_period,
|
274
|
+
:app_port, :continue_if_stop_app_fails, :stop_app_retries,
|
275
|
+
:is_alive?, :log_files, :tail_logs, :to => :deploy
|
276
|
+
delegate :launch_configuration, :to => :auto_scaling
|
277
|
+
|
278
|
+
private
|
279
|
+
|
280
|
+
def stop_one_app(instance)
|
281
|
+
debug "attempting to stop app on: #{instance.public_hostname}"
|
282
|
+
ssh.with_connection(instance) do |session|
|
283
|
+
session.exec!(stop_command)
|
284
|
+
session.loop
|
285
|
+
end
|
286
|
+
info "successfully stopped app on: #{instance.public_hostname}"
|
287
|
+
true
|
288
|
+
rescue => ex
|
289
|
+
warn "stop app failed on #{instance.public_hostname} due to: #{ex}"
|
290
|
+
false
|
291
|
+
end
|
292
|
+
|
293
|
+
# inspired by: http://stackoverflow.com/questions/3386233/how-to-get-exit-status-with-rubys-netssh-library
|
294
|
+
def ssh_exec!(ssh, command, options = {}, &block)
|
295
|
+
res = { :out => "", :err => "", :exit_status => nil }
|
296
|
+
ssh.open_channel do |channel|
|
297
|
+
if options[:tty]
|
298
|
+
channel.request_pty do |ch, success|
|
299
|
+
raise "could not start a pseudo-tty" unless success
|
300
|
+
channel = ch
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
channel.exec(command) do |ch, success|
|
305
|
+
raise "unable to run remote cmd: #{command}" unless success
|
306
|
+
|
307
|
+
channel.on_data do |_, data|
|
308
|
+
block.call(:out, data) unless block.nil?
|
309
|
+
res[:out] << data
|
310
|
+
end
|
311
|
+
channel.on_extended_data do |_, type, data|
|
312
|
+
block.call(:err, data) unless block.nil?
|
313
|
+
res[:err] << data
|
314
|
+
end
|
315
|
+
channel.on_request("exit-status") { |_, data| res[:exit_status] = data.read_long }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
ssh.loop
|
319
|
+
res
|
320
|
+
end
|
321
|
+
|
322
|
+
def random_open_port
|
323
|
+
socket = Socket.new(:INET, :STREAM, 0)
|
324
|
+
socket.bind(Addrinfo.tcp("127.0.0.1", 0))
|
325
|
+
port = socket.local_address.ip_port
|
326
|
+
socket.close
|
327
|
+
port
|
328
|
+
end
|
329
|
+
end
|
data/lib/aerosol/util.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'grit'
|
2
|
+
|
3
|
+
module Aerosol::Util
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def is_tar?(path)
|
7
|
+
if File.size(path) < 262
|
8
|
+
return false
|
9
|
+
end
|
10
|
+
magic = nil
|
11
|
+
File.open(path, "r") do |f|
|
12
|
+
f.read(257)
|
13
|
+
magic = f.read(5)
|
14
|
+
end
|
15
|
+
magic == "ustar"
|
16
|
+
end
|
17
|
+
|
18
|
+
def is_gzip?(path)
|
19
|
+
if File.size(path) < 2
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
magic = nil
|
23
|
+
File.open(path, "r") do |f|
|
24
|
+
magic = f.read(2)
|
25
|
+
end
|
26
|
+
magic = magic.unpack('H*')[0]
|
27
|
+
magic == "1f8b"
|
28
|
+
end
|
29
|
+
|
30
|
+
def strip_heredoc(str)
|
31
|
+
str.gsub(/^#{str[/\A\s*/]}/, '')
|
32
|
+
end
|
33
|
+
|
34
|
+
def git_repo
|
35
|
+
@git_repo ||= Grit::Repo.new('.')
|
36
|
+
end
|
37
|
+
|
38
|
+
def git_sha
|
39
|
+
@git_sha ||= git_repo.git.show.lines.first.chomp.match(/^commit ([a-f0-9]+)$/)[1][0..6] rescue 'unknown'
|
40
|
+
end
|
41
|
+
end
|
data/lib/aerosol.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'dockly/util'
|
3
|
+
|
4
|
+
module Aerosol
|
5
|
+
require 'aerosol/aws'
|
6
|
+
require 'aerosol/util'
|
7
|
+
require 'aerosol/aws_model'
|
8
|
+
require 'aerosol/launch_configuration'
|
9
|
+
require 'aerosol/auto_scaling'
|
10
|
+
require 'aerosol/instance'
|
11
|
+
require 'aerosol/connection'
|
12
|
+
require 'aerosol/deploy'
|
13
|
+
require 'aerosol/env'
|
14
|
+
|
15
|
+
attr_reader :deploy, :instance, :git_sha, :namespace
|
16
|
+
attr_writer :load_file
|
17
|
+
|
18
|
+
LOAD_FILE = 'aerosol.rb'
|
19
|
+
|
20
|
+
def load_file
|
21
|
+
@load_file || LOAD_FILE
|
22
|
+
end
|
23
|
+
|
24
|
+
def inst
|
25
|
+
@instance ||= load_inst
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_inst
|
29
|
+
setup.tap do |state|
|
30
|
+
if File.exists?(load_file)
|
31
|
+
instance_eval(IO.read(load_file), load_file)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def namespace(value = nil)
|
37
|
+
if value.nil?
|
38
|
+
@namespace
|
39
|
+
else
|
40
|
+
@namespace = value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup
|
45
|
+
{
|
46
|
+
:auto_scalings => Aerosol::AutoScaling.instances,
|
47
|
+
:deploys => Aerosol::Deploy.instances,
|
48
|
+
:launch_configurations => Aerosol::LaunchConfiguration.instances,
|
49
|
+
:sshs => Aerosol::Connection.instances,
|
50
|
+
:envs => Aerosol::Env.instances
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
{
|
55
|
+
:auto_scaling => Aerosol::AutoScaling,
|
56
|
+
:deploy => Aerosol::Deploy,
|
57
|
+
:launch_configuration => Aerosol::LaunchConfiguration,
|
58
|
+
:ssh => Aerosol::Connection,
|
59
|
+
:env => Aerosol::Env
|
60
|
+
}.each do |method, klass|
|
61
|
+
define_method(method) do |sym, &block|
|
62
|
+
if block.nil?
|
63
|
+
inst[:"#{method}s"][sym]
|
64
|
+
else
|
65
|
+
klass.new!(:name => sym, &block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
[:auto_scalings, :deploys, :launch_configurations, :sshs, :envs].each do |method|
|
71
|
+
define_method(method) do
|
72
|
+
inst[method]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
module_function :inst, :load_inst, :setup, :load_file, :load_file=,
|
77
|
+
:auto_scaling, :launch_configuration, :deploy, :ssh, :git_sha,
|
78
|
+
:auto_scalings, :launch_configurations, :deploys, :sshs,
|
79
|
+
:namespace, :env, :envs
|
80
|
+
end
|
81
|
+
|
82
|
+
require 'aerosol/runner'
|
83
|
+
require 'aerosol/rake_task'
|