opsworks_wrapper 0.1.0 → 0.2.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 +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:
|