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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 382ccf56835c9a6787c03a12a2de311989492b72
4
- data.tar.gz: 3efeb0cacd432e2e87f8b5d3f34945b289959c5b
3
+ metadata.gz: 40e9f15632090461fc0c1243cd6dd94370882945
4
+ data.tar.gz: 247915c8d6b5105abdabf2b960aa7650a0414268
5
5
  SHA512:
6
- metadata.gz: fae78c1576999010eb4ac8dfce4582f33d631da2445fec1f8839ac2ef744c1e711644b04ce2b34012dde28478ebde107ea4dde37c1f7e96de4ad405f2552f5d0
7
- data.tar.gz: e227d5f12324eb3ef06d5f5b68e7bb38499bb1ad69d09bb2513a4eb2d0824f0f4e891d72b645fa0046fa89f91a77dceb8cbaa83f673bc50519e20495397eade6
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:
@@ -17,8 +17,12 @@ module OpsworksWrapper
17
17
  @opsworks_app ||= get_opsworks_app
18
18
  end
19
19
 
20
- def client
21
- @client ||= Aws::OpsWorks::Client.new
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 = client.describe_layers(stack_id: opsworks_app[:stack_id])
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 = client.describe_apps(app_ids: [app_id])
43
- unless data[:apps] && data[:apps].count == 1
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 = client.describe_instances(stack_id: opsworks_app[:stack_id])
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 = client.describe_instances(layer_id: layer_id)
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
- # update cookbooks on all layers
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 to specified layer or all layers (default)
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
- instance_ids = get_instances(layer_name).map(&:instance_id)
101
+ instances = get_instances(layer_name)
71
102
  else
72
103
  puts "Deploying on all layers".light_white.bold
73
- instance_ids = nil
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'}, instance_ids, timeout)
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
- # deploy to all layers except specified layer
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
- all_instance_ids = get_instances.map(&:instance_id)
87
- excluded_instance_ids = get_instances(layer_to_exclude).map(&:instance_id)
88
- included_instance_ids = all_instance_ids - excluded_instance_ids
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, included_instance_ids, timeout)
166
+ create_deployment(command, included_instances, timeout)
91
167
  end
92
168
 
93
- def create_deployment(command, instance_ids, timeout)
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 = client.create_deployment(deployment_config)
103
- puts "Deployment created: #{deployment[:deployment_id]}".blue
104
- puts "Running Command: #{command[:name]} ".light_blue
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
- wait_until_deployed(deployment[:deployment_id], timeout)
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 wait_until_deployed(deployment_id, timeout)
118
- client.wait_until(:deployment_successful, deployment_ids: [deployment_id]) do |w|
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
- private :wait_until_deployed
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
@@ -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.1.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.1.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-15 00:00:00.000000000 Z
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:
@@ -1,3 +0,0 @@
1
- module OpsworksWrapper
2
- VERSION = "0.1.0"
3
- end