aws-cleaner 2.0.1 → 2.1.0

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