unsub 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c7573632df7e513045df45faf213a4ceb43711a9
4
+ data.tar.gz: cb65143b20481d3f8981248a2493dc577d23ce2f
5
+ SHA512:
6
+ metadata.gz: 4ed4f03b1f05827b24d18a0d0f967dbcbdc2c9b8e868b433323a2aaf0de093abe132be95f7722f0c28ebc37dbb0c5940414b54fe89b9655eec6ca26149c03b0c
7
+ data.tar.gz: 2df0645c5915ac9a6227331d53de395fc9667a7981b0f509f3099e1498d0b795fdde425648c8e818fc3d9729e0eff4055a171548da101a7dcb1ceb094f017095
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2015 Sean Clemmer and Blue Jeans Network
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,78 @@
1
+ # Unsub ![Version](https://img.shields.io/gem/v/unsub.svg?style=flat-square)
2
+
3
+ Proof-of-concept autoscaling "polyfill" for Sensu, Icinga & Chef
4
+
5
+ On _launch_:
6
+
7
+ - Icinga: Disable host downtime
8
+ - Chef: Remove tag "termianted", add tag "launched"
9
+
10
+ On _terminate_:
11
+
12
+ - Sensu: Remove host's client
13
+ - Icinga: Enable host downtime
14
+ - Chef: Remove tag "launched", add tag "terminated"
15
+
16
+
17
+
18
+ ## Installation, Usage &c.
19
+
20
+ ### Installation
21
+
22
+ Download the appropriate package for your system from the [GitHub releases page](https://github.com/sczizzo/unsub/releases)
23
+
24
+ If you've got a sane Ruby environment around, you can use RubyGems:
25
+
26
+ $ gem install unsub
27
+
28
+ ### Usage
29
+
30
+ Just call for help!
31
+
32
+ $ unsub help
33
+ Commands:
34
+ unsub art # Show the application art
35
+ unsub go ... # Start the main event loop
36
+ unsub help [COMMAND] # Describe available commands or one specific command
37
+ unsub version # Show the application version
38
+
39
+ Probably you're most interested in the default command (`go`):
40
+
41
+ $ unsub help go
42
+ Usage:
43
+ unsub go \
44
+ -c, --icinga-cmd=ICINGA_CMD \
45
+ -k, --knife-file=KNIFE_FILE \
46
+ -q, --queue-url=QUEUE_URL \
47
+ -s, --sensu-url=SENSU_URL
48
+
49
+ Options:
50
+ -d, [--database=DATABASE] # Local database path
51
+ # Default: .ops1722.db
52
+ -q, --queue-url=QUEUE_URL # SQS Queue URL
53
+ -c, --icinga-cmd=ICINGA_CMD # Icinga Command URI
54
+ -s, --sensu-url=SENSU_URL # Sensu API base URL
55
+ -k, --knife-file=KNIFE_FILE # Knife file for Chef
56
+ # Default: $HOME/.chef/knife.rb
57
+ -l, [--log=LOG] # Log to file instead of STDOUT
58
+ -v, [--debug], [--no-debug] # Enable DEBUG-level logging
59
+ -z, [--trace], [--no-trace] # Enable TRACE-level logging
60
+
61
+ Start the main event loop
62
+
63
+ Here's an example:
64
+
65
+ $ unsub \
66
+ -q 'https://sqs.my-region.amazonaws.com/123456789/my-queue' \
67
+ -c 'http://username:password@icinga/cgi-bin/icinga/cmd.cgi' \
68
+ -s 'http://sensu:1234' -d '/var/data/unsub.db'
69
+
70
+
71
+
72
+ ## Changelog
73
+
74
+ ### v1.0
75
+
76
+ _In development_
77
+
78
+ - Initial support for Sensu, Icinga & Chef
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'unsub'
3
+ Unsub::Main.start
@@ -0,0 +1 @@
1
+ require_relative 'unsub/main'
@@ -0,0 +1,44 @@
1
+ module Unsub
2
+ class Event
3
+ attr_reader :kind, :host
4
+
5
+ def initialize message, db, aws, services
6
+ discover_kind message
7
+ discover_host message, db, aws, services
8
+ end
9
+
10
+
11
+ private
12
+
13
+ def discover_kind message
14
+ @kind = case message['Event']
15
+ when 'autoscaling:EC2_INSTANCE_LAUNCH'
16
+ :launch
17
+ when 'autoscaling:EC2_INSTANCE_TERMINATE'
18
+ :terminate
19
+ when 'autoscaling:EC2_INSTANCE_LAUNCH_ERROR'
20
+ :launch_error
21
+ when 'autoscaling:EC2_INSTANCE_TERMINATE_ERROR'
22
+ :terminate_error
23
+ end
24
+ end
25
+
26
+
27
+ def discover_host message, db, aws, services
28
+ id = message['EC2InstanceId']
29
+
30
+ @host = unless id.nil? || id.empty?
31
+ is = aws[:ec2].describe_instances instance_ids: [ id ]
32
+ ip = is.reservations.first.instances.first.private_ip_address
33
+
34
+ host = { id: id, ip: ip }
35
+ cached_host = db[id] || {}
36
+ cached_host.each { |k,v| host[k] = v if host[k].nil? && v }
37
+ services.each { |_, service| service.extend_host host }
38
+
39
+ db.set! id, host
40
+ host
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,125 @@
1
+ require 'net/http'
2
+ require 'socket'
3
+ require 'json'
4
+ require 'date'
5
+ require 'time'
6
+ require 'uri'
7
+
8
+ require 'daybreak'
9
+ require 'aws-sdk'
10
+
11
+ require_relative 'mjolnir'
12
+ require_relative 'metadata'
13
+ require_relative 'service'
14
+ require_relative 'event'
15
+
16
+
17
+ Thread.abort_on_exception = false
18
+
19
+
20
+
21
+ module Unsub
22
+ class Main < Mjolnir
23
+
24
+ default_command :go
25
+
26
+ KNIFE = [
27
+ File.join(ENV['HOME'], '.chef', 'knife.rb'),
28
+ File.join('/etc', 'chef', 'knife.rb'),
29
+ ENV['KNIFE']
30
+ ].select { |f| f && File.exists?(f) }.shift
31
+
32
+
33
+ desc 'version', 'Show the application version'
34
+ def version
35
+ puts VERSION
36
+ end
37
+
38
+
39
+ desc 'art', 'Show the application art'
40
+ def art
41
+ puts "\n%s\n" % ART
42
+ end
43
+
44
+
45
+ desc 'go', 'Start the main event loop'
46
+ option :database, \
47
+ type: :string,
48
+ aliases: %w[ -d ],
49
+ desc: 'Local database path',
50
+ default: '.ops1722.db'
51
+ option :queue_url, \
52
+ type: :string,
53
+ aliases: %w[ -q ],
54
+ desc: 'SQS Queue URL',
55
+ required: true
56
+ option :icinga_cmd, \
57
+ type: :string,
58
+ aliases: %w[ -c ],
59
+ desc: 'Icinga Command URI',
60
+ required: true
61
+ option :sensu_url, \
62
+ type: :string,
63
+ aliases: %w[ -s ],
64
+ desc: 'Sensu API base URL',
65
+ required: true
66
+ option :knife_file, \
67
+ type: :string,
68
+ aliases: %w[ -k ],
69
+ desc: 'Knife file for Chef',
70
+ default: KNIFE,
71
+ required: true
72
+ include_common_options
73
+ def go
74
+ db = Daybreak::DB.new options.database
75
+
76
+ aws = {
77
+ ec2: Aws::EC2::Client.new,
78
+ sqs: Aws::SQS::Client.new
79
+ }
80
+
81
+ services = {
82
+ icinga: Service::Icinga.new(options.icinga_cmd, log),
83
+ sensu: Service::Sensu.new(options.sensu_url, log),
84
+ chef: Service::Chef.new(options.knife_file, log)
85
+ }
86
+
87
+ log.info service: 'main', event: 'hello', \
88
+ db: db, aws: aws, services: services
89
+
90
+ loop do
91
+ response = aws[:sqs].receive_message queue_url: options.queue_url
92
+
93
+ response.messages.each do |raw|
94
+ receipt = raw.receipt_handle
95
+ body = JSON.parse raw.body
96
+ message = JSON.parse body['Message']
97
+ event = Event.new message, db, aws, services
98
+
99
+ success = case event.kind
100
+ when :launch
101
+ s1 = services[:icinga].disable_downtime event.host
102
+ s2 = services[:chef].remove_tag event.host, 'terminated'
103
+ s3 = services[:chef].add_tag event.host, 'launched'
104
+ s1 && s2 && s3
105
+ when :terminate
106
+ s1 = services[:sensu].delete_client event.host
107
+ s2 = services[:icinga].enable_downtime event.host
108
+ s3 = services[:chef].remove_tag event.host, 'launched'
109
+ s4 = services[:chef].add_tag event.host, 'terminated'
110
+ db.delete! event.host[:id]
111
+ s1 && s2 && s3 && s4
112
+ end
113
+
114
+ aws[:sqs].delete_message \
115
+ queue_url: options.queue_url,
116
+ receipt_handle: receipt
117
+
118
+ log.info service: 'main', event: 'processed', \
119
+ message: message, receipt: receipt, success: success
120
+ end
121
+ end
122
+ end
123
+
124
+ end
125
+ end
@@ -0,0 +1,47 @@
1
+ module Unsub
2
+ # Officially, it's "unsub"
3
+ NAME = 'unsub'
4
+
5
+ # A quick summary for use in the command-line interface
6
+ SUMMARY = %q.Autoscaling "polyfill" for Sensu, Icinga & Chef.
7
+
8
+ # Take credit for your work
9
+ AUTHOR = 'Sean Clemmer'
10
+
11
+ # Take responsibility for your work
12
+ EMAIL = 'sczizzo@gmail.com'
13
+
14
+ # Like the MIT license, but even simpler
15
+ LICENSE = 'ISC'
16
+
17
+ # Where you should look first
18
+ HOMEPAGE = 'https://github.com/sczizzo/unsub'
19
+
20
+ # Project root
21
+ ROOT = File.join File.dirname(__FILE__), '..', '..'
22
+
23
+ # Pull the project version out of the VERSION file
24
+ VERSION = File.read(File.join(ROOT, 'VERSION')).strip
25
+
26
+ # Bundled extensions
27
+ TRAVELING_RUBY_VERSION = '20150517-2.2.2'
28
+ THIN_VERSION = '1.6.3'
29
+ FFI_VERSION = '1.9.6'
30
+ EM_VERSION = '1.0.4'
31
+
32
+ # Big money
33
+ ART = <<-'EOART'
34
+ ,---,
35
+ ,--, ,---, ,--,,---.'|
36
+ ,'_ /| ,-+-. / | .--.--. ,'_ /|| | :
37
+ .--. | | : ,--.'|' |/ / ' .--. | | :: : :
38
+ ,'_ /| : . || | ,"' | : /`./ ,'_ /| : . |: |,-.
39
+ | ' | | . .| | / | | : ;_ | ' | | . .| : ' |
40
+ | | ' | | || | | | |\ \ `. | | ' | | || | / :
41
+ : | : ; ; || | | |/ `----. \: | : ; ; |' : |: |
42
+ ' : `--' \ | |--' / /`--' /' : `--' \ | '/ :
43
+ : , .-./ |/ '--'. / : , .-./ : |
44
+ `--`----' '---' `--'---' `--`----' / \ /
45
+ `-'----'
46
+ EOART
47
+ end
@@ -0,0 +1,53 @@
1
+ require 'slog'
2
+ require 'thor'
3
+
4
+
5
+ module Unsub
6
+
7
+ # Thor's hammer! Like Thor with better logging
8
+ class Mjolnir < ::Thor
9
+
10
+ # Common options for Thor commands
11
+ COMMON_OPTIONS = {
12
+ log: {
13
+ type: :string,
14
+ aliases: %w[ -l ],
15
+ desc: 'Log to file instead of STDOUT',
16
+ default: ENV['UNSUB_LOG'] || nil
17
+ },
18
+ debug: {
19
+ type: :boolean,
20
+ aliases: %w[ -v ],
21
+ desc: 'Enable DEBUG-level logging',
22
+ default: ENV['UNSUB_DEBUG'] || false
23
+ },
24
+ trace: {
25
+ type: :boolean,
26
+ aliases: %w[ -z ],
27
+ desc: 'Enable TRACE-level logging',
28
+ default: ENV['UNSUB_TRACE'] || false
29
+ }
30
+ }
31
+
32
+ # Decorate Thor commands with the options above
33
+ def self.include_common_options
34
+ COMMON_OPTIONS.each do |name, spec|
35
+ option name, spec
36
+ end
37
+ end
38
+
39
+
40
+ no_commands do
41
+
42
+ # Construct a Logger given the command-line options
43
+ def log
44
+ return @logger if defined? @logger
45
+ @logger = Slog.new out: (options.log || $stdout), prettify: false
46
+ @logger.level = :debug if options.debug?
47
+ @logger.level = :trace if options.trace?
48
+ @logger
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'service/base'
2
+ require_relative 'service/chef'
3
+ require_relative 'service/sensu'
4
+ require_relative 'service/icinga'
@@ -0,0 +1,8 @@
1
+ module Unsub
2
+ module Service
3
+ class Base
4
+ def extend_host h ; h end
5
+ def log ; @log end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,59 @@
1
+ require 'chef-api'
2
+
3
+ require_relative 'chef/config'
4
+
5
+
6
+ module Unsub
7
+ module Service
8
+ class Chef < Base
9
+ attr_reader :knife, :api
10
+
11
+ def initialize knife, log
12
+ @log = log
13
+ @knife = Config.new knife
14
+ @api = ChefAPI::Connection.new \
15
+ endpoint: @knife.chef_server_url,
16
+ client: @knife.node_name,
17
+ key: @knife.client_key
18
+ end
19
+
20
+
21
+ def extend_host host
22
+ name = if ip = host[:ip]
23
+ api.search.query(:node, 'ipaddress:"%s"' % ip).rows.shift.name rescue nil
24
+ end
25
+
26
+ old_host = host.dup ; host.merge! chef_name: name if name
27
+ log.info service: 'chef', event: 'extend_host', old_host: old_host, host: host
28
+ host
29
+ end
30
+
31
+
32
+ def add_tag host, tag
33
+ success = if name = host[:chef_name]
34
+ node = api.nodes.fetch name
35
+ node.normal['tags'] ||= []
36
+ node.normal['tags'] << tag
37
+ node.normal['tags'].uniq!
38
+ !!node.save
39
+ end
40
+
41
+ log.info service: 'chef', event: 'add_tag', host: host, tag: tag, success: success
42
+ success
43
+ end
44
+
45
+
46
+ def remove_tag host, tag
47
+ success = if name = host[:chef_name]
48
+ node = api.nodes.fetch name
49
+ node.normal['tags'] ||= []
50
+ node.normal['tags'].delete tag
51
+ !!node.save
52
+ end
53
+
54
+ log.info service: 'chef', event: 'remove_tag', host: host, tag: tag, success: success
55
+ success
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Cribbed from Ridley: https://github.com/reset/ridley/blob/master/lib/ridley/chef/config.rb
3
+ #
4
+ require 'buff/config/ruby'
5
+
6
+ module Unsub
7
+ module Service
8
+ class Chef < Base
9
+ class Config < Buff::Config::Ruby
10
+ class << self
11
+ def location
12
+ possibles = []
13
+ possibles << ENV['CHEF_CONFIG'] if ENV['CHEF_CONFIG']
14
+ possibles << File.join(ENV['KNIFE_HOME'], 'knife.rb') if ENV['KNIFE_HOME']
15
+ possibles << File.join(Dir.pwd, 'knife.rb')
16
+ possibles << File.join(ENV['HOME'], '.chef', 'knife.rb') if ENV['HOME']
17
+ possibles.compact!
18
+
19
+ location = possibles.find { |loc| File.exists?(File.expand_path(loc)) }
20
+ File.expand_path(location) unless location.nil?
21
+ end
22
+ end
23
+
24
+ set_assignment_mode :carefree
25
+
26
+ attribute :node_name,
27
+ default: -> { Socket.gethostname }
28
+ attribute :chef_server_url,
29
+ default: 'http://localhost:4000'
30
+ attribute :client_key,
31
+ default: -> { '/etc/chef/client.pem' }
32
+
33
+ def initialize(path, options = {})
34
+ super(path || self.class.location, options)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ module Unsub
2
+ module Service
3
+ class Icinga < Base
4
+ attr_reader :uri, :http
5
+
6
+ def initialize cmd_url, log
7
+ @uri = URI.parse cmd_url
8
+ @http = Net::HTTP.new uri.host, uri.port
9
+ @log = log
10
+ end
11
+
12
+
13
+ def enable_downtime host
14
+ success = cgi_command 28, host # CMD_ENABLE_HOST_SVC_NOTIFICATIONS
15
+ log.info service: 'icinga', event: 'enable_downtime', host: host, success: success
16
+ success
17
+ end
18
+
19
+
20
+ def disable_downtime host
21
+ success = cgi_command 29, host # CMD_DISABLE_HOST_SVC_NOTIFICATIONS
22
+ log.info service: 'icinga', event: 'disable_downtime', host: host, success: success
23
+ success
24
+ end
25
+
26
+
27
+ private
28
+
29
+ def cgi_command command, host, opts={}
30
+ params = {
31
+ cmd_typ: command,
32
+ cmd_mod: 2,
33
+ host: (host[:sensu_name] || host[:chef_name]),
34
+ btnSubmit: 'Commit'
35
+ }.merge opts
36
+
37
+ if name = params[:host]
38
+ request = Net::HTTP::Post.new uri.request_uri
39
+ request.basic_auth uri.user, uri.password
40
+ request.set_form_data params
41
+ response = http.request request
42
+ response.kind_of? Net::HTTPSuccess
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ module Unsub
2
+ module Service
3
+ class Sensu < Base
4
+ attr_reader :http
5
+
6
+ def initialize url, log
7
+ uri = URI.parse url
8
+ @http = Net::HTTP.new uri.host, uri.port
9
+ @log = log
10
+ end
11
+
12
+
13
+ def extend_host host
14
+ name = if ip = host[:ip]
15
+ get_clients = Net::HTTP::Get.new '/clients?limit=1000000'
16
+ response = http.request get_clients
17
+ clients = JSON.parse response.body, symbolize_names: true
18
+ client = clients.select { |c| c[:address] == ip }.shift
19
+ client.nil? ? nil : client[:name]
20
+ end
21
+
22
+ old_host = host.dup ; host.merge! sensu_name: name if name
23
+ log.info service: 'sensu', event: 'extend_host', old_host: old_host, host: host
24
+ host
25
+ end
26
+
27
+
28
+ def delete_client host
29
+ success = if name = host[:sensu_name]
30
+ delete_client = Net::HTTP::Delete.new '/clients/%s' % name
31
+ response = http.request delete_client
32
+ response.kind_of? Net::HTTPSuccess
33
+ end
34
+
35
+ log.info service: 'sensu', event: 'delete_client', host: host, success: success
36
+ success
37
+ end
38
+ end
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,185 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unsub
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sean Clemmer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: slog
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aws-sdk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
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: daybreak
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: chef-api
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: buff-config
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: eventmachine
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 1.0.4
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 1.0.4
111
+ - !ruby/object:Gem::Dependency
112
+ name: thin
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '='
116
+ - !ruby/object:Gem::Version
117
+ version: 1.6.3
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '='
123
+ - !ruby/object:Gem::Version
124
+ version: 1.6.3
125
+ - !ruby/object:Gem::Dependency
126
+ name: ffi
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '='
130
+ - !ruby/object:Gem::Version
131
+ version: 1.9.6
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '='
137
+ - !ruby/object:Gem::Version
138
+ version: 1.9.6
139
+ description: Autoscaling "polyfill" for Sensu, Icinga & Chef.
140
+ email: sczizzo@gmail.com
141
+ executables:
142
+ - unsub
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - LICENSE
147
+ - Readme.md
148
+ - VERSION
149
+ - bin/unsub
150
+ - lib/unsub.rb
151
+ - lib/unsub/event.rb
152
+ - lib/unsub/main.rb
153
+ - lib/unsub/metadata.rb
154
+ - lib/unsub/mjolnir.rb
155
+ - lib/unsub/service.rb
156
+ - lib/unsub/service/base.rb
157
+ - lib/unsub/service/chef.rb
158
+ - lib/unsub/service/chef/config.rb
159
+ - lib/unsub/service/icinga.rb
160
+ - lib/unsub/service/sensu.rb
161
+ homepage: https://github.com/sczizzo/unsub
162
+ licenses:
163
+ - ISC
164
+ metadata: {}
165
+ post_install_message:
166
+ rdoc_options: []
167
+ require_paths:
168
+ - lib
169
+ required_ruby_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ required_rubygems_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ requirements: []
180
+ rubyforge_project:
181
+ rubygems_version: 2.4.5
182
+ signing_key:
183
+ specification_version: 4
184
+ summary: Autoscaling "polyfill" for Sensu, Icinga & Chef
185
+ test_files: []