asger 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1d7e123884650c6770de89781c7fb27cd17106c1
4
+ data.tar.gz: 85913ef76261482b872a1d87ec892c6869bbc212
5
+ SHA512:
6
+ metadata.gz: ddee73b770fb2fc1b69b59f5e89d230ec95d135264dd697920f453756dfd0e883a69d02f25eb477905538a424a9d99ca12edcf67a1b9ca2d2a07bfa286b4e99b
7
+ data.tar.gz: f0ac3696d8f16ece760f270a9cf0d5a9ba887f58bfc20218fff238562ed58ed648c81c09b9d2a454e4c379a23cafd6b67c2c835885e4e150f37f8a66f7986384
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --private --list-undoc --markup markdown - LICENSE.md
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in asger.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Ed Ropple
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # `asger` #
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).
4
+
5
+ ### Important Notes ###
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.
7
+
8
+ ## Contributors ##
9
+ `asger` was built primarily at [Leaf](http://leaf.me) by [Ed Ropple](mailto:ed+asger@edropple.com) ([twitter](https://twitter.com/edropple)).
10
+
11
+ ## Standalone ##
12
+
13
+ `asger` is designed primarily to be run as a daemon, accepting "tasks" in the form of Ruby files. Tasks are [fairly simple](https://github.com/eropple/asger/blob/master/samples/echo.rb); more documentation will be forthcoming.
14
+
15
+ Sample usage:
16
+
17
+ ```bash
18
+ ./bin/asger --queue-url 'https://sqs.us-east-1.amazonaws.com/ACCOUNT_ID/QUEUE_NAME' --shared-credentials=CREDS --parameter-file /tmp/some_params.yaml --task-file %/echo.rb --task-file %/chef_deregister.rb
19
+ ```
20
+
21
+ (Note: `%` is a special path character when passed to `--task-file`; it refers to the `stock_scripts` directory within the gem, so you can get started right away with the stock scripts.)
22
+
23
+ ## Embedded ##
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ ```ruby
28
+ gem 'asger'
29
+ ```
30
+
31
+ And then execute:
32
+
33
+ ```bash
34
+ bundle
35
+ ```
36
+
37
+ Or install it yourself as:
38
+
39
+ ```bash
40
+ gem install asger
41
+ ```
42
+
43
+ Yardocs are available with `yard`, though they aren't complete. Nothing in `asger` is particularly complicated, though, so I recommend just taking a look at the source.
44
+
45
+ ## Contributing ##
46
+
47
+ 1. Fork it ( https://github.com/[my-github-username]/asger/fork )
48
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
49
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
50
+ 4. Push to the branch (`git push origin my-new-feature`)
51
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/asger.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'asger/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "asger"
8
+ spec.version = Asger::VERSION
9
+ spec.authors = ["Ed Ropple"]
10
+ spec.email = ["ed@edropple.com"]
11
+ spec.summary = %q{A persistent daemon that watches an AWS autoscaling group for changes and dispatches your code.}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.7"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "pry"
23
+
24
+ spec.add_runtime_dependency 'aws-sdk', '~> 2.0.27'
25
+ spec.add_runtime_dependency 'trollop', '~> 2.1.1'
26
+ spec.add_runtime_dependency "hashie", "~> 3.3"
27
+ spec.add_runtime_dependency 'activesupport', '~> 4.2.0'
28
+ end
data/bin/asger ADDED
@@ -0,0 +1,7 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'asger/cli'
7
+ Asger::CLI::main()
data/lib/asger.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "asger/version"
2
+
3
+ # The root namespace for Asger.
4
+ module Asger
5
+ end
data/lib/asger/cli.rb ADDED
@@ -0,0 +1,97 @@
1
+ require 'logger'
2
+ require 'json'
3
+ require 'yaml'
4
+ require 'trollop'
5
+
6
+ require 'aws-sdk'
7
+ require 'active_support'
8
+
9
+ require 'asger/runner'
10
+
11
+ module Asger
12
+ # Command line functionality for Asger.
13
+ module CLI
14
+ # Entry point called from `bin/asger`.
15
+ def self.main()
16
+ opts = Trollop::options do
17
+ opt :task_file, "path to a task (Ruby file; pass in order of execution; % refers to the stock_scripts directory)",
18
+ :type => :string, :multi => true
19
+ opt :parameter_file, "path to a params file (YAML or JSON; later files override earlier ones)",
20
+ :type => :string, :multi => true
21
+ opt :queue_url, "URL of the SQS queue to read from",
22
+ :type => :string
23
+ opt :pause_time, "Time (in seconds) to pause between polls.",
24
+ :default => 0
25
+ opt :verbose, "enables verbose logging",
26
+ :default => false
27
+ opt :die_on_error, "Terminates if an exception is thrown within the task runner.",
28
+ :default => true
29
+
30
+ opt :aws_logging, "Provides the Asger logger to AWS (use for deep debugging).", :default => false
31
+
32
+ opt :shared_credentials, "Tells Asger to use shared credentials from '~/.aws/credentials'.", :type => :string
33
+ end
34
+
35
+ logger = Logger.new($stderr)
36
+ logger.info "Initializing Asger."
37
+
38
+ logger.level = opts[:verbose] ? Logger::DEBUG : Logger::INFO
39
+
40
+ if !opts[:queue_url]
41
+ logger.error "--queue-url is required."
42
+ exit(1)
43
+ end
44
+ logger.warn "No tasks configured; Asger will run, but won't do much." unless (opts[:task_file] && !opts[:task_file].empty?)
45
+
46
+ param_files =
47
+ opts[:parameter_file].map do |pf|
48
+ logger.debug "Parsing parameter file '#{pf}'."
49
+ case File.extname(pf)
50
+ when ".json"
51
+ JSON.parse(File.read(pf))
52
+ when ".yaml"
53
+ YAML.load(File.read(pf))
54
+ else
55
+ raise "Unrecognized parameter file: '#{pf}'."
56
+ end
57
+ end
58
+
59
+ parameters = {}
60
+ param_files.each { |p| parameters.deep_merge!(p) }
61
+
62
+ credentials = nil
63
+ if opts[:shared_credentials]
64
+ logger.info "Using shared credentials '#{opts[:shared_credentials]}'."
65
+ credentials = Aws::SharedCredentials.new(profile_name: opts[:shared_credentials])
66
+ end
67
+
68
+ aws_logger = opts[:aws_logging] ? logger : nil
69
+ sqs_client = Aws::SQS::Client.new(logger: aws_logger, credentials: credentials)
70
+ ec2_client = Aws::EC2::Client.new(logger: aws_logger, credentials: credentials)
71
+
72
+
73
+ stock_scripts_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "stock_scripts"))
74
+ task_files = opts[:task_file].map { |f| f.gsub("%", stock_scripts_dir)}
75
+
76
+ logger.info "Using task files:"
77
+ task_files.each { |tf| logger.info " - #{tf}" }
78
+ runner = Runner.new(logger, sqs_client, ec2_client, opts[:queue_url], parameters, task_files)
79
+
80
+ logger.info "Beginning run loop. Sleeping between steps for #{opts[:pause_time]} seconds."
81
+ loop do
82
+ begin
83
+ runner.step()
84
+ rescue StandardError => err
85
+ logger.error "Encountered an error."
86
+ logger.error "#{err.class.name}: #{err.message}"
87
+ err.backtrace.each { |bt| logger.error bt }
88
+
89
+ if opts[:die_on_error]
90
+ raise err
91
+ end
92
+ end
93
+ sleep opts[:pause_time] unless opts[:pause_time] == 0
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,75 @@
1
+ require 'logger'
2
+ require 'aws-sdk'
3
+ require 'hashie'
4
+
5
+ require 'asger/task'
6
+
7
+ module Asger
8
+ class Runner
9
+ # @param logger [Logger] the logger for Asger to use
10
+ # @param sqs_client [Aws::SQS::Client] the SQS client to use for polling
11
+ # @param ec2_client [Aws::EC2::Client] the EC2 client to use to get instance information
12
+ # @param queue_url [String] the queue URL to poll
13
+ # @param parameters [Hash] a hash of parameters to pass to {Task}s
14
+ # @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)
16
+ @logger = logger
17
+ @sqs_client = sqs_client
18
+ @ec2_client = ec2_client
19
+ @ec2_resource_client = Aws::EC2::Resource.new(client: @ec2_client)
20
+ @queue_url = queue_url
21
+ @parameters = Hashie::Mash.new(parameters)
22
+ @tasks = task_files.map { |tf| Task.from_file(@logger, tf) }
23
+
24
+ @logger.info "#{@tasks.length} task(s) set up."
25
+
26
+ @tasks.each { |t| t.invoke_sanity_check(@parameters) }
27
+ end
28
+
29
+
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:", "")
36
+ when "EC2_INSTANCE_LAUNCH"
37
+ instance_id = notification["EC2InstanceId"]
38
+ @logger.info "Instance launched: #{instance_id}"
39
+
40
+ instance = @ec2_resource_client.instance(instance_id)
41
+ @tasks.each do |task|
42
+ task.invoke_up(instance, @parameters)
43
+ end
44
+
45
+ delete_message(msg)
46
+ when "EC2_INSTANCE_LAUNCH_ERROR"
47
+ @logger.warn "Instance launch error received."
48
+ delete_message(msg)
49
+ when "EC2_INSTANCE_TERMINATE"
50
+ instance_id = notification["EC2InstanceId"]
51
+ @logger.info "Instance terminated: #{instance_id}"
52
+
53
+ @tasks.reverse_each do |task|
54
+ task.invoke_down(instance_id, @parameters)
55
+ end
56
+ delete_message(msg)
57
+ when "EC2_INSTANCE_TERMINATE_ERROR"
58
+ @logger.warn "Instance terminate error received."
59
+ delete_message(msg)
60
+ when "TEST_NOTIFICATION"
61
+ @logger.debug "Found test notification in queue."
62
+ delete_message(msg)
63
+ else
64
+ @logger.debug "Unrecognized notification '#{notification["Event"]}', ignoring."
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+ def delete_message(msg)
72
+ @sqs_client.delete_message(queue_url: @queue_url, receipt_handle: msg[:receipt_handle])
73
+ end
74
+ end
75
+ end
data/lib/asger/task.rb ADDED
@@ -0,0 +1,78 @@
1
+ require 'asger/util'
2
+
3
+ module Asger
4
+ # A `Task` is a wrapper around an `up` and a `down` function. Up functions
5
+ # are called when an auto-scaling group adds an instance, and Asger retrieves
6
+ # the instance data for the task. Down functions are called when an
7
+ # auto-scaling group downs an instance, and Asger passes along only the
8
+ # instance ID (because it's all we've got).
9
+ class Task
10
+ attr_reader :logger
11
+
12
+ def initialize(logger, code, filename = "unknown_file.rb")
13
+ @logger = logger
14
+ @name = File.basename(filename)
15
+ instance_eval(code, filename, 1)
16
+ end
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)
22
+ else
23
+ logger.debug "No sanity check for '#{@name}'."
24
+ end
25
+ end
26
+
27
+ def invoke_up(instance, parameters)
28
+ if @up_proc
29
+ logger.debug "Invoking up for '#{@name}'..."
30
+ @up_proc.call(instance, parameters)
31
+ logger.debug "Up invoked for '#{@name}'..."
32
+ else
33
+ logger.debug "No up for '#{@name}'."
34
+ end
35
+ end
36
+
37
+ def invoke_down(instance_id, parameters)
38
+ if @down_proc
39
+ logger.debug "Invoking down for '#{@name}'..."
40
+ @down_proc.call(instance_id, parameters)
41
+ logger.debug "Down invoked for '#{@name}'..."
42
+ else
43
+ logger.debug "No down for '#{@name}'."
44
+ end
45
+ end
46
+
47
+ def self.from_file(logger, file)
48
+ Task.new(logger, File.read(file), file)
49
+ end
50
+
51
+ 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
+ #
56
+ # @yield [parameters]
57
+ # @yieldparam parameters [Hash] the parameters passed in to Asger
58
+ def sanity_check(&block)
59
+ @sanity_check_proc = block
60
+ end
61
+
62
+ # Defines an 'up' function.
63
+ # @yield [instance, parameters]
64
+ # @yieldparam instance [Aws::EC2::Instance] the instance that has been created
65
+ # @yieldparam parameters [Hash] the parameters passed in to Asger
66
+ def up(&block)
67
+ @up_proc = block
68
+ end
69
+
70
+ # Defines a 'down' function.
71
+ # @yield [instance_id, parameters]
72
+ # @yieldparam instance_id [String] the ID of the recently terminated instance
73
+ # @yieldparam parameters [Hash] the parameters passed in to Asger
74
+ def down(&block)
75
+ @down_proc = block
76
+ end
77
+ end
78
+ end
data/lib/asger/util.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'open3'
2
+
3
+ module Asger
4
+ module Util
5
+
6
+ def self.run_command(subcommand_name, command, logger = nil, directory = nil)
7
+ directory = directory || Dir.getwd()
8
+ logger.info "[#{subcommand_name}] => #{command}" unless !logger
9
+
10
+ lines = []
11
+
12
+ Dir.chdir(directory) do
13
+ # TODO: this should probably raise on nonzero.
14
+ thread = Open3::popen3(command) do |stdin, stdout, stderr, wait_thr|
15
+ stdout.read.split("\n").each do |line|
16
+ lines << line
17
+ logger.debug "[#{subcommand_name}] O: #{line}" unless !logger
18
+ end
19
+ stderr.read.split("\n").each do |line|
20
+ logger.debug "[#{subcommand_name}] E: #{line}" unless !logger
21
+ end
22
+
23
+ wait_thr
24
+ end
25
+
26
+ [ thread.value.exitstatus, lines.join("\n") ]
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ module Asger
2
+ # The current version of Asger.
3
+ VERSION = "0.2.0"
4
+ end
@@ -0,0 +1,61 @@
1
+ # This script will deregister a node from the Chef server given in the parameters files.
2
+ # It will perform a Chef search for an 'instance_id' tag that matches the instance ID
3
+ # provided by the AWS notification. To tag a node with the instance_id from bash, do
4
+ # something like this on-node (probably in cloud-init):
5
+ #
6
+ # ```bash
7
+ # knife tag create -c /path/to/client.rb $NODE_NAME $INSTANCE_ID
8
+ # ```
9
+ #
10
+ # Which, in our naming scheme at Leaf, becomes:
11
+ #
12
+ # ```bash
13
+ # knife tag create -c /etc/chef/client.rb vpn.test-cloud.infra.06f1d39c i-06f1d39c
14
+ # ```
15
+ #
16
+ # PARAMETERS:
17
+ #
18
+ # chef_deregister.knife_config: the path to the knife config to use for search/node delete
19
+
20
+ require 'json'
21
+
22
+ sanity_check do |parameters|
23
+ knife_config = parameters[:chef_deregister][:knife_config]
24
+ raise "parameters[:chef_deregister][:knife_config] is not set" unless knife_config
25
+
26
+ raise "file '#{knife_config}' does not exist" unless File.exist?(knife_config)
27
+ end
28
+
29
+ down do |instance_id, parameters|
30
+ knife_config = parameters[:chef_deregister][:knife_config]
31
+
32
+ search_result = Asger::Util::run_command("knife search: #{instance_id}",
33
+ "knife search node -c '#{knife_config}' -i -F json 'tags:#{instance_id}'",
34
+ logger)
35
+
36
+ raise "knife search for '#{instance_id}' failed with exit code #{search_result[0]}." unless search_result[0] == 0
37
+
38
+ search_json = JSON.parse(search_result[1])
39
+
40
+ case search_json["rows"].length
41
+ when 0
42
+ logger.warn "No Chef entry found for instance '#{instance_id}'."
43
+ logger.warn "Make sure the instance was Chef-tagged and came up successfully."
44
+ when 1
45
+ node_name = search_json["rows"][0]
46
+ delete_node_result = Asger::Util::run_command("knife node delete: #{node_name}",
47
+ "knife node delete -y -c '#{knife_config}' #{node_name}",
48
+ logger)
49
+ raise "knife node delete for '#{instance_id}' failed with exit code #{search_result[0]}." unless delete_node_result[0] == 0
50
+ delete_client_result = Asger::Util::run_command("knife client delete: #{node_name}",
51
+ "knife client delete -y -c '#{knife_config}' #{node_name}",
52
+ logger)
53
+
54
+ raise "knife client delete for '#{instance_id}' failed with exit code #{search_result[0]}." unless delete_client_result[0] == 0
55
+ else
56
+ logger.error "#{search_json["rows"].length} entries found in Chef for '#{instance_id}."
57
+ logger.error "I don't know what to safely do; you should handle this yourself."
58
+ end
59
+
60
+ logger.info "Removed instance '#{instance_id}' from Chef."
61
+ end
@@ -0,0 +1,9 @@
1
+ # Just logs the instances being created and destroyed to the logger for testing.
2
+
3
+ up do |instance, parameters|
4
+ logger.info "upping instance: #{instance}"
5
+ end
6
+
7
+ down do |instance_id, parameters|
8
+ logger.info "downing instance: #{instance_id}"
9
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: asger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Ed Ropple
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: aws-sdk
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.27
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.27
69
+ - !ruby/object:Gem::Dependency
70
+ name: trollop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 2.1.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 2.1.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: hashie
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.3'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: activesupport
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 4.2.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 4.2.0
111
+ description:
112
+ email:
113
+ - ed@edropple.com
114
+ executables:
115
+ - asger
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".yardopts"
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - asger.gemspec
126
+ - bin/asger
127
+ - lib/asger.rb
128
+ - lib/asger/cli.rb
129
+ - lib/asger/runner.rb
130
+ - lib/asger/task.rb
131
+ - lib/asger/util.rb
132
+ - lib/asger/version.rb
133
+ - stock_scripts/chef_deregister.rb
134
+ - stock_scripts/echo.rb
135
+ homepage: ''
136
+ licenses:
137
+ - MIT
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.2.2
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: A persistent daemon that watches an AWS autoscaling group for changes and
159
+ dispatches your code.
160
+ test_files: []
161
+ has_rdoc: