asger 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 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: