opsworks_wrapper 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +0 -2
- data/lib/opsworks_wrapper.rb +219 -25
- data/opsworks_wrapper.gemspec +1 -1
- metadata +2 -3
- data/lib/opsworks_wrapper/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40e9f15632090461fc0c1243cd6dd94370882945
|
4
|
+
data.tar.gz: 247915c8d6b5105abdabf2b960aa7650a0414268
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5666992cdb2760f699dc2bdc671408734ca9fadfda59c79be5ac09ea6eb0c75c165ede33994a21b45dec1c6b0a9bd08ef312adbbf1cb1cea3e7a0c680c72d96d
|
7
|
+
data.tar.gz: 3bf986520f92cd8a7edd3ec2ecafea23278f6f1a0e574be6c4f1a40c6fed464e16634a9de7a05eecbe2142629018f1d3c618477639d5943d875e29fc777f3a87
|
data/README.md
CHANGED
@@ -8,8 +8,6 @@ A simple wrapper for aws-sdk to make handling opsworks operations easier.
|
|
8
8
|
- Deployment Creation for all layers except a specified layer (exclusion by layer name)
|
9
9
|
- Retrieve all instances for a given layer name
|
10
10
|
|
11
|
-
TODO: Delete this and the text above, and describe your gem
|
12
|
-
|
13
11
|
## Installation
|
14
12
|
|
15
13
|
Add this line to your application's Gemfile:
|
data/lib/opsworks_wrapper.rb
CHANGED
@@ -17,8 +17,12 @@ module OpsworksWrapper
|
|
17
17
|
@opsworks_app ||= get_opsworks_app
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
@
|
20
|
+
def opsworks_client
|
21
|
+
@opsworks_client ||= Aws::OpsWorks::Client.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_balancer_client
|
25
|
+
@client ||= Aws::ElasticLoadBalancing::Client.new
|
22
26
|
end
|
23
27
|
|
24
28
|
def app_id
|
@@ -29,8 +33,10 @@ module OpsworksWrapper
|
|
29
33
|
@layers ||= get_opsworks_layers
|
30
34
|
end
|
31
35
|
|
36
|
+
# Returns a dictionary for all OpsWorks layers keyed by layer name
|
37
|
+
# @return [Dictionary]
|
32
38
|
def get_opsworks_layers
|
33
|
-
data =
|
39
|
+
data = opsworks_client.describe_layers(stack_id: opsworks_app[:stack_id])
|
34
40
|
layers = {}
|
35
41
|
data.layers.each do |layer|
|
36
42
|
layers[layer.name] = layer
|
@@ -38,59 +44,143 @@ module OpsworksWrapper
|
|
38
44
|
layers
|
39
45
|
end
|
40
46
|
|
47
|
+
# Returns OpsWorks app details
|
48
|
+
# @return [Object]
|
41
49
|
def get_opsworks_app
|
42
|
-
data =
|
43
|
-
|
50
|
+
data = opsworks_client.describe_apps(app_ids: [app_id])
|
51
|
+
if !(data[:apps] && data[:apps].count == 1)
|
44
52
|
raise Error, "App #{app_id} not found.", error.backtrace
|
45
53
|
end
|
46
54
|
data[:apps].first
|
47
55
|
end
|
48
56
|
|
57
|
+
|
58
|
+
# Returns a list of OpsWorks instances for a specific layer or all layers if @layer_name is not provided
|
59
|
+
# @param [String] layer_name
|
60
|
+
# @return [List[Object]] - List of OpsWorks instances
|
49
61
|
def get_instances(layer_name = nil)
|
50
62
|
if layer_name == nil
|
51
|
-
data =
|
63
|
+
data = opsworks_client.describe_instances(stack_id: opsworks_app[:stack_id])
|
52
64
|
else
|
53
65
|
layer_id = layers[layer_name].layer_id
|
54
|
-
data =
|
66
|
+
data = opsworks_client.describe_instances(layer_id: layer_id)
|
55
67
|
end
|
56
68
|
|
57
69
|
data.instances
|
58
70
|
end
|
59
71
|
|
60
|
-
#
|
72
|
+
# Returns ELB instance for layer if one is attached
|
73
|
+
# @param [String] layer_name
|
74
|
+
# @return [Object?] - ELB instance
|
75
|
+
def get_elb(layer_name)
|
76
|
+
layer_id = layers[layer_name].layer_id
|
77
|
+
elbs = opsworks_client.describe_elastic_load_balancers(layer_ids:[layer_id])
|
78
|
+
if elbs.elastic_load_balancers.size > 0
|
79
|
+
name = elbs.elastic_load_balancers.first.elastic_load_balancer_name
|
80
|
+
ELB.new(name)
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Run update cookbooks on all layers
|
87
|
+
# @param [Number] timeout
|
88
|
+
# @return [Boolean]
|
61
89
|
def update_cookbooks(timeout = 150)
|
62
90
|
puts 'Updating cookbooks'.light_white.bold
|
63
91
|
create_deployment({name: 'update_custom_cookbooks'}, nil, timeout)
|
64
92
|
end
|
65
93
|
|
66
|
-
# deploy
|
94
|
+
# Run deploy command on specified layer or all layers if @layer_name is not specified (non rolling)
|
95
|
+
# @param [String] layer_name
|
96
|
+
# @param [Number] timeout
|
97
|
+
# @return [Boolean]
|
67
98
|
def deploy(layer_name = nil, timeout = 600)
|
68
99
|
if layer_name
|
69
100
|
puts "Deploying on #{layer_name} layer".light_white.bold
|
70
|
-
|
101
|
+
instances = get_instances(layer_name)
|
71
102
|
else
|
72
103
|
puts "Deploying on all layers".light_white.bold
|
73
|
-
|
104
|
+
instances = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
create_deployment({name: 'deploy'}, instances, timeout)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Performs a rolling deploy on each instance in the layer
|
111
|
+
# If an elb is attached to the layer, de-registration and registration will be performed for the instance
|
112
|
+
# @param [String] layer_name
|
113
|
+
# @param [Number] timeout
|
114
|
+
# @return [Boolean]
|
115
|
+
def deploy_layer_rolling(layer_name, timeout = 600)
|
116
|
+
instances = get_instances(layer_name)
|
117
|
+
elb = get_elb(layer_name)
|
118
|
+
success = true
|
119
|
+
instances.each do |instance|
|
120
|
+
success = deploy_instance_rolling(instance, elb, timeout)
|
121
|
+
break if !success
|
122
|
+
end
|
123
|
+
success
|
124
|
+
end
|
125
|
+
|
126
|
+
# Performs rolling deployment on an instance
|
127
|
+
# Will detach instance if elb is provided and re-attach after deployment succeeds
|
128
|
+
# @param [Object] instance - opsworks instance
|
129
|
+
# @param [Object] elb - elb instance
|
130
|
+
# @param [Number] timeout
|
131
|
+
# @return [Boolean]
|
132
|
+
def deploy_instance_rolling(instance, elb, timeout = 600)
|
133
|
+
if !elb.nil?
|
134
|
+
elb.remove_instance(instance)
|
74
135
|
end
|
75
136
|
|
76
|
-
create_deployment({name: 'deploy'},
|
137
|
+
success = create_deployment({name: 'deploy'}, [instance], timeout)
|
138
|
+
|
139
|
+
# only add instance back to elb if deployment succeeded
|
140
|
+
if !elb.nil? && success
|
141
|
+
success = elb.add_instance(instance)
|
142
|
+
end
|
143
|
+
|
144
|
+
success
|
77
145
|
end
|
78
146
|
|
79
|
-
#
|
147
|
+
# Deploy to all layers except specified layer (non-rolling)
|
148
|
+
# @param [String] layer_name
|
149
|
+
# @param [Number] timeout
|
150
|
+
# @return [Boolean]
|
80
151
|
def deploy_exclude(layer_name, timeout = 600)
|
81
152
|
puts "Deploying to all layers except #{layer_name}".light_white.bold
|
82
153
|
create_deployment_exclude({name: 'deploy'}, layer_name, timeout)
|
83
154
|
end
|
84
155
|
|
156
|
+
# Creates an OpsWorks deployment with specified command on all layers excluding layer_to_exclude
|
157
|
+
# @param [Object] command - Opsworks deployment command
|
158
|
+
# @param [String] layer_to_exclude
|
159
|
+
# @param [Number] timeout
|
160
|
+
# @return [Boolean]
|
85
161
|
def create_deployment_exclude(command, layer_to_exclude, timeout)
|
86
|
-
|
87
|
-
|
88
|
-
|
162
|
+
all_instances = get_instances
|
163
|
+
excluded_instances = get_instances(layer_to_exclude)
|
164
|
+
included_instances = all_instances - excluded_instances
|
89
165
|
|
90
|
-
create_deployment(command,
|
166
|
+
create_deployment(command, included_instances, timeout)
|
91
167
|
end
|
92
168
|
|
93
|
-
|
169
|
+
# Creates an OpsWorks deployment with specified command
|
170
|
+
# If @instances is not nil, the deployment will only be performed on specified instances
|
171
|
+
# @param [Object] command
|
172
|
+
# @param [Array[Object]] instances
|
173
|
+
# @param [Number] timeout
|
174
|
+
# @return [Boolean]
|
175
|
+
def create_deployment(command, instances, timeout)
|
176
|
+
instance_ids = nil
|
177
|
+
instance_description = "all instances"
|
178
|
+
|
179
|
+
if !instances.nil?
|
180
|
+
instance_ids = instances.map(&:instance_id)
|
181
|
+
instance_description = instances.map(&:hostname).join(',')
|
182
|
+
end
|
183
|
+
|
94
184
|
deployment_config = {
|
95
185
|
stack_id: opsworks_app[:stack_id],
|
96
186
|
app_id: app_id,
|
@@ -99,12 +189,13 @@ module OpsworksWrapper
|
|
99
189
|
comment: "Git Sha: #{current_sha}"
|
100
190
|
}
|
101
191
|
|
102
|
-
deployment =
|
103
|
-
|
104
|
-
|
192
|
+
deployment = opsworks_client.create_deployment(deployment_config)
|
193
|
+
print "Running command ".light_blue
|
194
|
+
print "#{command[:name]}".light_blue.bold
|
195
|
+
puts " on #{instance_description}".light_blue
|
105
196
|
|
106
197
|
begin
|
107
|
-
|
198
|
+
_wait_until_deployed(deployment[:deployment_id], timeout)
|
108
199
|
puts "Deployment successful".green
|
109
200
|
true
|
110
201
|
rescue Aws::Waiters::Errors::WaiterFailed => e
|
@@ -114,8 +205,8 @@ module OpsworksWrapper
|
|
114
205
|
end
|
115
206
|
|
116
207
|
# Waits on the provided deployment for specified timeout (seconds)
|
117
|
-
def
|
118
|
-
|
208
|
+
def _wait_until_deployed(deployment_id, timeout)
|
209
|
+
opsworks_client.wait_until(:deployment_successful, deployment_ids: [deployment_id]) do |w|
|
119
210
|
w.before_attempt do |attempt|
|
120
211
|
puts "Attempt #{attempt} to check deployment status".light_black
|
121
212
|
end
|
@@ -123,7 +214,110 @@ module OpsworksWrapper
|
|
123
214
|
w.max_attempts = timeout / w.interval
|
124
215
|
end
|
125
216
|
end
|
126
|
-
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
class ELB
|
221
|
+
require 'aws-sdk'
|
222
|
+
require 'colorize'
|
223
|
+
|
224
|
+
def initialize(name)
|
225
|
+
@name = name
|
226
|
+
end
|
227
|
+
|
228
|
+
def name
|
229
|
+
@name
|
230
|
+
end
|
231
|
+
|
232
|
+
def client
|
233
|
+
@client ||= Aws::ElasticLoadBalancing::Client.new
|
234
|
+
end
|
235
|
+
|
236
|
+
def attributes
|
237
|
+
@attributes ||= client.describe_load_balancer_attributes(load_balancer_name: name)
|
238
|
+
end
|
239
|
+
|
240
|
+
def health_check
|
241
|
+
elb = client.describe_load_balancers(load_balancer_names: [name]).load_balancer_descriptions.first
|
242
|
+
@health_check ||= elb.health_check
|
243
|
+
end
|
244
|
+
|
245
|
+
# Determines if elb has connection draining enabled and waits for the timeout period or (20s) default
|
246
|
+
def _wait_for_connection_draining
|
247
|
+
connection_draining = attributes.load_balancer_attributes.connection_draining
|
248
|
+
if connection_draining.enabled
|
249
|
+
timeout = connection_draining.timeout
|
250
|
+
puts "Connection Draining Enabled - sleeping for #{timeout}".light_black
|
251
|
+
sleep(timeout)
|
252
|
+
else
|
253
|
+
puts "Connection Draining Disabled - sleeping for 20 seconds".light_black
|
254
|
+
sleep(20)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Waits on instance to be in service according to the ELB
|
259
|
+
# @param [Object] instance
|
260
|
+
# @return [Boolean]
|
261
|
+
def _wait_for_instance_health_check(instance)
|
262
|
+
health_threshold = health_check.healthy_threshold
|
263
|
+
interval = health_check.interval
|
264
|
+
|
265
|
+
# wait a little longer than the defined threshold to account for application launch time
|
266
|
+
timeout = ((health_threshold + 2) * interval)
|
267
|
+
|
268
|
+
begin
|
269
|
+
client.wait_until(:instance_in_service, {load_balancer_name: name,
|
270
|
+
instances: [{instance_id: instance.ec2_instance_id}]}) do |w|
|
271
|
+
w.before_attempt do |attempt|
|
272
|
+
puts "Attempt #{attempt} to check health status for #{instance.hostname}".light_black
|
273
|
+
end
|
274
|
+
w.interval = 10
|
275
|
+
w.max_attempts = timeout / w.interval
|
276
|
+
end
|
277
|
+
puts "Instance #{instance.hostname} is now InService".green
|
278
|
+
true
|
279
|
+
rescue Aws::Waiters::Errors::WaiterFailed => e
|
280
|
+
puts "Instance #{instance.hostname} failed to move to InService, #{e.message}".red
|
281
|
+
false
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
# Removes instance from ELB and waits for connection draining
|
287
|
+
# @param [Object] instance - object with ec2_instance_id and hostname
|
288
|
+
def remove_instance(instance)
|
289
|
+
deregister_response = client.deregister_instances_from_load_balancer(load_balancer_name: name,
|
290
|
+
instances: [{instance_id: instance.ec2_instance_id}])
|
291
|
+
remaining_instance_count = deregister_response.instances.size
|
292
|
+
puts "Removed #{instance.hostname} from ELB #{name}. Remaining instances: #{remaining_instance_count}".light_blue
|
293
|
+
_wait_for_connection_draining
|
294
|
+
end
|
295
|
+
|
296
|
+
# Adds instance to ELB and waits for instance health check to pass
|
297
|
+
# @param [Object] instance - object with ec2_instance_id and hostname
|
298
|
+
# @return [Boolean]
|
299
|
+
def add_instance(instance)
|
300
|
+
register_response = client.register_instances_with_load_balancer(load_balancer_name: name,
|
301
|
+
instances: [{instance_id: instance.ec2_instance_id}])
|
302
|
+
remaining_instance_count = register_response.instances.size
|
303
|
+
puts "Added #{instance.hostname} to ELB #{name}. Attached instances: #{remaining_instance_count}".light_blue
|
304
|
+
_wait_for_instance_health_check(instance)
|
305
|
+
end
|
306
|
+
|
307
|
+
# Checks whether an instance attached to ELB is healthy
|
308
|
+
# @param [Object] instance - object with ec2_instance_id and hostname
|
309
|
+
# @return [Boolean]
|
310
|
+
def is_instance_healthy(instance)
|
311
|
+
instance_health = client.describe_instance_health(load_balancer_name: name,
|
312
|
+
instances: [{instance_id: instance.ec2_instance_id}])
|
313
|
+
state_info = instance_health.instance_states.first
|
314
|
+
status_detail = ''
|
315
|
+
if state_info.state != 'InService'
|
316
|
+
status_detail = "#{state_info.reason_code} - #{state_info.description}."
|
317
|
+
end
|
318
|
+
puts "Instance state is #{state_info.state} #{status_detail}"
|
319
|
+
state_info.state == 'InService'
|
320
|
+
end
|
127
321
|
|
128
322
|
end
|
129
323
|
end
|
data/opsworks_wrapper.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "opsworks_wrapper"
|
7
|
-
spec.version = '0.
|
7
|
+
spec.version = '0.2.0'
|
8
8
|
spec.authors = ["Umair Kayani", "Calvin Fernandes"]
|
9
9
|
spec.email = ["ukayani@loyalty.com", "cfernandes@loyalty.com"]
|
10
10
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: opsworks_wrapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Umair Kayani
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-04-
|
12
|
+
date: 2016-04-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk
|
@@ -82,7 +82,6 @@ files:
|
|
82
82
|
- README.md
|
83
83
|
- Rakefile
|
84
84
|
- lib/opsworks_wrapper.rb
|
85
|
-
- lib/opsworks_wrapper/version.rb
|
86
85
|
- opsworks_wrapper.gemspec
|
87
86
|
homepage: https://github.com/ukayani/opsworks-wrapper
|
88
87
|
licenses:
|