qurd 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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +367 -0
  7. data/Rakefile +9 -0
  8. data/bin/qurd +10 -0
  9. data/lib/hash.rb +6 -0
  10. data/lib/qurd.rb +49 -0
  11. data/lib/qurd/action.rb +92 -0
  12. data/lib/qurd/action/chef.rb +128 -0
  13. data/lib/qurd/action/dummy.rb +27 -0
  14. data/lib/qurd/action/route53.rb +168 -0
  15. data/lib/qurd/configuration.rb +182 -0
  16. data/lib/qurd/listener.rb +207 -0
  17. data/lib/qurd/message.rb +231 -0
  18. data/lib/qurd/mixins.rb +8 -0
  19. data/lib/qurd/mixins/aws_clients.rb +37 -0
  20. data/lib/qurd/mixins/configuration.rb +29 -0
  21. data/lib/qurd/mixins/configuration_helpers.rb +79 -0
  22. data/lib/qurd/processor.rb +97 -0
  23. data/lib/qurd/version.rb +5 -0
  24. data/lib/string.rb +12 -0
  25. data/qurd.gemspec +32 -0
  26. data/test/action_test.rb +115 -0
  27. data/test/chef_test.rb +206 -0
  28. data/test/configuration_test.rb +333 -0
  29. data/test/dummy_action_test.rb +51 -0
  30. data/test/inputs/foo.pem +27 -0
  31. data/test/inputs/knife.rb +9 -0
  32. data/test/inputs/qurd.yml +32 -0
  33. data/test/inputs/qurd_chef.yml +35 -0
  34. data/test/inputs/qurd_chef_route53.yml +43 -0
  35. data/test/inputs/qurd_route53.yml +39 -0
  36. data/test/inputs/qurd_route53_wrong.yml +37 -0
  37. data/test/inputs/validator.pem +27 -0
  38. data/test/listener_test.rb +135 -0
  39. data/test/message_test.rb +187 -0
  40. data/test/mixin_aws_clients_test.rb +28 -0
  41. data/test/mixin_configuration_test.rb +36 -0
  42. data/test/processor_test.rb +41 -0
  43. data/test/responses/aws/ec2-describe-instances-0.xml +2 -0
  44. data/test/responses/aws/ec2-describe-instances-1.xml +127 -0
  45. data/test/responses/aws/error-response.xml +1 -0
  46. data/test/responses/aws/route53-change-resource-record-sets.xml +2 -0
  47. data/test/responses/aws/route53-list-hosted-zones-by-name-0.xml +3 -0
  48. data/test/responses/aws/route53-list-hosted-zones-by-name-1.xml +4 -0
  49. data/test/responses/aws/route53-list-hosted-zones-by-name-n.xml +5 -0
  50. data/test/responses/aws/route53-list-resource-record-sets-0.xml +2 -0
  51. data/test/responses/aws/route53-list-resource-record-sets-1.xml +4 -0
  52. data/test/responses/aws/route53-list-resource-record-sets-n.xml +6 -0
  53. data/test/responses/aws/sqs-list-queues-0.xml +1 -0
  54. data/test/responses/aws/sqs-list-queues-n.xml +4 -0
  55. data/test/responses/aws/sqs-receive-message-1-launch.xml +6 -0
  56. data/test/responses/aws/sqs-receive-message-1-launch_error.xml +6 -0
  57. data/test/responses/aws/sqs-receive-message-1-other.xml +12 -0
  58. data/test/responses/aws/sqs-receive-message-1-terminate.xml +6 -0
  59. data/test/responses/aws/sqs-receive-message-1-terminate_error.xml +6 -0
  60. data/test/responses/aws/sqs-receive-message-1-test.xml +12 -0
  61. data/test/responses/aws/sqs-set-queue-attributes.xml +1 -0
  62. data/test/responses/aws/sts-assume-role.xml +17 -0
  63. data/test/responses/chef/search-client-name-0.json +6 -0
  64. data/test/responses/chef/search-client-name-1.json +7 -0
  65. data/test/responses/chef/search-client-name-n.json +8 -0
  66. data/test/responses/chef/search-node-instance-0.json +5 -0
  67. data/test/responses/chef/search-node-instance-1.json +784 -0
  68. data/test/responses/chef/search-node-instance-n.json +1565 -0
  69. data/test/responses/ec2/latest-meta-data-iam-security-credentials-client.txt +9 -0
  70. data/test/responses/ec2/latest-meta-data-iam-security-credentials.txt +1 -0
  71. data/test/route53_test.rb +231 -0
  72. data/test/support/web_mock_stubs.rb +109 -0
  73. data/test/test_helper.rb +10 -0
  74. metadata +307 -0
@@ -0,0 +1,333 @@
1
+ require 'test_helper'
2
+ describe Qurd::Configuration do
3
+ include WebMockStubs
4
+
5
+ subject { Qurd::Configuration.instance }
6
+ let(:mock) { Minitest::Mock.new }
7
+ let(:config) do
8
+ {
9
+ # dry_run:"true",
10
+ # daemonize:false,
11
+ pid_file: 'tmp/qurd.pid',
12
+ # wait_time:"0",
13
+ # visibility_timeout:"1",
14
+ log_file: 'tmp/qurd.log',
15
+ log_level: 'debug',
16
+ aws_credentials: [
17
+ {
18
+ name: 'credentials',
19
+ type: 'credentials',
20
+ options: {
21
+ access_key_id: 'access',
22
+ secret_access_key: 'secret'
23
+ }
24
+ }
25
+ ],
26
+ auto_scaling_queues: {
27
+ staging: {
28
+ credentials: 'credentials',
29
+ region: 'us-west-2',
30
+ queues: ['/ScalingNotificationsQueue/']
31
+ }
32
+ },
33
+ actions: {
34
+ launch: ['Qurd::Action::Dummy'],
35
+ launch_error: ['Qurd::Action::Dummy'],
36
+ terminate: ['Qurd::Action::Dummy'],
37
+ terminate_error: ['Qurd::Action::Dummy'],
38
+ test: ['Qurd::Action::Dummy']
39
+ }
40
+ }
41
+ end
42
+
43
+ def hashie_config(merge = {})
44
+ Hashie::Mash.new(config.merge!(merge))
45
+ end
46
+
47
+ def stub_yaml(config, &_block)
48
+ YAML.stub :load, config do
49
+ yield if block_given?
50
+ end
51
+ end
52
+
53
+ describe '#init' do
54
+ it 'sets default config values' do
55
+ config.merge!(log_file: nil, pid_file: nil)
56
+ stub_yaml config do
57
+ subject.init('/dev/null')
58
+ subject.config.daemonize.must_equal false
59
+ subject.config.dry_run.must_equal false
60
+ subject.config.listen_timeout.must_equal 300.0
61
+ subject.config.pid_file.must_equal '/var/run/qurd/qurd.pid'
62
+ subject.config.save_failures.must_equal true
63
+ subject.config.sqs_set_attributes_timeout.must_equal 10.0
64
+ subject.config.visibility_timeout.must_equal '300'
65
+ subject.config.wait_time.must_equal '20'
66
+ end
67
+ end
68
+
69
+ it 'keeps config values' do
70
+ config.merge!(
71
+ daemonize: true,
72
+ dry_run: true,
73
+ listen_timeout: 2,
74
+ pid_file: 'tmp/qurd.pid',
75
+ save_failures: false,
76
+ sqs_set_attributes_timeout: 3,
77
+ visibility_timeout: 0,
78
+ wait_time: 1
79
+ )
80
+ stub_yaml config do
81
+ subject.init('/dev/null')
82
+ subject.config.daemonize.must_equal true
83
+ subject.config.dry_run.must_equal true
84
+ subject.config.listen_timeout.must_equal 2.0
85
+ subject.config.pid_file.must_equal 'tmp/qurd.pid'
86
+ subject.config.save_failures.must_equal false
87
+ subject.config.sqs_set_attributes_timeout.must_equal 3.0
88
+ subject.config.visibility_timeout.must_equal '0'
89
+ subject.config.wait_time.must_equal '1'
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#debug?' do
95
+ it 'is true' do
96
+ hashie = hashie_config(log_level: 'debug')
97
+ subject.stub :config, hashie do
98
+ subject.debug?.must_equal true
99
+ end
100
+ end
101
+
102
+ it 'is false' do
103
+ hashie = hashie_config(log_level: 'info')
104
+ subject.stub :config, hashie do
105
+ subject.debug?.must_equal false
106
+ end
107
+ end
108
+ end
109
+
110
+ describe '#logger!' do
111
+ it 'logs and raises RuntimeError' do
112
+ mock.expect :error, nil, ['foo']
113
+ lambda do
114
+ subject.stub :logger, mock do
115
+ subject.logger!('foo')
116
+ end
117
+ end.must_raise RuntimeError, 'foo'
118
+ mock.verify
119
+ end
120
+
121
+ it 'logs and raises StandardError' do
122
+ mock.expect :error, nil, ['foo']
123
+ lambda do
124
+ subject.stub :logger, mock do
125
+ subject.logger!('foo', StandardError)
126
+ end
127
+ end.must_raise StandardError, 'foo'
128
+ mock.verify
129
+ end
130
+ end
131
+
132
+ describe '#configure' do
133
+ it 'configures logger, accounts, queues, and actions' do
134
+ aws_sqs_list_queues
135
+ aws_sqs_set_queue_attributes
136
+ stub_yaml config do
137
+ subject.configure('/dev/null')
138
+ end
139
+ end
140
+ end
141
+
142
+ describe '#configure_actions' do
143
+ it 'raises when objects are not found' do
144
+ config.merge!(actions: { launch: ['DoesNotExist'] })
145
+ stub_yaml config do
146
+ subject.init('/dev/null')
147
+ lambda do
148
+ subject.send :configure_actions
149
+ end.must_raise RuntimeError, 'Class undefined for actions: DoesNotExist'
150
+ end
151
+ end
152
+
153
+ it 'raises when action values are not arrays' do
154
+ config.merge!(actions: { launch: 'Qurd::Action::Dummy' })
155
+ stub_yaml config do
156
+ subject.init('/dev/null')
157
+ lambda do
158
+ subject.send :configure_actions
159
+ end.must_raise RuntimeError, 'Action types must be an array'
160
+ end
161
+ end
162
+ end
163
+
164
+ describe '#configure_listeners' do
165
+ it 'creates accounts data structure' do
166
+ aws_sqs_list_queues
167
+ aws_sqs_set_queue_attributes
168
+ stub_yaml config do
169
+ subject.init '/dev/null'
170
+ subject.send :configure_credentials
171
+ subject.send :configure_auto_scaling_queues
172
+ subject.config.listeners.count.must_equal 1
173
+ end
174
+ end
175
+ end
176
+
177
+ describe '#get_or_default' do
178
+ class Dummy
179
+ def nil
180
+ nil
181
+ end
182
+
183
+ def true
184
+ true
185
+ end
186
+ end
187
+ let(:obj) { Dummy.new }
188
+
189
+ it 'chooses a defined value' do
190
+ val = subject.send :get_or_default, obj, :true, 0
191
+ val.must_equal true
192
+ end
193
+
194
+ it 'chooses a default value' do
195
+ val = subject.send :get_or_default, obj, :nil, 0
196
+ val.must_equal 0
197
+ end
198
+
199
+ it 'casts a value' do
200
+ val = subject.send :get_or_default, obj, :nil, 0, :to_s
201
+ val.must_equal '0'
202
+ end
203
+ end
204
+
205
+ describe '#verify_account!' do
206
+ %w(credentials region queues).each do |key|
207
+ let(:monitor) do
208
+ {
209
+ 'credentials' => 'foo',
210
+ 'region' => 'bar',
211
+ 'queues' => 'baz'
212
+ }
213
+ end
214
+
215
+ it "raises if #{key} is nil" do
216
+ monitor.delete(key)
217
+ lambda do
218
+ subject.send :verify_account!, :bam, monitor
219
+ end.must_raise RuntimeError, "Account bam missing keys: #{key}"
220
+ end
221
+
222
+ it "raises if #{key} is empty" do
223
+ monitor[key] = ''
224
+ lambda do
225
+ subject.send :verify_account!, :bam, monitor
226
+ end.must_raise RuntimeError, "Account bam missing keys: #{key}"
227
+ end
228
+
229
+ it 'is ok' do
230
+ ret = subject.send :verify_account!, :bam, monitor
231
+ ret.must_equal nil
232
+ end
233
+ end
234
+ end
235
+
236
+ describe '#mkdir_p_file!' do
237
+ it 'makes directories' do
238
+ subject.send :mkdir_p_file!, '/tmp/qurd-test/foo/bar/bam/baz.txt'
239
+ File.new('/tmp/qurd-test/foo/bar/bam')
240
+ `rm -rf /tmp/qurd-test`
241
+ end
242
+
243
+ it 'raises RuntimeError' do
244
+ skip "can't be root" if Process.uid == 0
245
+ lambda do
246
+ subject.send :mkdir_p_file!, '/etc/foo'
247
+ end.must_raise RuntimeError, 'Directory not writable: /etc'
248
+ end
249
+ end
250
+
251
+ describe '#default_credentials' do
252
+ it 'creates a default' do
253
+ ec2metadata
254
+ ret = subject.send :default_credentials
255
+ ret[0][0].must_equal 'default'
256
+ ret[0][1].must_be_kind_of Aws::InstanceProfileCredentials
257
+ ret.count.must_equal 1
258
+ end
259
+ end
260
+
261
+ describe '#assume_role_credentials' do
262
+ let(:cred) do
263
+ Hashie::Mash.new(name: 'foo',
264
+ options: {
265
+ role_arn: 'arn:aws:iam::1:user/bob@example.com',
266
+ role_session_name: 'foo'
267
+ })
268
+ end
269
+ %w(policy duration_seconds external_id).each do |key|
270
+ it "sets option #{key}" do
271
+ Aws.config[:region] = 'us-west-2'
272
+ aws_sts_assume_role
273
+ cred.options[key] = '1'
274
+ ret = subject.send :assume_role_credentials, cred
275
+ ret[0].must_equal cred.name
276
+ ret[1].must_be_kind_of Aws::AssumeRoleCredentials
277
+ Aws.config[:region] = nil
278
+ end
279
+ end
280
+ end
281
+
282
+ describe '#credentials' do
283
+ let(:cred) do
284
+ Hashie::Mash.new(name: 'foo',
285
+ options: {
286
+ access_key_id: 'abc',
287
+ secret_access_key: 'def'
288
+ })
289
+ end
290
+ it 'sets access_key_id and secret_access_key' do
291
+ ret = subject.send :credentials, cred
292
+ ret[0].must_equal cred.name
293
+ ret[1].access_key_id.must_equal cred.options.access_key_id
294
+ ret[1].secret_access_key.must_equal cred.options.secret_access_key
295
+ end
296
+
297
+ it 'sets access_key_id, secret_access_key, and session_token' do
298
+ cred.options.session_token = 'foo'
299
+ ret = subject.send :credentials, cred
300
+ ret[0].must_equal cred.name
301
+ ret[1].access_key_id.must_equal cred.options.access_key_id
302
+ ret[1].secret_access_key.must_equal cred.options.secret_access_key
303
+ ret[1].session_token.must_equal cred.options.session_token
304
+ end
305
+ end
306
+
307
+ describe '#instance_profile_credentials' do
308
+ let(:cred) do
309
+ Hashie::Mash.new(name: 'foo',
310
+ options: {
311
+ retries: 0,
312
+ http_open_timeout: 0,
313
+ http_read_timeout: 0
314
+ })
315
+ end
316
+ %w(port delay http_debug_output).each do |key|
317
+ it "sets option #{key}" do
318
+ ec2metadata
319
+ cred.options[key] = '80'
320
+ ret = subject.send :instance_profile_credentials, cred
321
+ ret[0].must_equal cred.name
322
+ ret[1].must_be_kind_of Aws::InstanceProfileCredentials
323
+ end
324
+ end
325
+ end
326
+
327
+ describe '#string2class' do
328
+ it 'returns a class' do
329
+ ret = subject.send :string2class, 'Qurd::Action::Dummy'
330
+ ret.must_equal Qurd::Action::Dummy
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,51 @@
1
+ require 'test_helper'
2
+ describe Qurd::Action::Dummy do
3
+ include WebMockStubs
4
+
5
+ describe 'class' do
6
+ def setup
7
+ aws_sqs_list_queues
8
+ aws_sqs_set_queue_attributes
9
+ Qurd::Configuration.instance.configure('test/inputs/qurd.yml')
10
+ end
11
+ let(:subject) { Qurd::Action::Dummy }
12
+
13
+ describe '#configure' do
14
+ %w(launch launch_error terminate terminate_error test).each do |action|
15
+ it "logs '#{action}'" do
16
+ aws_sqs_receive_message "test/responses/aws/sqs-receive-message-1-#{action}.xml"
17
+ mock = Minitest::Mock.new
18
+ mock.expect :debug, nil, [action]
19
+ subject.stub :qurd_logger, mock do
20
+ subject.configure(action)
21
+ end
22
+ mock.verify
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ describe 'instance' do
29
+ def setup
30
+ aws_sqs_list_queues
31
+ aws_sqs_set_queue_attributes
32
+ aws_ec2_describe_instances 'test/responses/aws/ec2-describe-instances-1.xml'
33
+ Qurd::Configuration.instance.configure('test/inputs/qurd.yml')
34
+ end
35
+ let(:sqs_client) { Aws::SQS::Client.new(region: 'us-west-2') }
36
+ let(:queue_url) { 'https://sqs.us-west-2.amazonaws.com/123456890/test2-ScalingNotificationsQueue-HPPYDAYSAGAI1' }
37
+ let(:sqs_message) { sqs_client.receive_message(queue_url: queue_url).messages.first }
38
+ let(:qurd_message) { Qurd::Message.new(message: sqs_message, region: 'us-west-2', aws_credentials: Aws::Credentials.new('a', 'b')) }
39
+ let(:subject) { Qurd::Action::Dummy.new(qurd_message) }
40
+
41
+ %w[launch launch_error terminate terminate_error test].each do |action|
42
+ describe "##{action}" do
43
+ it 'sets dummy context' do
44
+ aws_sqs_receive_message "test/responses/aws/sqs-receive-message-1-#{action}.xml"
45
+ subject.send action
46
+ subject.context[:dummy].must_equal true
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEowIBAAKCAQEA2vEv2wptCoW4z0mrsGQ/t9gHGai0u7O2NiNXHURHNDnr722o
3
+ QaFkedwnOfPRAW3YzV0t5mbjudN1XiT7L9EAV1Yek9fxSPTkPttRkTyr9ya4CAer
4
+ W4J6uIpz2VPOo57DX8bo/sv2STSiMlaTYgKizZlPWLpNKYbDt34+lYJerffhC7in
5
+ eHPLJifhH6sYQ0XCG5fEo+QA8VecLS3OdNJ/dGeWWrOgP9MdklaquSsVrGzZoTDR
6
+ OzuX81L4ml2bdJJY55tfpbMyACkNxcmwIAcvXLsHa5+yL/wcVHQVvNTl/mgOmUws
7
+ hYa7lcjsjZUSy2ky4syKvsiDgdJFU5L++n8suQIDAQABAoIBAQDLwHUmxZcgmb2Z
8
+ uxe6ejEKKFLDQEuh+ubxjwX9SJNzOQwmr8hL+6SD+6vNtOenCtEenT6gEtSPFQk+
9
+ 6e+RWlGU9aJTOD1mBfF7xaMtJGdtBG1zm5+O8+i9YKvbbrD5eECeG4CtzqaPDhp7
10
+ +jICni1gZPKAwjAZGz98vlID1WXDQxlTd5z7S+HcHx4bWDKFVb1b7fjhPMljTkvJ
11
+ AUBGiyhfrPohsieaxvbn/whzQyIdKLnV9ve7IDZgAvPW1BUn9qI+ya/4ZP7XV5/T
12
+ OHIzxJ+Q60CA4RPyy2gkdFKJLzsJXjSEkCmky5iySBuUlvXqpc6VoOyqIKDJHMlp
13
+ Qb6SH+thAoGBAO7hnga6beEcbAC2JsbS2Ill8RM23V/aKB8ZdDpRpdsA39dDc7JU
14
+ nEHS7oEwmjhwEBt/T0enlcYuzU50C9/YVgXCAIibTJ9NCzJAt3Wf1CsDcxTANEwO
15
+ /vMjMF5k9QYX+hSEE6gtwjiPGREtD7hJTMfR5dJ6cxDMdBvofCWMGK4NAoGBAOqh
16
+ xs/osLolQ8mrLguN5DCimQO3GEOfTOhoYd9jsOHSgxAdYpgyBa7QiFZz8QmxisJI
17
+ 7iocSDPVwm90O15+qV09aa9mYxWBnmtT8Y7tM9nSkOJxHohSTYMeSk9zeCFpKCqC
18
+ ZqMhilALfml7Sir+xnVgkfTBbY3d5llG6T/ZPTpdAoGAdugbbyHxe/JX2GrTFnjx
19
+ jEMQIw5WV1YSTpivADCQLFldEpvixUvUk/dY/TmiKqGtguJ2JibHKOU3EOw21O+s
20
+ siUFevpCw7Dn99W6/HPYeqi26gdagNmeEozbkMEULjpVI00zM63DVb/1LFWP0524
21
+ +GYEziZhjGUZDqNE2LPJvSkCgYBbLdsQ2KA09l5NdJREMTPIIqfL5c8sGC/O8SF3
22
+ qkoGt8Pu8zxpYKcMtEUHM68r0S7XKXhELRFx81PH9CdOJXKzpTX4z5UZunFRzCFH
23
+ Ja7MqyNanBL9bt5oYdEG5xx7/114h4xTQKl7SbwqSZsc8j01R/3wW2qzgtQNRZ1H
24
+ DqjYkQKBgEruMhyLVO0b/V5iVGKIxYnoA3oCcfYEa5oKHGD5nEHIM67J947MnxU/
25
+ CIpVmMGZ6w9ah5kMhIVroZvEjlk6YMuWvaoUjwB/EN505ylq0Wacb8kscbjqdaLu
26
+ dZO8vHOGxI92WtXrINU/97nnRAeMdLm4RqW86yWoKSlm72RWmcol
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,9 @@
1
+ current_dir = File.dirname(__FILE__)
2
+ log_level :info
3
+ log_location STDOUT
4
+ node_name "foo"
5
+ client_name "foo"
6
+ client_key "#{current_dir}/foo.pem"
7
+ validation_client_name "foo-validator"
8
+ validation_key "#{current_dir}/validator.pem"
9
+ chef_server_url "https://api.opscode.com/organizations/foo"
@@ -0,0 +1,32 @@
1
+ ---
2
+ daemonize: false
3
+ pid_file: tmp/qurd.pid
4
+ wait_time: 20
5
+ visibility_timeout: 600
6
+ log_file: tmp/qurd.log
7
+ log_level: debug
8
+ aws_credentials:
9
+ - name: test
10
+ type: credentials
11
+ options:
12
+ access_key_id: bar
13
+ secret_access_key: foo
14
+ auto_scaling_queues:
15
+ staging:
16
+ credentials: test
17
+ region: us-west-2
18
+ queues:
19
+ - "/ScalingNotificationsQueue/"
20
+
21
+ actions:
22
+ launch:
23
+ - "Qurd::Action::Dummy"
24
+ launch_error:
25
+ - "Qurd::Action::Dummy"
26
+ terminate:
27
+ - "Qurd::Action::Dummy"
28
+ terminate_error:
29
+ - "Qurd::Action::Dummy"
30
+ test:
31
+ - "Qurd::Action::Dummy"
32
+