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
data/img/aerosol.png
ADDED
Binary file
|
@@ -0,0 +1,240 @@
|
|
1
|
+
class Aerosol::AutoScaling
|
2
|
+
include Aerosol::AWSModel
|
3
|
+
include Dockly::Util::Logger::Mixin
|
4
|
+
|
5
|
+
logger_prefix '[aerosol auto_scaling]'
|
6
|
+
aws_attribute :aws_identifier => 'AutoScalingGroupName',
|
7
|
+
:availability_zones => 'AvailabilityZones',
|
8
|
+
:min_size => 'MinSize',
|
9
|
+
:max_size => 'MaxSize',
|
10
|
+
:default_cooldown => 'DefaultCooldown',
|
11
|
+
:desired_capacity => 'DesiredCapacity',
|
12
|
+
:health_check_grace_period => 'HealthCheckGracePeriod',
|
13
|
+
:health_check_type => 'HealthCheckType',
|
14
|
+
:load_balancer_names => 'LoadBalancerNames',
|
15
|
+
:placement_group => 'PlacementGroup',
|
16
|
+
:tag_from_array => 'Tags',
|
17
|
+
:created_time => 'CreatedTime',
|
18
|
+
:vpc_zone_identifier => 'VPCZoneIdentifier'
|
19
|
+
aws_class_attribute :launch_configuration, Aerosol::LaunchConfiguration
|
20
|
+
primary_key :aws_identifier
|
21
|
+
|
22
|
+
def initialize(options={}, &block)
|
23
|
+
tag = options.delete(:tag)
|
24
|
+
super(options, &block)
|
25
|
+
|
26
|
+
tags.merge!(tag) unless tag.nil?
|
27
|
+
|
28
|
+
tags["GitSha"] ||= Aerosol::Util.git_sha
|
29
|
+
tags["Deploy"] ||= namespaced_name
|
30
|
+
end
|
31
|
+
|
32
|
+
def aws_identifier(arg = nil)
|
33
|
+
if arg
|
34
|
+
raise "You cannot set the aws_identifer directly" unless from_aws
|
35
|
+
@aws_identifier = arg
|
36
|
+
else
|
37
|
+
@aws_identifier || default_identifier
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def exists?
|
42
|
+
info "auto_scaling: needed?: #{namespaced_name}: " +
|
43
|
+
"checking for auto scaling group: #{aws_identifier}"
|
44
|
+
exists = super
|
45
|
+
info "auto scaling: needed?: #{namespaced_name}: " +
|
46
|
+
"#{exists ? 'found' : 'did not find'} auto scaling group: #{aws_identifier}"
|
47
|
+
exists
|
48
|
+
end
|
49
|
+
|
50
|
+
def create!
|
51
|
+
ensure_present! :launch_configuration, :max_size, :min_size
|
52
|
+
raise 'availability_zones or vpc_zone_identifier must be set' if [availability_zones, vpc_zone_identifier].none?
|
53
|
+
|
54
|
+
info "creating auto scaling group"
|
55
|
+
launch_configuration.create
|
56
|
+
info self.inspect
|
57
|
+
|
58
|
+
conn.create_auto_scaling_group(aws_identifier, availability_zones,
|
59
|
+
launch_configuration.aws_identifier,
|
60
|
+
max_size, min_size,
|
61
|
+
create_options)
|
62
|
+
make_fake_instances
|
63
|
+
sleep 10 # TODO: switch to fog models and .wait_for { ready? }
|
64
|
+
end
|
65
|
+
|
66
|
+
def destroy!
|
67
|
+
info self.inspect
|
68
|
+
conn.delete_auto_scaling_group(aws_identifier, 'ForceDelete' => true)
|
69
|
+
begin
|
70
|
+
(0..2).each { break if deleting?; sleep 1 }
|
71
|
+
launch_configuration.destroy
|
72
|
+
rescue => ex
|
73
|
+
info "Launch Config: #{launch_configuration} for #{aws_identifier} was not deleted."
|
74
|
+
info ex.message
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def deleting?
|
79
|
+
asgs = conn.describe_auto_scaling_groups("AutoScalingGroupNames" => self.aws_identifier)
|
80
|
+
.body
|
81
|
+
.[]('DescribeAutoScalingGroupsResult')
|
82
|
+
.[]('AutoScalingGroups')
|
83
|
+
|
84
|
+
return true if asgs.empty?
|
85
|
+
|
86
|
+
asgs.first['Status'].to_s.include?('Delete')
|
87
|
+
end
|
88
|
+
|
89
|
+
def all_instances
|
90
|
+
Aerosol::AWS.auto_scaling
|
91
|
+
.describe_auto_scaling_groups('AutoScalingGroupNames' => self.aws_identifier)
|
92
|
+
.body
|
93
|
+
.[]('DescribeAutoScalingGroupsResult')
|
94
|
+
.[]('AutoScalingGroups')
|
95
|
+
.first
|
96
|
+
.[]('Instances')
|
97
|
+
.map { |instance| Aerosol::Instance.from_hash(instance) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def tag(val)
|
101
|
+
tags.merge!(val)
|
102
|
+
end
|
103
|
+
|
104
|
+
def tags
|
105
|
+
@tags ||= {}
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.request_all_for_token(next_token)
|
109
|
+
options = next_token.nil? ? {} : { 'NexToken' => '' }
|
110
|
+
Aerosol::AWS.auto_scaling
|
111
|
+
.describe_auto_scaling_groups(options)
|
112
|
+
.body
|
113
|
+
.[]('DescribeAutoScalingGroupsResult')
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.request_all
|
117
|
+
next_token = nil
|
118
|
+
asgs = []
|
119
|
+
|
120
|
+
begin
|
121
|
+
new_asgs = request_all_for_token(next_token)
|
122
|
+
asgs.concat(new_asgs['AutoScalingGroups'])
|
123
|
+
next_token = new_asgs['NextToken']
|
124
|
+
end while !next_token.nil?
|
125
|
+
|
126
|
+
asgs
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.latest_for_tag(key, value)
|
130
|
+
all.select { |group| group.tags[key] == value }
|
131
|
+
.sort_by { |group| group.created_time }.last
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_s
|
135
|
+
%{Aerosol::AutoScaling { \
|
136
|
+
"aws_identifier" => "#{aws_identifier}", \
|
137
|
+
"availability_zones" => "#{availability_zones}", \
|
138
|
+
"min_size" => "#{min_size}", \
|
139
|
+
"max_size" => "#{max_size}", \
|
140
|
+
"default_cooldown" => "#{default_cooldown}", \
|
141
|
+
"desired_capacity" => "#{desired_capacity}", \
|
142
|
+
"health_check_grace_period" => "#{health_check_grace_period}", \
|
143
|
+
"health_check_type" => "#{health_check_type}", \
|
144
|
+
"load_balancer_names" => "#{load_balancer_names}", \
|
145
|
+
"placement_group" => "#{placement_group}", \
|
146
|
+
"tags" => #{tags.to_s}, \
|
147
|
+
"created_time" => "#{created_time}" \
|
148
|
+
}}
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
def conn
|
153
|
+
Aerosol::AWS.auto_scaling
|
154
|
+
end
|
155
|
+
|
156
|
+
def create_options
|
157
|
+
{
|
158
|
+
'DefaultCooldown' => default_cooldown,
|
159
|
+
'DesiredCapacity' => desired_capacity,
|
160
|
+
'HealthCheckGracePeriod' => health_check_grace_period,
|
161
|
+
'HealthCheckType' => health_check_type,
|
162
|
+
'LoadBalancerNames' => load_balancer_names,
|
163
|
+
'PlacementGroup' => placement_group,
|
164
|
+
'Tags' => tags,
|
165
|
+
'VPCZoneIdentifier' => vpc_zone_identifier
|
166
|
+
}.reject { |k, v| v.nil? }
|
167
|
+
end
|
168
|
+
|
169
|
+
def tag_from_array(ary)
|
170
|
+
if ary.is_a? Hash
|
171
|
+
ary.each do |key, value|
|
172
|
+
tag key => value
|
173
|
+
end
|
174
|
+
else
|
175
|
+
ary.each do |hash|
|
176
|
+
tag hash['Key'] => hash['Value']
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Unfortunately, Fog does not create fake instances after an auto scaling
|
182
|
+
# group is created.
|
183
|
+
def make_fake_instances
|
184
|
+
return unless Fog.mock?
|
185
|
+
|
186
|
+
asg_instances = []
|
187
|
+
all_instances = []
|
188
|
+
min_size.times do |n|
|
189
|
+
instance_id = Fog::AWS::Mock.instance_id
|
190
|
+
asg_instances << {
|
191
|
+
'AvailabilityZone' => availability_zones,
|
192
|
+
'HealthStatus' => 'Good',
|
193
|
+
'InstanceId' => instance_id,
|
194
|
+
'LifecycleState' => 'Pending',
|
195
|
+
'LaunchConfigurationName' => launch_configuration.aws_identifier
|
196
|
+
}
|
197
|
+
|
198
|
+
all_instances << {
|
199
|
+
'amiLaunchIndex' => n,
|
200
|
+
'architecture' => 'i386',
|
201
|
+
'blockDeviceMapping' => [],
|
202
|
+
'clientToken' => 'FAKE_CLIENT_TOKEN',
|
203
|
+
'dnsName' => 'not-a-real-hostname',
|
204
|
+
'ebsOptimized' => false,
|
205
|
+
'hypervisor' => 'xen',
|
206
|
+
'imageId' => launch_configuration.ami,
|
207
|
+
'instanceId' => instance_id,
|
208
|
+
'instanceState' => { 'code' => 0, 'name' => 'not pending?' },
|
209
|
+
'instanceType' => launch_configuration.instance_type,
|
210
|
+
'kernelId' => launch_configuration.kernel_id || Fog::AWS::Mock.kernel_id,
|
211
|
+
'keyName' => launch_configuration.key_name,
|
212
|
+
'launchTime' => Time.now,
|
213
|
+
'monitoring' => { 'state' => false },
|
214
|
+
'placement' => { 'availabilityZone' => availability_zones,
|
215
|
+
'groupName' => self.aws_identifier,
|
216
|
+
'tenancy' => 'default' },
|
217
|
+
'privateDnsName' => nil,
|
218
|
+
'productCodes' => [],
|
219
|
+
'reason' => nil,
|
220
|
+
'rootDeviceType' => 'instance-store',
|
221
|
+
'virtualizationType' => 'paravirtual',
|
222
|
+
'groupIds' => [],
|
223
|
+
'groupSet' => launch_configuration.security_groups,
|
224
|
+
'iamInstanceProfile' => launch_configuration.iam_role,
|
225
|
+
'networkInterfaces' => [],
|
226
|
+
'ownerId' => nil,
|
227
|
+
'privateIpAddress' => nil,
|
228
|
+
'reservationId' => Fog::AWS::Mock.reservation_id,
|
229
|
+
'stateReason' => {},
|
230
|
+
'ipAddress' => Fog::AWS::Mock.ip_address,
|
231
|
+
'privateIpAddress' => Fog::AWS::Mock.private_ip_address
|
232
|
+
}
|
233
|
+
end
|
234
|
+
Aerosol::AWS.auto_scaling.data[:auto_scaling_groups][aws_identifier]
|
235
|
+
.merge!('Instances' => asg_instances)
|
236
|
+
all_instances.each do |instance|
|
237
|
+
Aerosol::AWS.compute.data[:instances][instance['instanceId']] = instance
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
data/lib/aerosol/aws.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'fog/aws'
|
2
|
+
|
3
|
+
# This module holds the connections for all AWS services used by the gem.
|
4
|
+
module Aerosol::AWS
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def service(name, klass)
|
8
|
+
define_method name do
|
9
|
+
if val = instance_variable_get(:"@#{name}")
|
10
|
+
val
|
11
|
+
else
|
12
|
+
instance = klass.new(creds)
|
13
|
+
instance_variable_set(:"@#{name}", instance)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
services << name
|
17
|
+
end
|
18
|
+
|
19
|
+
def services
|
20
|
+
@services ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def env_attr(*names)
|
24
|
+
names.each do |name|
|
25
|
+
define_method name do
|
26
|
+
instance_variable_get(:"@#{name}") || ENV[name.to_s.upcase]
|
27
|
+
end
|
28
|
+
|
29
|
+
define_method :"#{name}=" do |val|
|
30
|
+
reset_cache!
|
31
|
+
instance_variable_set(:"@#{name}", val)
|
32
|
+
end
|
33
|
+
|
34
|
+
env_attrs << name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def env_attrs
|
39
|
+
@env_attrs ||= []
|
40
|
+
end
|
41
|
+
|
42
|
+
def creds
|
43
|
+
attrs = Hash[env_attrs.map { |attr| [attr, public_send(attr)] }].reject { |k, v| v.nil? }
|
44
|
+
if attrs.empty?
|
45
|
+
if ENV['FOG_CREDENTIAL']
|
46
|
+
attrs = {} # let Fog use the env var
|
47
|
+
else
|
48
|
+
attrs = { :use_iam_profile => true }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
attrs
|
52
|
+
end
|
53
|
+
|
54
|
+
def reset_cache!
|
55
|
+
services.each { |service| instance_variable_set(:"@#{service}", nil) }
|
56
|
+
end
|
57
|
+
|
58
|
+
service :s3, Fog::Storage::AWS
|
59
|
+
service :compute, Fog::Compute::AWS
|
60
|
+
service :auto_scaling, Fog::AWS::AutoScaling
|
61
|
+
env_attr :aws_access_key_id, :aws_secret_access_key
|
62
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Aerosol::AWSModel
|
2
|
+
def self.included(base)
|
3
|
+
base.instance_eval do
|
4
|
+
include Dockly::Util::DSL
|
5
|
+
extend ClassMethods
|
6
|
+
|
7
|
+
attr_accessor :from_aws
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(hash={}, &block)
|
12
|
+
self.from_aws = false
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def namespaced_name
|
17
|
+
Aerosol.namespace ? "#{Aerosol.namespace}-#{name}" : name.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_identifier
|
21
|
+
"#{namespaced_name}-#{Aerosol::Util.git_sha}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def create
|
25
|
+
raise '#create! must be defined to use #create' unless respond_to?(:create!)
|
26
|
+
create! unless exists?
|
27
|
+
end
|
28
|
+
|
29
|
+
def destroy
|
30
|
+
raise '#destroy! must be defined to use #destroy' unless respond_to?(:destroy!)
|
31
|
+
destroy! if exists?
|
32
|
+
end
|
33
|
+
|
34
|
+
def exists?
|
35
|
+
primary_value = send(self.class.primary_key)
|
36
|
+
self.class.exists?(primary_value)
|
37
|
+
end
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
def primary_key(attr = nil)
|
41
|
+
@primary_key = attr unless attr.nil?
|
42
|
+
@primary_key
|
43
|
+
end
|
44
|
+
|
45
|
+
def aws_attribute(hash)
|
46
|
+
dsl_attribute(*hash.keys)
|
47
|
+
aws_attributes.merge!(hash)
|
48
|
+
end
|
49
|
+
|
50
|
+
def aws_class_attribute(name, klass)
|
51
|
+
unless klass.ancestors.include?(Aerosol::AWSModel) || (klass == self)
|
52
|
+
raise '.aws_class_attribute requires a Aerosol::AWSModel that is not the current class.'
|
53
|
+
end
|
54
|
+
|
55
|
+
dsl_class_attribute(name, klass)
|
56
|
+
aws_class_attributes.merge!({ name => klass })
|
57
|
+
end
|
58
|
+
|
59
|
+
def exists?(key)
|
60
|
+
all.map { |inst| inst.send(primary_key) }.include?(key)
|
61
|
+
end
|
62
|
+
|
63
|
+
def all
|
64
|
+
raise 'Please define .request_all to use .all' unless respond_to?(:request_all)
|
65
|
+
request_all.map { |hash| from_hash(hash) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def from_hash(hash)
|
69
|
+
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|
|
71
|
+
[name, klass.instances.values.find do |inst|
|
72
|
+
inst.send(klass.primary_key) &&
|
73
|
+
(inst.send(klass.primary_key) == hash[klass.aws_attributes[klass.primary_key]])
|
74
|
+
end]
|
75
|
+
end].reject { |k, v| v.nil? }
|
76
|
+
|
77
|
+
instance = new!
|
78
|
+
instance.from_aws = true
|
79
|
+
|
80
|
+
aws_attributes.each { |k, v| instance.send(k, hash[v]) unless hash[v].nil? }
|
81
|
+
refs.each { |name, inst| instance.send(name, inst.name) }
|
82
|
+
instance
|
83
|
+
end
|
84
|
+
|
85
|
+
def aws_attributes
|
86
|
+
@aws_attributes ||= {}
|
87
|
+
end
|
88
|
+
|
89
|
+
def aws_class_attributes
|
90
|
+
@aws_class_attributes ||= {}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/aerosol/cli.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'aerosol'
|
3
|
+
require 'clamp'
|
4
|
+
|
5
|
+
class Aerosol::AbstractCommand < Clamp::Command
|
6
|
+
option ['-f', '--file'], 'FILE', 'aerosol file to read', :default => 'aerosol.rb', :attribute_name => :file
|
7
|
+
|
8
|
+
def execute
|
9
|
+
if File.exist?(file)
|
10
|
+
Aerosol.load_file = file
|
11
|
+
else
|
12
|
+
raise 'Could not find an aerosol file!'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Aerosol::SshCommand < Aerosol::AbstractCommand
|
18
|
+
option ['-r', '--run'], :flag, 'run first ssh command', :attribute_name => :run_first
|
19
|
+
parameter 'DEPLOY', 'the deploy to list commands for', :attribute_name => :deploy_name
|
20
|
+
|
21
|
+
def execute
|
22
|
+
super
|
23
|
+
if deploy = Aerosol.deploy(deploy_name.to_sym)
|
24
|
+
ssh_commands = deploy.generate_ssh_commands
|
25
|
+
raise 'No instances to ssh too!' if ssh_commands.empty?
|
26
|
+
|
27
|
+
ssh_commands.each do |ssh_command|
|
28
|
+
puts ssh_command
|
29
|
+
end
|
30
|
+
|
31
|
+
if run_first?
|
32
|
+
system(ssh_commands.first)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Aerosol::Cli < Aerosol::AbstractCommand
|
39
|
+
subcommand ['ssh', 's'], 'Print ssh commands for latest running instances', Aerosol::SshCommand
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/ssh/gateway'
|
3
|
+
|
4
|
+
class Aerosol::Connection
|
5
|
+
include Dockly::Util::DSL
|
6
|
+
include Dockly::Util::Logger::Mixin
|
7
|
+
|
8
|
+
logger_prefix '[aerosol connection]'
|
9
|
+
dsl_attribute :user, :host, :jump
|
10
|
+
|
11
|
+
def with_connection(overridden_host=nil, &block)
|
12
|
+
actual_host = overridden_host || host
|
13
|
+
unless actual_host.is_a?(String)
|
14
|
+
actual_host = (actual_host.public_hostname || actual_host.private_ip_address)
|
15
|
+
end
|
16
|
+
|
17
|
+
if jump
|
18
|
+
info "connecting to gateway #{jump[:user] || user}@#{jump[:host]}"
|
19
|
+
gateway = nil
|
20
|
+
Timeout.timeout(20) do
|
21
|
+
gateway = Net::SSH::Gateway.new(jump[:host], jump[:user] || user, :forward_agent => true)
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
info "connecting to #{user}@#{actual_host} through gateway"
|
26
|
+
gateway.ssh(actual_host, user, &block)
|
27
|
+
ensure
|
28
|
+
info "shutting down gateway connection"
|
29
|
+
gateway.shutdown!
|
30
|
+
end
|
31
|
+
else
|
32
|
+
info "connecting to #{user}@#{actual_host}"
|
33
|
+
Net::SSH.start(actual_host, user, &block)
|
34
|
+
end
|
35
|
+
rescue Timeout::Error => ex
|
36
|
+
error "Timeout error #{ex.message}"
|
37
|
+
error ex.backtrace.join("\n")
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
class Aerosol::Deploy
|
2
|
+
include Dockly::Util::DSL
|
3
|
+
include Dockly::Util::Logger::Mixin
|
4
|
+
|
5
|
+
logger_prefix '[aerosol deploy]'
|
6
|
+
dsl_attribute :stop_command, :db_config_path,
|
7
|
+
:instance_live_grace_period, :app_port,
|
8
|
+
:continue_if_stop_app_fails, :stop_app_retries,
|
9
|
+
:sleep_before_termination, :post_deploy_command,
|
10
|
+
:ssl, :log_files, :tail_logs
|
11
|
+
|
12
|
+
dsl_class_attribute :ssh, Aerosol::Connection
|
13
|
+
dsl_class_attribute :migration_ssh, Aerosol::Connection
|
14
|
+
dsl_class_attribute :local_ssh, Aerosol::Connection
|
15
|
+
dsl_class_attribute :auto_scaling, Aerosol::AutoScaling
|
16
|
+
|
17
|
+
default_value :db_config_path, 'config/database.yml'
|
18
|
+
default_value :instance_live_grace_period, 30 * 60 # 30 Minutes
|
19
|
+
default_value :continue_if_stop_app_fails, false
|
20
|
+
default_value :stop_app_retries, 2
|
21
|
+
default_value :sleep_before_termination, 20
|
22
|
+
default_value :ssl, false
|
23
|
+
default_value :tail_logs, false
|
24
|
+
default_value :log_files, ['/var/log/syslog']
|
25
|
+
|
26
|
+
def live_check(arg = nil)
|
27
|
+
case
|
28
|
+
when arg.nil?
|
29
|
+
@live_check
|
30
|
+
when arg.start_with?('/')
|
31
|
+
@live_check = arg
|
32
|
+
else
|
33
|
+
@live_check = "/#{arg}"
|
34
|
+
end
|
35
|
+
@live_check
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_alive?(&block)
|
39
|
+
@is_alive = block unless block.nil?
|
40
|
+
@is_alive
|
41
|
+
end
|
42
|
+
|
43
|
+
def live_check_url
|
44
|
+
[ssl ? 'https' : 'http', '://localhost:', app_port, live_check].join
|
45
|
+
end
|
46
|
+
|
47
|
+
def do_not_migrate!
|
48
|
+
self.instance_variable_set(:@db_config_path, nil)
|
49
|
+
end
|
50
|
+
|
51
|
+
def migration(opts = {})
|
52
|
+
self.db_config_path(opts[:db_config_path])
|
53
|
+
end
|
54
|
+
|
55
|
+
def migrate?
|
56
|
+
!!db_config_path
|
57
|
+
end
|
58
|
+
|
59
|
+
def local_ssh_ref
|
60
|
+
local_ssh || ssh
|
61
|
+
end
|
62
|
+
|
63
|
+
def run_post_deploy
|
64
|
+
return if post_deploy_command.nil?
|
65
|
+
info "running post deploy: #{post_deploy_command}"
|
66
|
+
if system(post_deploy_command)
|
67
|
+
info "post deploy ran successfully"
|
68
|
+
true
|
69
|
+
else
|
70
|
+
raise "post deploy failed"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def generate_ssh_command(instance)
|
75
|
+
ssh_command = "ssh -o 'UserKnownHostsFile=/dev/null' -o 'StrictHostKeyChecking=no' "
|
76
|
+
unless local_ssh_ref.nil?
|
77
|
+
unless local_ssh_ref.jump.nil? || local_ssh_ref.jump.empty?
|
78
|
+
ssh_command << "-o 'ProxyCommand=ssh -W %h:%p "
|
79
|
+
ssh_command << "#{local_ssh_ref.jump[:user]}@" if local_ssh_ref.jump[:user]
|
80
|
+
ssh_command << "#{local_ssh_ref.jump[:host]}' "
|
81
|
+
end
|
82
|
+
ssh_command << "#{local_ssh_ref.user}@" unless local_ssh_ref.user.nil?
|
83
|
+
end
|
84
|
+
ssh_command << "#{instance.public_hostname || instance.private_ip_address}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def generate_ssh_commands
|
88
|
+
group = Aerosol::AutoScaling.latest_for_tag('Deploy', auto_scaling.namespaced_name)
|
89
|
+
raise "Could not find any auto scaling groups for this deploy (#{name})." if group.nil?
|
90
|
+
|
91
|
+
ssh_commands = []
|
92
|
+
|
93
|
+
with_prefix("[#{name}]") do |logger|
|
94
|
+
logger.info "found group: #{group.aws_identifier}"
|
95
|
+
instances = group.all_instances
|
96
|
+
raise "Could not find any instances for auto scaling group #{group.namespaced_name}" if instances.empty?
|
97
|
+
instances.each do |instance|
|
98
|
+
logger.info "printing ssh command for #{instance.public_hostname || instance.private_ip_address}"
|
99
|
+
ssh_commands << generate_ssh_command(instance)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
return ssh_commands
|
104
|
+
end
|
105
|
+
end
|
data/lib/aerosol/env.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
class Aerosol::Instance
|
2
|
+
include Aerosol::AWSModel
|
3
|
+
|
4
|
+
aws_attribute :availability_zone => 'AvailabilityZone',
|
5
|
+
:health_status => 'HealthStatus',
|
6
|
+
:id => 'InstanceId',
|
7
|
+
:lifecycle_state => 'LifecycleState'
|
8
|
+
aws_class_attribute :launch_configuration, Aerosol::LaunchConfiguration
|
9
|
+
primary_key :id
|
10
|
+
|
11
|
+
def live?
|
12
|
+
describe_again
|
13
|
+
instance_state_name == 'running'
|
14
|
+
end
|
15
|
+
|
16
|
+
def instance_state_name
|
17
|
+
description['instanceState']['name']
|
18
|
+
end
|
19
|
+
|
20
|
+
def public_hostname
|
21
|
+
description['dnsName']
|
22
|
+
end
|
23
|
+
|
24
|
+
def private_ip_address
|
25
|
+
description['privateIpAddress']
|
26
|
+
end
|
27
|
+
|
28
|
+
def ami
|
29
|
+
description['imageId']
|
30
|
+
end
|
31
|
+
|
32
|
+
def description
|
33
|
+
@description ||= describe!
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.request_all
|
37
|
+
Aerosol::AWS.auto_scaling
|
38
|
+
.describe_auto_scaling_instances
|
39
|
+
.body
|
40
|
+
.[]('DescribeAutoScalingInstancesResult')
|
41
|
+
.[]('AutoScalingInstances')
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def describe!
|
46
|
+
ensure_present! :id
|
47
|
+
result = Aerosol::AWS.compute.describe_instances('instance-id' => id).body
|
48
|
+
result['reservationSet'].first['instancesSet'].first rescue nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def describe_again
|
52
|
+
@description = nil
|
53
|
+
description
|
54
|
+
end
|
55
|
+
end
|