asger 0.2.1 → 1.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: abc4ad19cb57955e168d9f3c1931eec3ba3f4835
4
- data.tar.gz: d9c71640218a1819c020207e04b962be00875325
3
+ metadata.gz: 5c91487b1cd2ad1031fc3d9b1e72a789dd446ca4
4
+ data.tar.gz: fc47bf1f5b779029ef2a69d8219777c653422462
5
5
  SHA512:
6
- metadata.gz: f08683fa0fdaff1e6ab811d79d46dc855b6817063815748b1889a3dd28f404341e1089f8a0c5d7056867355a598807c649845aa19a2c39c1640ee210f1263886
7
- data.tar.gz: 58804ae9a19d5e4edab5b77fba9fa9aaf6057b57bc63fe59ca1b507b3c17db433aff00a3df7667ea14f43c20247e5235498fbfa20eee10f7cd1ed2456f212387
6
+ metadata.gz: daad5b8b810429b6989cfda98b67da0384022edb68970071ec00a339ec6cee7a7013edde5d7ce27fc60990ea63e7337112aede22ba23c5d85915f77023a591e8
7
+ data.tar.gz: 090c4f37a0f6e461e74f4933fcb5fe921f5c3a0f65e7b5e908de355dbd52ea640ee510c5e78906c8ad04ffc4d29cf5940a658201a599293bcc88f52bcfc15a2d
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `asger` #
2
2
 
3
- `asger` is a tool designed to field notifications from Amazon EC2 auto-scaling groups via a SNS topic subscribed to an SQS queue. (Which probably sounds alarmingly specific, but it's the most common way to do this!) Once a notification is fielded, the user can define Tasks that then perform actions on instance creation ("up" functions) and termination ("down" functions).
3
+ `asger` is a tool designed to field notifications from Amazon EC2 auto-scaling groups via a SNS topic subscribed to an SQS queue. (Which probably sounds alarmingly specific, but it's the most common way to do this!) Once a notification is fielded, the user can define Tasks that then perform actions on instance creation ("up" functions) and termination ("down" functions), as well as their associated failure events.
4
4
 
5
5
  ### Important Notes ###
6
6
  - When multiple tasks are running in a single `asger` instance, they will be run in order on instance creation and _in reverse order_ on instance termination.
data/asger.gemspec CHANGED
@@ -21,8 +21,9 @@ Gem::Specification.new do |spec|
21
21
  spec.add_development_dependency "rake", "~> 10.0"
22
22
  spec.add_development_dependency "pry"
23
23
 
24
- spec.add_runtime_dependency 'aws-sdk', '~> 2.0.27'
25
- spec.add_runtime_dependency 'trollop', '~> 2.1.1'
24
+ spec.add_runtime_dependency 'aws-sdk', '~> 2.2.22'
25
+ spec.add_runtime_dependency 'trollop', '~> 2.1.1'
26
26
  spec.add_runtime_dependency "hashie", "~> 3.3"
27
+ spec.add_runtime_dependency 'ice_nine', '~> 0.11.2'
27
28
  spec.add_runtime_dependency 'activesupport', '~> 4.2.0'
28
29
  end
data/lib/asger/cli.rb CHANGED
@@ -20,17 +20,19 @@ module Asger
20
20
  :type => :string, :multi => true
21
21
  opt :queue_url, "URL of the SQS queue to read from",
22
22
  :type => :string
23
- opt :pause_time, "Time (in seconds) to pause between polls.",
24
- :default => 0
25
23
  opt :verbose, "enables verbose logging",
26
24
  :default => false
27
25
  opt :die_on_error, "Terminates if an exception is thrown within the task runner.",
28
26
  :default => true
29
27
 
28
+ opt :delete_messages, 'Delete messages from the SQS queue after processing (off is useful for development).',
29
+ :default => true
30
+
30
31
  opt :aws_logging, "Provides the Asger logger to AWS (use for deep debugging).", :default => false
31
32
 
32
33
  opt :shared_credentials, "Tells Asger to use shared credentials from '~/.aws/credentials'.", :type => :string
33
34
  opt :iam, "Tells Asger to use IAM credentials.", :default => false
35
+ opt :region, 'Specifies an AWS region.', :type => :string
34
36
  end
35
37
 
36
38
  logger = Logger.new($stderr)
@@ -47,9 +49,10 @@ module Asger
47
49
  exit(1)
48
50
  end
49
51
 
50
- logger.warn "No tasks configured; Asger will run, but won't do much." unless (opts[:task_file] && !opts[:task_file].empty?)
52
+ logger.warn "No tasks configured; Asger will run, but won't do much." \
53
+ unless (opts[:task_file] && !opts[:task_file].empty?)
51
54
 
52
- param_files =
55
+ param_files =
53
56
  opts[:parameter_file].map do |pf|
54
57
  logger.debug "Parsing parameter file '#{pf}'."
55
58
  case File.extname(pf)
@@ -76,8 +79,12 @@ module Asger
76
79
  end
77
80
 
78
81
  aws_logger = opts[:aws_logging] ? logger : nil
79
- sqs_client = Aws::SQS::Client.new(logger: aws_logger, credentials: credentials)
80
- ec2_client = Aws::EC2::Client.new(logger: aws_logger, credentials: credentials)
82
+ sqs_client = Aws::SQS::Client.new(logger: aws_logger,
83
+ region: opts[:region], credentials: credentials)
84
+ ec2_client = Aws::EC2::Client.new(logger: aws_logger,
85
+ region: opts[:region], credentials: credentials)
86
+ asg_client = Aws::AutoScaling::Client.new(logger: aws_logger,
87
+ region: opts[:region], credentials: credentials)
81
88
 
82
89
 
83
90
  stock_scripts_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "stock_scripts"))
@@ -85,12 +92,17 @@ module Asger
85
92
 
86
93
  logger.info "Using task files:"
87
94
  task_files.each { |tf| logger.info " - #{tf}" }
88
- runner = Runner.new(logger, sqs_client, ec2_client, opts[:queue_url], parameters, task_files)
89
-
90
- logger.info "Beginning run loop. Sleeping between steps for #{opts[:pause_time]} seconds."
95
+ runner = Runner.new(logger: logger, aws_logger: aws_logger,
96
+ region: opts[:region], credentials: credentials,
97
+ queue_url: opts[:queue_url],
98
+ parameters: parameters,
99
+ task_files: task_files,
100
+ no_delete_messages: !opts[:delete_messages])
101
+
102
+ logger.info "Beginning poll loop."
91
103
  loop do
92
104
  begin
93
- runner.step()
105
+ runner.poll
94
106
  rescue StandardError => err
95
107
  logger.error "Encountered an error."
96
108
  logger.error "#{err.class.name}: #{err.message}"
@@ -98,10 +110,11 @@ module Asger
98
110
 
99
111
  if opts[:die_on_error]
100
112
  raise err
113
+ else
114
+ logger.error "re-entering poll."
101
115
  end
102
116
  end
103
- sleep opts[:pause_time] unless opts[:pause_time] == 0
104
117
  end
105
118
  end
106
119
  end
107
- end
120
+ end
data/lib/asger/runner.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'logger'
2
2
  require 'aws-sdk'
3
- require 'hashie'
3
+ require 'ice_nine'
4
+ require 'active_support/all'
4
5
 
5
6
  require 'asger/task'
6
7
 
@@ -9,66 +10,102 @@ module Asger
9
10
  # @param logger [Logger] the logger for Asger to use
10
11
  # @param sqs_client [Aws::SQS::Client] the SQS client to use for polling
11
12
  # @param ec2_client [Aws::EC2::Client] the EC2 client to use to get instance information
13
+ # @param asg_client [Aws::AutoScaling::Client] the ASG client to use to get ASG information
12
14
  # @param queue_url [String] the queue URL to poll
13
15
  # @param parameters [Hash] a hash of parameters to pass to {Task}s
14
16
  # @param task_files [Array<String>] list of file paths to load as {Task}s
15
- def initialize(logger, sqs_client, ec2_client, queue_url, parameters, task_files)
17
+ # @param no_delete_messages [TrueClass, FalseClass] if true, don't call sqs:DeleteMessage
18
+ def initialize(logger:, aws_logger:, credentials:,
19
+ region:, queue_url:,
20
+ parameters:, task_files:, no_delete_messages:)
16
21
  @logger = logger
17
- @sqs_client = sqs_client
18
- @ec2_client = ec2_client
22
+ @region = region
23
+ @parameters = IceNine.deep_freeze(parameters.merge(
24
+ region: region, credentials: credentials
25
+ ).deep_symbolize_keys)
26
+
27
+ @sqs_client = Aws::SQS::Client.new(logger: aws_logger,
28
+ region: region, credentials: credentials)
29
+ @ec2_client = Aws::EC2::Client.new(logger: aws_logger,
30
+ region: region, credentials: credentials)
31
+ @asg_client = Aws::AutoScaling::Client.new(logger: aws_logger,
32
+ region: region, credentials: credentials)
19
33
  @ec2_resource_client = Aws::EC2::Resource.new(client: @ec2_client)
34
+ @asg_resource_client = Aws::AutoScaling::Resource.new(client: @asg_client)
20
35
  @queue_url = queue_url
21
- @parameters = Hashie::Mash.new(parameters)
22
36
  @tasks = task_files.map { |tf| Task.from_file(@logger, tf) }
37
+ @no_delete_messages = no_delete_messages
23
38
 
24
39
  @logger.info "#{@tasks.length} task(s) set up."
40
+ @logger.warn('no_delete_messages is set; will not clear SQS messages!') \
41
+ if @no_delete_messages
25
42
 
26
- @tasks.each { |t| t.invoke_sanity_check(@parameters) }
43
+ @tasks.each { |t| t.invoke_init(@parameters) }
27
44
  end
28
45
 
29
46
 
30
- def step()
31
- messages = @sqs_client.receive_message(queue_url: @queue_url)[:messages]
32
- messages.each do |msg|
33
- notification = JSON.parse(JSON.parse(msg[:body])["Message"])
34
- if notification["Event"] != nil
35
- case notification["Event"].gsub("autoscaling:", "")
47
+ def poll()
48
+ poller = Aws::SQS::QueuePoller.new(@queue_url, client: @sqs_client,
49
+ max_number_of_messages: 10, skip_delete: true)
50
+
51
+ poller.poll do |msgs|
52
+ [ msgs ].flatten.each do |msg|
53
+ notification = JSON.parse(JSON.parse(msg.body)["Message"])
54
+ if notification["Event"] != nil
55
+ asg = @asg_resource_client.group(notification['AutoScalingGroupName'])
56
+ instance_id = notification["EC2InstanceId"]
57
+
58
+ @logger.warn("ASG '#{asg}' has fired event, but does not exist - already cleaned up?") \
59
+ unless asg.exists?
60
+
61
+ case notification["Event"].gsub("autoscaling:", "")
36
62
  when "EC2_INSTANCE_LAUNCH"
37
- instance_id = notification["EC2InstanceId"]
38
- @logger.info "Instance launched: #{instance_id}"
63
+ @logger.info "Instance launched in '#{asg.name}': #{instance_id}"
39
64
 
40
65
  instance = @ec2_resource_client.instance(instance_id)
41
66
  @tasks.each do |task|
42
- task.invoke_up(instance, @parameters)
67
+ task.invoke_up(instance, asg, @parameters)
43
68
  end
44
69
 
45
- delete_message(msg)
70
+ delete_message(msg) unless @no_delete_messages
46
71
  when "EC2_INSTANCE_LAUNCH_ERROR"
47
- @logger.warn "Instance launch error received."
48
- delete_message(msg)
72
+ @logger.warn "Instance failed to launch in '#{asg.name}'."
73
+
74
+ @tasks.each do |task|
75
+ task.invoke_up_failed(asg, @parameters)
76
+ end
77
+
78
+ delete_message(msg) unless @no_delete_messages
49
79
  when "EC2_INSTANCE_TERMINATE"
50
- instance_id = notification["EC2InstanceId"]
51
- @logger.info "Instance terminated: #{instance_id}"
80
+ @logger.info "Instance terminated in '#{asg.name}': #{instance_id}"
52
81
 
53
82
  @tasks.reverse_each do |task|
54
- task.invoke_down(instance_id, @parameters)
83
+ task.invoke_down(instance_id, asg, @parameters)
55
84
  end
56
- delete_message(msg)
85
+
86
+ delete_message(msg) unless @no_delete_messages
57
87
  when "EC2_INSTANCE_TERMINATE_ERROR"
58
- @logger.warn "Instance terminate error received."
59
- delete_message(msg)
88
+ @logger.warn "Instance failed to terminate in '#{asg.name}': #{instance_id}"
89
+
90
+ @tasks.reverse_each do |task|
91
+ task.invoke_down_failed(instance_id, asg, @parameters)
92
+ end
93
+ delete_message(msg) unless @no_delete_messages
60
94
  when "TEST_NOTIFICATION"
61
- @logger.debug "Found test notification in queue."
62
- delete_message(msg)
95
+ @logger.info "Found test notification in queue."
96
+ delete_message(msg) unless @no_delete_messages
63
97
  else
64
98
  @logger.debug "Unrecognized notification '#{notification["Event"]}', ignoring."
99
+ end
65
100
  end
66
101
  end
67
102
  end
68
103
  end
69
104
 
70
105
  private
106
+
71
107
  def delete_message(msg)
108
+ @logger.debug "Deleting message '#{msg[:receipt_handle]}'"
72
109
  @sqs_client.delete_message(queue_url: @queue_url, receipt_handle: msg[:receipt_handle])
73
110
  end
74
111
  end
data/lib/asger/task.rb CHANGED
@@ -15,64 +15,103 @@ module Asger
15
15
  instance_eval(code, filename, 1)
16
16
  end
17
17
 
18
- def invoke_sanity_check(parameters)
19
- if @sanity_check_proc
20
- logger.debug "Sanity checking for '#{@name}'..."
21
- @sanity_check_proc.call(parameters)
18
+ def invoke_init(parameters)
19
+ if @init_proc
20
+ logger.debug "Initializing for '#{@name}'..."
21
+ @init_proc.call(parameters)
22
22
  else
23
- logger.debug "No sanity check for '#{@name}'."
23
+ logger.debug "No init for '#{@name}'."
24
24
  end
25
25
  end
26
26
 
27
- def invoke_up(instance, parameters)
27
+ def invoke_up(instance, asg, parameters)
28
28
  if @up_proc
29
29
  logger.debug "Invoking up for '#{@name}'..."
30
- @up_proc.call(instance, parameters)
30
+ @up_proc.call(instance, asg, parameters)
31
31
  logger.debug "Up invoked for '#{@name}'..."
32
32
  else
33
33
  logger.debug "No up for '#{@name}'."
34
34
  end
35
35
  end
36
36
 
37
- def invoke_down(instance_id, parameters)
37
+ def invoke_down(instance_id, asg, parameters)
38
38
  if @down_proc
39
39
  logger.debug "Invoking down for '#{@name}'..."
40
- @down_proc.call(instance_id, parameters)
40
+ @down_proc.call(instance_id, asg, parameters)
41
41
  logger.debug "Down invoked for '#{@name}'..."
42
42
  else
43
43
  logger.debug "No down for '#{@name}'."
44
44
  end
45
45
  end
46
46
 
47
+ def invoke_up_failed(asg, parameters)
48
+ if @up_failed_proc
49
+ logger.debug "Invoking up_failed for '#{@name}'..."
50
+ @up_failed_proc.call(asg, parameters)
51
+ logger.debug "up_failed invoked for '#{@name}'..."
52
+ else
53
+ logger.debug "No up_failed for '#{@name}'."
54
+ end
55
+ end
56
+
57
+ def invoke_down_failed(instance_id, asg, parameters)
58
+ if @down_failed_proc
59
+ logger.debug "Invoking down_failed for '#{@name}'..."
60
+ @down_failed_proc.call(instance_id, asg, parameters)
61
+ logger.debug "down_failed invoked for '#{@name}'..."
62
+ else
63
+ logger.debug "No down_failed for '#{@name}'."
64
+ end
65
+ end
66
+
47
67
  def self.from_file(logger, file)
48
68
  Task.new(logger, File.read(file), file)
49
69
  end
50
70
 
51
71
  private
52
- # Defines a sanity check function, which should raise and fail (which will halt
53
- # Asger before it does anything with the actual queue) if there's a problem with
54
- # the parameter set.
55
- #
72
+ # Defines an init function, which should set member vars. Raise and fail (which
73
+ # will halt Asger before it does anything with the actual queue) if there's a
74
+ # problem with the parameter set.
75
+ #
56
76
  # @yield [parameters]
57
77
  # @yieldparam parameters [Hash] the parameters passed in to Asger
58
- def sanity_check(&block)
59
- @sanity_check_proc = block
78
+ def init(&block)
79
+ @init_proc = block
60
80
  end
61
81
 
62
- # Defines an 'up' function.
82
+ # Defines an 'up' function, addressing `EC2_INSTANCE_LAUNCH`.
63
83
  # @yield [instance, parameters]
64
84
  # @yieldparam instance [Aws::EC2::Instance] the instance that has been created
85
+ # @yieldparam asg [nil, Aws::AutoScaling::AutoScalingGroup] the ASG resource of the launched instance
65
86
  # @yieldparam parameters [Hash] the parameters passed in to Asger
66
87
  def up(&block)
67
88
  @up_proc = block
68
89
  end
69
90
 
70
- # Defines a 'down' function.
91
+ # Defines a 'down' function, addressing `EC2_INSTANCE_TERMINATE`.
71
92
  # @yield [instance_id, parameters]
72
93
  # @yieldparam instance_id [String] the ID of the recently terminated instance
94
+ # @yieldparam asg [nil, Aws::AutoScaling::AutoScalingGroup] the ASG resource of the terminated instance
73
95
  # @yieldparam parameters [Hash] the parameters passed in to Asger
74
96
  def down(&block)
75
97
  @down_proc = block
76
98
  end
99
+
100
+ # Defines an 'up_failed' function, addressing `EC2_INSTANCE_LAUNCH_ERROR`.
101
+ # @yield [asg, parameters]
102
+ # @yieldparam asg [nil, Aws::AutoScaling::AutoScalingGroup] the ASG resource of the failed instance
103
+ # @yieldparam parameters [Hash] the parameters passed in to Asger
104
+ def up_failed(&block)
105
+ @up_failed_proc = block
106
+ end
107
+
108
+ # Defines an 'up_failed' function, addressing `EC2_INSTANCE_TERMINATE_ERROR`.
109
+ # @yield [asg, parameters]
110
+ # @yieldparam instance_id [String] the ID of the instance that failed to terminate
111
+ # @yieldparam asg [nil, Aws::AutoScaling::AutoScalingGroup] the ASG resource of the failed instance
112
+ # @yieldparam parameters [Hash] the parameters passed in to Asger
113
+ def down_failed(&block)
114
+ @down_failed_proc = block
115
+ end
77
116
  end
78
117
  end
data/lib/asger/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Asger
2
2
  # The current version of Asger.
3
- VERSION = "0.2.1"
3
+ VERSION = "1.0.2"
4
4
  end
@@ -2,31 +2,31 @@
2
2
  # It will perform a Chef search for an 'instance_id' tag that matches the instance ID
3
3
  # provided by the AWS notification. To tag a node with the instance_id from bash, do
4
4
  # something like this on-node (probably in cloud-init):
5
- #
5
+ #
6
6
  # ```bash
7
7
  # knife tag create -c /path/to/client.rb $NODE_NAME $INSTANCE_ID
8
8
  # ```
9
- #
9
+ #
10
10
  # Which, in our naming scheme at Leaf, becomes:
11
- #
11
+ #
12
12
  # ```bash
13
13
  # knife tag create -c /etc/chef/client.rb vpn.test-cloud.infra.06f1d39c i-06f1d39c
14
14
  # ```
15
- #
15
+ #
16
16
  # PARAMETERS:
17
- #
17
+ #
18
18
  # chef_deregister.knife_config: the path to the knife config to use for search/node delete
19
19
 
20
20
  require 'json'
21
21
 
22
- sanity_check do |parameters|
22
+ init do |parameters|
23
23
  knife_config = parameters[:chef_deregister][:knife_config]
24
24
  raise "parameters[:chef_deregister][:knife_config] is not set" unless knife_config
25
25
 
26
26
  raise "file '#{knife_config}' does not exist" unless File.exist?(knife_config)
27
27
  end
28
28
 
29
- down do |instance_id, parameters|
29
+ down do |instance_id, asg, parameters|
30
30
  knife_config = parameters[:chef_deregister][:knife_config]
31
31
 
32
32
  search_result = Asger::Util::run_command("knife search: #{instance_id}",
@@ -1,9 +1,20 @@
1
1
  # Just logs the instances being created and destroyed to the logger for testing.
2
+ init do |parameters|
3
+ logger.info 'echo - init'
4
+ end
5
+
6
+ up do |instance, asg, parameters|
7
+ logger.info "echo - upping instance in '#{asg.name}': #{instance}"
8
+ end
2
9
 
3
- up do |instance, parameters|
4
- logger.info "upping instance: #{instance}"
10
+ up_failed do |asg, parameters|
11
+ logger.warn "echo - failed to up instance in '#{asg.name}'"
5
12
  end
6
13
 
7
- down do |instance_id, parameters|
8
- logger.info "downing instance: #{instance_id}"
9
- end
14
+ down do |instance_id, asg, parameters|
15
+ logger.info "echo - downing instance in '#{asg.name}': #{instance_id}"
16
+ end
17
+
18
+ down_failed do |instance_id, asg, parameters|
19
+ logger.warn "echo - failed to down instance in '#{asg.name}': #{instance_id}"
20
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ed Ropple
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-26 00:00:00.000000000 Z
11
+ date: 2016-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 2.0.27
61
+ version: 2.2.22
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 2.0.27
68
+ version: 2.2.22
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: trollop
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '3.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: ice_nine
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.11.2
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.11.2
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: activesupport
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -117,6 +131,7 @@ extensions: []
117
131
  extra_rdoc_files: []
118
132
  files:
119
133
  - ".gitignore"
134
+ - ".ruby-version"
120
135
  - ".yardopts"
121
136
  - Gemfile
122
137
  - LICENSE.txt
@@ -152,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
167
  version: '0'
153
168
  requirements: []
154
169
  rubyforge_project:
155
- rubygems_version: 2.4.5
170
+ rubygems_version: 2.5.1
156
171
  signing_key:
157
172
  specification_version: 4
158
173
  summary: A persistent daemon that watches an AWS autoscaling group for changes and