aerosol 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|