unsub 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []