aws-cleaner 2.0.1 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7dbf7b40e9d34f325743db1b1a16b359fb7ff316
4
- data.tar.gz: 1f4479e2e60ebb733785c73940e086694c0de5f2
3
+ metadata.gz: 634ceede119aecae922a17090cb5d18a18855e54
4
+ data.tar.gz: e1f7409e0ba5e6096bbc9d2667aba98333e47054
5
5
  SHA512:
6
- metadata.gz: 6a8db0835e460feb45834b4c382af525ed56d786605ad21a58420234e2329776b87a42b11ae2025a141eb9d16bfc36e6c57d4c227fd02f499a5794f91075717e
7
- data.tar.gz: ffb52f2e284ef0542dd04364713f2f47fe5e56d0646e5f580f74749508305d71ddaff34384a723ca5ca117bc2c3b8da3d5ce54b8305709caa8935eac100c5b23
6
+ metadata.gz: 0d03730f5c4a09e5ba56eb1dc4cebce0c3a27fe320a776a543faa30d70f11d85a06de04010a69537d7cd1712fbf26e4a61b96474271bd4627e7e32974c081ad4
7
+ data.tar.gz: 51409d6d97e5abb81c4bdc722f1ac7b7e0ff1d6a9915fe3ac1ff9ea356d8b4cb9ce9f5e76ebb5c95c5427747270ece2540fd6e2c73e107defd2599884e2df007
@@ -4,10 +4,11 @@
4
4
  # and remove the node from Chef and Sensu and send a notification
5
5
  # to Hipchat or Slack
6
6
  #
7
- # Copyright (c) 2015, 2016 Eric Heydrick
7
+ # Copyright (c) 2015-2017 Eric Heydrick
8
8
  # Licensed under The MIT License
9
9
  #
10
10
 
11
+ # ensure gems are present
11
12
  begin
12
13
  require 'json'
13
14
  require 'yaml'
@@ -21,6 +22,9 @@ rescue LoadError => e
21
22
  raise "Missing gems: #{e}"
22
23
  end
23
24
 
25
+ # require our class
26
+ require_relative '../lib/aws_cleaner/aws_cleaner.rb'
27
+
24
28
  def config(file)
25
29
  YAML.load(File.read(file))
26
30
  rescue StandardError => e
@@ -28,185 +32,21 @@ rescue StandardError => e
28
32
  end
29
33
 
30
34
  # get options
31
- opts = Trollop::options do
35
+ opts = Trollop.options do
32
36
  opt :config, 'Path to config file', type: :string, default: 'config.yml'
33
37
  end
34
38
 
35
39
  @config = config(opts[:config])
36
40
 
37
- @sqs = Aws::SQS::Client.new(@config[:aws])
38
-
39
- @chef = ChefAPI::Connection.new(
40
- endpoint: @config[:chef][:url],
41
- client: @config[:chef][:client],
42
- key: @config[:chef][:key]
43
- )
44
-
45
- # delete the message from SQS
46
- def delete_message(id)
47
- delete = @sqs.delete_message(
48
- queue_url: @config[:sqs][:queue],
49
- receipt_handle: id
50
- )
51
- delete ? true : false
52
- end
53
-
54
- # return the body of the SQS message in JSON
55
- def parse(body)
56
- JSON.parse(body)
57
- rescue JSON::ParserError
58
- return false
59
- end
60
-
61
- # return the instance_id of the terminated instance
62
- def process_message(message_body)
63
- return false if message_body['detail']['instance-id'].nil? &&
64
- message_body['detail']['state'] != 'terminated'
65
-
66
- instance_id = message_body['detail']['instance-id']
67
- instance_id
68
- end
69
-
70
- # call the Chef API to get the node name of the instance
71
- def get_chef_node_name(instance_id)
72
- results = @chef.search.query(:node, "ec2_instance_id:#{instance_id} OR chef_provisioning_reference_server_id:#{instance_id}")
73
- if results.rows.size > 0
74
- return results.rows.first['name']
75
- else
76
- return false
77
- end
78
- end
79
-
80
- # call the Chef API to get the FQDN of the instance
81
- def get_chef_fqdn(instance_id)
82
- results = @chef.search.query(:node, "ec2_instance_id:#{instance_id} OR chef_provisioning_reference_server_id:#{instance_id}")
83
- if results.rows.size > 0
84
- return results.rows.first['automatic']['fqdn']
85
- else
86
- return false
87
- end
88
- end
89
-
90
- # check if the node exists in Sensu
91
- def in_sensu?(node_name)
92
- begin
93
- RestClient::Request.execute(url: "#{@config[:sensu][:url]}/clients/#{node_name}", method: :get, timeout: 5, open_timeout: 5)
94
- rescue RestClient::ResourceNotFound
95
- return false
96
- rescue => e
97
- puts "Sensu request failed: #{e}"
98
- return false
99
- else
100
- return true
101
- end
102
- end
103
-
104
- # call the Sensu API to remove the node
105
- def remove_from_sensu(node_name)
106
- response = RestClient::Request.execute(url: "#{@config[:sensu][:url]}/clients/#{node_name}", method: :delete, timeout: 5, open_timeout: 5)
107
- case response.code
108
- when 202
109
- notify_chat('Removed ' + node_name + ' from Sensu')
110
- return true
111
- else
112
- notify_chat('Failed to remove ' + node_name + ' from Sensu')
113
- return false
114
- end
115
- end
116
-
117
- # call the Chef API to remove the node
118
- def remove_from_chef(node_name)
119
- begin
120
- client = @chef.clients.fetch(node_name)
121
- client.destroy
122
- node = @chef.nodes.fetch(node_name)
123
- node.destroy
124
- rescue => e
125
- puts "Failed to remove chef node: #{e}"
126
- else
127
- notify_chat('Removed ' + node_name + ' from Chef')
128
- end
129
- end
130
-
131
- # notify hipchat
132
- def notify_hipchat(msg)
133
- hipchat = HipChat::Client.new(
134
- @config[:hipchat][:api_token],
135
- api_version: 'v2'
136
- )
137
- room = @config[:hipchat][:room]
138
- hipchat[room].send('AWS Cleaner', msg)
139
- end
140
-
141
- # notify slack
142
- def notify_slack(msg)
143
- slack = Slack::Poster.new(@config[:slack][:webhook_url])
144
- slack.channel = @config[:slack][:channel]
145
- slack.username = @config[:slack][:username] ||= 'aws-cleaner'
146
- slack.icon_emoji = @config[:slack][:icon_emoji] ||= nil
147
- slack.send_message(msg)
148
- end
149
-
150
- # generic chat notification method
151
- def notify_chat(msg)
152
- if @config[:hipchat][:enable]
153
- notify_hipchat(msg)
154
- elsif @config[:slack][:enable]
155
- notify_slack(msg)
156
- end
157
- end
158
-
159
- # generate the URL for the webhook
160
- def generate_template(item, template_variable_method, template_variable_argument, template_variable)
161
- begin
162
- replacement = send(template_variable_method, eval(template_variable_argument))
163
- item.gsub!(/{#{template_variable}}/, replacement)
164
- rescue Exception => e
165
- puts "Error generating template: #{e.message}"
166
- return false
167
- else
168
- item
169
- end
170
- end
41
+ # @sqs = Aws::SQS::Client.new(@config[:aws])
42
+ @sqs_client = AwsCleaner::SQS.client(@config)
171
43
 
172
- # call an HTTP endpoint
173
- def fire_webhook(config)
174
- # generate templated URL
175
- if config[:template_variables] && config[:url] =~ /\{\S+\}/
176
- url = generate_template(
177
- config[:url],
178
- config[:template_variables][:method],
179
- config[:template_variables][:argument],
180
- config[:template_variables][:variable]
181
- )
182
- return false unless url
183
- else
184
- url = config[:url]
185
- end
186
-
187
- hook = { method: config[:method].to_sym, url: url }
188
- r = RestClient::Request.execute(hook)
189
- if r.code != 200
190
- return false
191
- else
192
- # notify chat when webhook is successful
193
- if config[:chat][:enable]
194
- msg = generate_template(
195
- config[:chat][:message],
196
- config[:chat][:method],
197
- config[:chat][:argument],
198
- config[:chat][:variable]
199
- )
200
- notify_chat(msg)
201
- end
202
- return true
203
- end
204
- end
44
+ @chef_client = AwsCleaner::Chef.client(@config)
205
45
 
206
46
  # main loop
207
47
  loop do
208
48
  # get messages from SQS
209
- messages = @sqs.receive_message(
49
+ messages = @sqs_client.receive_message(
210
50
  queue_url: @config[:sqs][:queue],
211
51
  max_number_of_messages: 10,
212
52
  visibility_timeout: 3
@@ -216,53 +56,52 @@ loop do
216
56
 
217
57
  messages.each_with_index do |message, index|
218
58
  puts "Looking at message number #{index}"
219
- body = parse(message.body)
59
+ body = AwsCleaner.new.parse(message.body)
220
60
  id = message.receipt_handle
221
61
 
222
62
  unless body
223
- delete_message(id)
63
+ AwsCleaner.new.delete_message(id, @config)
224
64
  next
225
65
  end
226
66
 
227
- @instance_id = process_message(body)
67
+ @instance_id = AwsCleaner.new.process_message(body)
228
68
 
229
69
  if @instance_id
230
70
  if @config[:webhooks]
231
- @config[:webhooks].each do |hook, config|
232
- if fire_webhook(config)
71
+ @config[:webhooks].each do |hook, hook_config|
72
+ if AwsCleaner::Webhooks.fire_webhook(hook_config, @config, @instance_id)
233
73
  puts "Successfully ran webhook #{hook}"
234
74
  else
235
75
  puts "Failed to run webhook #{hook}"
236
76
  end
237
77
  end
238
- delete_message(id)
78
+ AwsCleaner.new.delete_message(id, @config)
239
79
  end
240
80
 
241
- chef_node = get_chef_node_name(@instance_id)
81
+ chef_node = AwsCleaner::Chef.get_chef_node_name(@instance_id, @config)
242
82
 
243
83
  if chef_node
244
- if remove_from_chef(chef_node)
84
+ if AwsCleaner::Chef.remove_from_chef(chef_node, @chef_client, @config)
245
85
  puts "Removed #{chef_node} from Chef"
246
- delete_message(id)
86
+ AwsCleaner.new.delete_message(id, @config)
247
87
  end
248
88
  else
249
89
  puts "Instance #{@instance_id} does not exist in Chef, deleting message"
250
- delete_message(id)
90
+ AwsCleaner.new.delete_message(id, @config)
251
91
  end
252
92
 
253
- if in_sensu?(chef_node)
254
- if remove_from_sensu(chef_node)
93
+ if AwsCleaner::Sensu.in_sensu?(chef_node, @config)
94
+ if AwsCleaner::Sensu.remove_from_sensu(chef_node, @config)
255
95
  puts "Removed #{chef_node} from Sensu"
256
- delete_message(id)
257
96
  else
258
97
  puts "Instance #{@instance_id} does not exist in Sensu, deleting message"
259
- delete_message(id)
260
98
  end
99
+ AwsCleaner.new.delete_message(id, @config)
261
100
  end
262
101
 
263
102
  else
264
103
  puts 'Message not relevant, deleting'
265
- delete_message(id)
104
+ AwsCleaner.new.delete_message(id, @config)
266
105
  end
267
106
  end
268
107
 
@@ -0,0 +1,197 @@
1
+ # main aws_cleaner lib
2
+ class AwsCleaner
3
+ # SQS related stuff
4
+ module SQS
5
+ # sqs connection
6
+ def self.client(config)
7
+ Aws::SQS::Client.new(config[:aws])
8
+ end
9
+ end
10
+
11
+ # delete the message from SQS
12
+ def delete_message(id, config)
13
+ delete = AwsCleaner::SQS.client(config).delete_message(
14
+ queue_url: config[:sqs][:queue],
15
+ receipt_handle: id
16
+ )
17
+ delete ? true : false
18
+ end
19
+
20
+ module Chef
21
+ # chef connection
22
+ def self.client(config)
23
+ ChefAPI::Connection.new(
24
+ endpoint: config[:chef][:url],
25
+ client: config[:chef][:client],
26
+ key: config[:chef][:key]
27
+ )
28
+ end
29
+
30
+ # call the Chef API to get the node name of the instance
31
+ def self.get_chef_node_name(instance_id, config)
32
+ chef = client(config)
33
+ results = chef.search.query(:node, "ec2_instance_id:#{instance_id} OR chef_provisioning_reference_server_id:#{instance_id}")
34
+ return false if results.rows.empty?
35
+ results.rows.first['name']
36
+ end
37
+
38
+ # call the Chef API to get the FQDN of the instance
39
+ def self.get_chef_fqdn(instance_id, config)
40
+ chef = client(config)
41
+ results = chef.search.query(:node, "ec2_instance_id:#{instance_id} OR chef_provisioning_reference_server_id:#{instance_id}")
42
+ return false if results.rows.empty?
43
+ results.rows.first['automatic']['fqdn']
44
+ end
45
+
46
+ # call the Chef API to remove the node
47
+ def self.remove_from_chef(node_name, chef, config)
48
+ client = chef.clients.fetch(node_name)
49
+ client.destroy
50
+ node = chef.nodes.fetch(node_name)
51
+ node.destroy
52
+ rescue => e
53
+ puts "Failed to remove chef node: #{e}"
54
+ else
55
+ # puts "Removed #{node_name} from chef"
56
+ AwsCleaner::Notify.notify_chat('Removed ' + node_name + ' from Chef', config)
57
+ end
58
+ end
59
+
60
+ module Sensu
61
+ # check if the node exists in Sensu
62
+ def self.in_sensu?(node_name, config)
63
+ RestClient::Request.execute(
64
+ url: "#{config[:sensu][:url]}/clients/#{node_name}",
65
+ method: :get,
66
+ timeout: 5,
67
+ open_timeout: 5
68
+ )
69
+ rescue RestClient::ResourceNotFound
70
+ return false
71
+ rescue => e
72
+ puts "Sensu request failed: #{e}"
73
+ return false
74
+ else
75
+ return true
76
+ end
77
+
78
+ # call the Sensu API to remove the node
79
+ def self.remove_from_sensu(node_name, config)
80
+ response = RestClient::Request.execute(
81
+ url: "#{config[:sensu][:url]}/clients/#{node_name}",
82
+ method: :delete,
83
+ timeout: 5,
84
+ open_timeout: 5
85
+ )
86
+ case response.code
87
+ when 202
88
+ AwsCleaner::Notify.notify_chat('Removed ' + node_name + ' from Sensu', config)
89
+ return true
90
+ else
91
+ AwsCleaner::Notify.notify_chat('Failed to remove ' + node_name + ' from Sensu', config)
92
+ return false
93
+ end
94
+ end
95
+ end
96
+
97
+ # return the body of the SQS message in JSON
98
+ def parse(body)
99
+ JSON.parse(body)
100
+ rescue JSON::ParserError
101
+ return false
102
+ end
103
+
104
+ # return the instance_id of the terminated instance
105
+ def process_message(message_body)
106
+ return false if message_body['detail']['instance-id'].nil? &&
107
+ message_body['detail']['state'] != 'terminated'
108
+
109
+ instance_id = message_body['detail']['instance-id']
110
+ instance_id
111
+ end
112
+
113
+ module Notify
114
+ # notify hipchat
115
+ def self.notify_hipchat(msg, config)
116
+ hipchat = HipChat::Client.new(
117
+ config[:hipchat][:api_token],
118
+ api_version: 'v2'
119
+ )
120
+ room = config[:hipchat][:room]
121
+ hipchat[room].send('AWS Cleaner', msg)
122
+ end
123
+
124
+ # notify slack
125
+ def self.notify_slack(msg, config)
126
+ slack = Slack::Poster.new(config[:slack][:webhook_url])
127
+ slack.channel = config[:slack][:channel]
128
+ slack.username = config[:slack][:username] ||= 'aws-cleaner'
129
+ slack.icon_emoji = config[:slack][:icon_emoji] ||= nil
130
+ slack.send_message(msg)
131
+ end
132
+
133
+ # generic chat notification method
134
+ def self.notify_chat(msg, config)
135
+ if config[:hipchat][:enable]
136
+ notify_hipchat(msg, config)
137
+ elsif config[:slack][:enable]
138
+ notify_slack(msg, config)
139
+ end
140
+ end
141
+ end
142
+
143
+ module Webhooks
144
+ # generate the URL for the webhook
145
+ def self.generate_template(item, template_variable_method, template_variable, config, instance_id)
146
+ if template_variable_method == 'get_chef_fqdn'
147
+ replacement = AwsCleaner::Chef.get_chef_fqdn(instance_id, config)
148
+ elsif template_variable_method == 'get_chef_node_name'
149
+ replacement = AwsCleaner::Chef.get_chef_node_name(instance_id, config)
150
+ else
151
+ raise 'Unknown templating method'
152
+ end
153
+ item.gsub!(/{#{template_variable}}/, replacement)
154
+ rescue StandardError => e
155
+ puts "Error generating template: #{e.message}"
156
+ return false
157
+ else
158
+ item
159
+ end
160
+
161
+ # call an HTTP endpoint
162
+ def self.fire_webhook(hook_config, config, instance_id)
163
+ # generate templated URL
164
+ if hook_config[:template_variables] && hook_config[:url] =~ /\{\S+\}/
165
+ url = AwsCleaner::Webhooks.generate_template(
166
+ hook_config[:url],
167
+ hook_config[:template_variables][:method],
168
+ hook_config[:template_variables][:variable],
169
+ config,
170
+ instance_id
171
+ )
172
+ return false unless url
173
+ else
174
+ url = hook_config[:url]
175
+ end
176
+
177
+ hook = { method: hook_config[:method].to_sym, url: url }
178
+ r = RestClient::Request.execute(hook)
179
+ if r.code != 200
180
+ return false
181
+ else
182
+ # notify chat when webhook is successful
183
+ if hook_config[:chat][:enable]
184
+ msg = AwsCleaner::Webhooks.generate_template(
185
+ hook_config[:chat][:message],
186
+ hook_config[:chat][:method],
187
+ hook_config[:chat][:variable],
188
+ config,
189
+ instance_id
190
+ )
191
+ AwsCleaner::Notify.notify_chat(msg, config)
192
+ end
193
+ return true
194
+ end
195
+ end
196
+ end
197
+ end
metadata CHANGED
@@ -1,15 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-cleaner
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Heydrick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-30 00:00:00.000000000 Z
11
+ date: 2017-03-28 00:00:00.000000000 Z
12
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.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.46.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.46.0
13
69
  - !ruby/object:Gem::Dependency
14
70
  name: aws-sdk-core
15
71
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +114,14 @@ dependencies:
58
114
  requirements:
59
115
  - - "~>"
60
116
  - !ruby/object:Gem::Version
61
- version: '1.8'
117
+ version: '2'
62
118
  type: :runtime
63
119
  prerelease: false
64
120
  version_requirements: !ruby/object:Gem::Requirement
65
121
  requirements:
66
122
  - - "~>"
67
123
  - !ruby/object:Gem::Version
68
- version: '1.8'
124
+ version: '2'
69
125
  - !ruby/object:Gem::Dependency
70
126
  name: slack-poster
71
127
  requirement: !ruby/object:Gem::Requirement
@@ -102,6 +158,7 @@ extensions: []
102
158
  extra_rdoc_files: []
103
159
  files:
104
160
  - bin/aws_cleaner.rb
161
+ - lib/aws_cleaner/aws_cleaner.rb
105
162
  homepage: https://github.com/eheydrick/aws-cleaner
106
163
  licenses:
107
164
  - MIT
@@ -122,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
179
  version: '0'
123
180
  requirements: []
124
181
  rubyforge_project:
125
- rubygems_version: 2.5.1
182
+ rubygems_version: 2.6.6
126
183
  signing_key:
127
184
  specification_version: 4
128
185
  summary: AWS Cleaner cleans up after EC2 instances are terminated