qurd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,8 @@
1
+ module Qurd
2
+ # Mixins for Qurd
3
+ module Mixins
4
+ autoload :AwsClients, 'qurd/mixins/aws_clients'
5
+ autoload :Configuration, 'qurd/mixins/configuration'
6
+ autoload :ConfigurationHelpers, 'qurd/mixins/configuration_helpers'
7
+ end
8
+ end
@@ -0,0 +1,37 @@
1
+ module Qurd
2
+ module Mixins
3
+ # Generic method for instantiating Aws clients
4
+ module AwsClients
5
+ # Memoize Aws clients, the caller must respond to +region+ and
6
+ # +aws_credentials+
7
+ # @param [String|Symbol] client the name of the client to instantiate
8
+ # @return [Object] an Aws client
9
+ # @example SQS
10
+ # executor.aws_client(:SQS).list_queues
11
+ # @example EC2
12
+ # executor.aws_client("EC2").describe_instances
13
+ # @raise [NameError] if the +client+ is not a valid Aws client class
14
+ def aws_client(client)
15
+ @qurd_aws_clients ||= {}
16
+ klass = Object.const_get("Aws::#{client}::Client")
17
+ @qurd_aws_clients[client.to_sym] ||= klass.new(
18
+ region: region,
19
+ credentials: aws_credentials)
20
+ end
21
+
22
+ # Wrap a block in a +begin+ +rescue+, which retries, if
23
+ # +Aws::Errors::ServiceError+ is raised. The method will retry the block
24
+ # immediately, up to n +tries+.
25
+ # @param [Fixnum] tries
26
+ # @raise [Aws::Errors::ServiceError] Any number of Aws error classes
27
+ def aws_retryable(tries = 2)
28
+ tries = [1, tries.to_i].max
29
+ begin
30
+ yield
31
+ rescue Aws::Errors::ServiceError => e
32
+ (tries -= 1).zero? ? raise(e) : retry
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ module Qurd
2
+ module Mixins
3
+ # Mixin the {#Qurd::Configuration} singleton
4
+ module Configuration
5
+ # Get the Qurd::Configuration singleton
6
+ # @return [Qurd::Configuration]
7
+ def qurd_config
8
+ @qurd_config ||= Qurd::Configuration.instance
9
+ end
10
+
11
+ # Get the parsed configuration for the daemon
12
+ # @return [Hashie::Mash]
13
+ def qurd_configuration
14
+ qurd_config.config
15
+ end
16
+
17
+ # Get the logger
18
+ # @return [Logger]
19
+ def qurd_logger
20
+ qurd_config.logger
21
+ end
22
+
23
+ # Log an error and raise an exception
24
+ def qurd_logger!(*a)
25
+ qurd_config.logger!(*a)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,79 @@
1
+ module Qurd
2
+ module Mixins
3
+ # Helpers for managing configuration details
4
+ module ConfigurationHelpers
5
+ private
6
+
7
+ def get_or_default(obj, method, default, cast = nil)
8
+ val = obj.send(method).nil? ? default : obj.send(method)
9
+ cast.nil? ? val : val.send(cast)
10
+ end
11
+
12
+ def verify_account!(name, monitor)
13
+ missing_keys = []
14
+ %w(credentials region queues).each do |key|
15
+ missing_keys << key if monitor[key].nil? || monitor[key].empty?
16
+ end
17
+
18
+ keys = missing_keys.join(', ')
19
+ logger! "Account #{name} missing keys: #{keys}" unless keys.empty?
20
+ end
21
+
22
+ def mkdir_p_file!(path)
23
+ return if File.exist?(path) && File.writable?(path)
24
+ dirname = File.dirname(path)
25
+ FileUtils.mkdir_p dirname
26
+ logger! "Directory not writable: #{dirname}" \
27
+ unless File.writable?(dirname)
28
+ end
29
+
30
+ def default_credentials
31
+ [['default',
32
+ Aws::InstanceProfileCredentials.new(http_open_timeout: 1,
33
+ http_read_timeout: 1,
34
+ retries: 1
35
+ )]]
36
+ end
37
+
38
+ def assume_role_credentials(cred)
39
+ opts = {}
40
+ %w(role_arn role_session_name policy duration_seconds
41
+ external_id).each do |key|
42
+ opts[key.to_sym] = cred.options[key] if cred.options.key?(key)
43
+ end
44
+ [cred.name, Aws::AssumeRoleCredentials.new(opts)]
45
+ end
46
+
47
+ def credentials(cred)
48
+ opts = [cred.options.access_key_id,
49
+ cred.options.secret_access_key,
50
+ cred.options.session_token]
51
+ [cred.name, Aws::Credentials.new(*opts)]
52
+ end
53
+
54
+ def shared_credentials(cred)
55
+ opts = {
56
+ profile_name: cred.options.profile_name
57
+ }
58
+ opts[:path] = cred.options.path if cred.options.key?(:path)
59
+ [cred.name, Aws::SharedCredentials.new(opts)]
60
+ end
61
+
62
+ def instance_profile_credentials(cred)
63
+ opts = {}
64
+ %w(retries ip_address port http_open_timeout http_read_timeout
65
+ delay http_debug_output).each do |key|
66
+ opts[key.to_sym] = cred.options[key] if cred.options.key?(key)
67
+ end
68
+ [cred.name, Aws::InstanceProfileCredentials.new(opts)]
69
+ end
70
+
71
+ def string2class(klass)
72
+ require klass.underscore
73
+ obj = Object.const_get(klass)
74
+ logger.debug("Found action #{klass}")
75
+ obj
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,97 @@
1
+ # Gem module
2
+ module Qurd
3
+ # Use a {#Qurd::Listener} to act on an AWS SQS message
4
+ class Processor
5
+ include Qurd::Mixins::Configuration
6
+ include Qurd::Mixins::AwsClients
7
+
8
+ # @!attribute listener [r]
9
+ # @return [Qurd::Listener]
10
+ # @!attribute message [r]
11
+ # @return [Qurd::Message]
12
+ attr_reader :listener, :message
13
+
14
+ # @param [Qurd::Listener] listener
15
+ # @param [Struct] msg An AWS SQS message
16
+ def initialize(listener, msg, listener_name, queue_url)
17
+ @listener = listener
18
+ @message = Message.new(
19
+ message: msg,
20
+ name: listener_name,
21
+ queue_url: queue_url,
22
+ aws_credentials: @listener.aws_credentials,
23
+ region: @listener.region
24
+ )
25
+ end
26
+
27
+ # Process an SQS message, by instantiating an instance of each action,
28
+ # calling +run_before+, +run+, and +run_after+, and deleting the message.
29
+ def process
30
+ qurd_logger.info("Processing #{listener.name} " \
31
+ "action:#{message.action} " \
32
+ "event:#{message.message.Event}")
33
+
34
+ if message.action
35
+ instantiate_actions
36
+ run_before
37
+ run_action
38
+ run_after
39
+ end
40
+
41
+ message.delete
42
+ end
43
+
44
+ # @private
45
+ def inspect
46
+ format('<Qurd::Processor:%x listener:%s message:%s>',
47
+ object_id,
48
+ listener.inspect,
49
+ message.inspect
50
+ )
51
+ end
52
+
53
+ private
54
+
55
+ def instantiate_actions
56
+ @actions = qurd_configuration.actions[message.action].map do |klass|
57
+ qurd_logger.debug("Instantiating #{klass}")
58
+ klass.new(message)
59
+ end
60
+ end
61
+
62
+ def run_before
63
+ run_actions('run_before') do |action|
64
+ action.send(:run_before)
65
+ end
66
+ end
67
+
68
+ def run_action
69
+ run_actions('run') do |action|
70
+ action.send(message.action) if message.action
71
+ end
72
+ end
73
+
74
+ def run_after
75
+ run_actions('run_after') do |action|
76
+ action.send(:run_after)
77
+ end
78
+ end
79
+
80
+ def run_actions(desc, &block)
81
+ @actions.each do |action|
82
+ qurd_logger.time("#{desc} #{action}") do
83
+ run(action, &block)
84
+ end
85
+ end
86
+ end
87
+
88
+ def run(action, &_block)
89
+ qurd_logger.debug("Running #{action}")
90
+ yield action
91
+ rescue StandardError => e
92
+ qurd_logger.error "#{action} raised #{e}"
93
+ qurd_logger.error e.backtrace.join("\n")
94
+ message.failed!(e)
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,5 @@
1
+ # Gem module
2
+ module Qurd
3
+ # The daemon version
4
+ VERSION = '0.0.1'
5
+ end
data/lib/string.rb ADDED
@@ -0,0 +1,12 @@
1
+ # rubocop:disable Documentation
2
+ class String
3
+ def underscore
4
+ word = dup
5
+ word.gsub!(/::/, '/')
6
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
7
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
8
+ word.tr!('-', '_')
9
+ word.downcase!
10
+ word
11
+ end
12
+ end
data/qurd.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'qurd/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "qurd"
8
+ spec.version = Qurd::VERSION
9
+ spec.authors = ["Philip Champon"]
10
+ spec.email = ["philip@adaptly.com"]
11
+ spec.summary = %q{QUeue Resource Daemon: reaping and sowing your auto-scaled resources}
12
+ spec.description = %q{Configure resources, based on auto scaling events, published to SQS. Qurd is extensible, simply create a plugin and let it rip.}
13
+ spec.homepage = "https://github.com/Adaptly/qurd"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.8"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "minitest"
24
+ spec.add_development_dependency "pry-nav"
25
+ spec.add_development_dependency "webmock"
26
+ spec.add_development_dependency "minitest-matchers_vaccine"
27
+
28
+ spec.add_runtime_dependency "cabin", "~> 0.7.0"
29
+ spec.add_runtime_dependency "hashie"
30
+ spec.add_runtime_dependency "aws-sdk", "~> 2.0.30"
31
+ spec.add_runtime_dependency "chef", ">= 11.16.0"
32
+ end
@@ -0,0 +1,115 @@
1
+ require 'test_helper'
2
+ describe Qurd::Action do
3
+ include WebMockStubs
4
+ class TestActionClass < Qurd::Action; end
5
+
6
+ describe 'class' do
7
+ def setup
8
+ aws_sqs_list_queues
9
+ aws_sqs_set_queue_attributes
10
+ aws_sqs_receive_message 'test/responses/aws/sqs-receive-message-1-launch.xml'
11
+ Qurd::Configuration.instance.configure('test/inputs/qurd.yml')
12
+ end
13
+ let(:subject) { TestActionClass }
14
+
15
+ it 'includes configuration mixin' do
16
+ %w(qurd_config qurd_configuration qurd_logger qurd_logger!).each do |m|
17
+ subject.must_respond_to m
18
+ end
19
+ end
20
+
21
+ it 'includes aws_client mixin' do
22
+ subject.must_respond_to :aws_client
23
+ end
24
+
25
+ describe '#configure' do
26
+ it 'logs something' do
27
+ mock = Minitest::Mock.new
28
+ mock.expect :debug, nil, ['Nothing to do']
29
+ subject.stub :qurd_logger, mock do
30
+ subject.configure('launch')
31
+ end
32
+ mock.verify
33
+ end
34
+ end
35
+ end
36
+
37
+ describe 'instance' do
38
+ def setup
39
+ aws_sqs_list_queues
40
+ aws_sqs_set_queue_attributes
41
+ aws_sqs_receive_message 'test/responses/aws/sqs-receive-message-1-launch.xml'
42
+ Qurd::Configuration.instance.configure('test/inputs/qurd.yml')
43
+ end
44
+ let(:sqs_client) { Aws::SQS::Client.new(region: 'us-west-2') }
45
+ let(:queue_url) { 'https://sqs.us-west-2.amazonaws.com/123456890/test2-ScalingNotificationsQueue-HPPYDAYSAGAI1' }
46
+ let(:sqs_message) { sqs_client.receive_message(queue_url: queue_url).messages.first }
47
+ let(:qurd_message) { Qurd::Message.new(message: sqs_message) }
48
+ let(:subject) { TestActionClass.new(qurd_message) }
49
+
50
+ it 'includes configuration mixin' do
51
+ %w(qurd_config qurd_configuration qurd_logger qurd_logger!).each do |m|
52
+ subject.must_respond_to m
53
+ end
54
+ end
55
+
56
+ it 'includes aws_client mixin' do
57
+ subject.must_respond_to :aws_client
58
+ end
59
+
60
+ describe '#new' do
61
+ it 'raises Qurd::Action::InvalidMessage' do
62
+ lambda do
63
+ TestActionClass.new(Object.new)
64
+ end.must_raise Qurd::Action::InvalidMessage
65
+ end
66
+
67
+ it 'sets various ivars' do
68
+ subject.message.must_equal qurd_message
69
+ subject.context.must_equal qurd_message.context
70
+ subject.instance_variable_get(:@message).must_equal subject.message
71
+ subject.instance_variable_get(:@context).must_equal subject.context
72
+ end
73
+ end
74
+
75
+ it 'responds to region' do
76
+ subject.region.must_equal qurd_message.region
77
+ end
78
+
79
+ it 'responds to aws_credentials' do
80
+ subject.aws_credentials.must_equal qurd_message.aws_credentials
81
+ end
82
+
83
+ %w[launch launch_error terminate terminate_error test].each do |action|
84
+ describe "##{action}" do
85
+ it 'raises RuntimeError' do
86
+ lambda do
87
+ subject.send action
88
+ end.must_raise RuntimeError
89
+ end
90
+ end
91
+ end
92
+
93
+ describe '#run_after' do
94
+ it 'logs a message' do
95
+ mock = Minitest::Mock.new
96
+ mock.expect :debug, nil, ['Nothing to do']
97
+ subject.stub :qurd_logger, mock do
98
+ subject.run_after
99
+ end
100
+ mock.verify
101
+ end
102
+ end
103
+
104
+ describe '#run_before' do
105
+ it 'logs a message' do
106
+ mock = Minitest::Mock.new
107
+ mock.expect :debug, nil, ['Nothing to do']
108
+ subject.stub :qurd_logger, mock do
109
+ subject.run_before
110
+ end
111
+ mock.verify
112
+ end
113
+ end
114
+ end
115
+ end
data/test/chef_test.rb ADDED
@@ -0,0 +1,206 @@
1
+ require 'test_helper'
2
+ describe Qurd::Action::Chef do
3
+ include WebMockStubs
4
+ def setup
5
+ aws_sqs_list_queues
6
+ aws_sqs_set_queue_attributes
7
+ aws_ec2_describe_instances 'test/responses/aws/ec2-describe-instances-1.xml'
8
+ aws_sqs_receive_message 'test/responses/aws/sqs-receive-message-1-launch.xml'
9
+ end
10
+
11
+ let(:sqs_client) { Aws::SQS::Client.new(region: 'us-west-2') }
12
+ let(:queue_url) { 'https://sqs.us-west-2.amazonaws.com/123456890/test2-ScalingNotificationsQueue-HPPYDAYSAGAI1' }
13
+ let(:sqs_message) { sqs_client.receive_message(queue_url: queue_url).messages.first }
14
+ let(:qurd_message) { Qurd::Message.new(message: sqs_message, region: 'us-west-2', aws_credentials: Aws::Credentials.new('a', 'b')) }
15
+ let(:subject) { Qurd::Action::Chef.new(qurd_message) }
16
+
17
+ describe '#configure' do
18
+ it 'adds the Qurd::Message accessors chef_node, chef_client' do
19
+ Qurd::Configuration.instance.init('test/inputs/qurd_chef.yml')
20
+ Qurd::Action::Chef.configure('launch')
21
+ Qurd::Message.instance_methods.must_include :chef_node
22
+ Qurd::Message.instance_methods.must_include :chef_client
23
+ end
24
+
25
+ it 'sets the logger for chef' do
26
+ Qurd::Configuration.instance.init('test/inputs/qurd_chef.yml')
27
+ Qurd::Action::Chef.configure('launch')
28
+ ::Chef::Config[:log_location].path.must_equal 'tmp/qurd.log'
29
+ end
30
+
31
+ it 'sets the chef log level' do
32
+ expected = Qurd::Configuration.instance.config.log_level
33
+ Qurd::Configuration.instance.init('test/inputs/qurd_chef.yml')
34
+ Qurd::Action::Chef.configure('launch')
35
+ ::Chef::Config[:log_level].must_equal expected
36
+ end
37
+ end
38
+
39
+ describe '#chef_search' do
40
+ it 'memoizes Chef::Search::Query' do
41
+ subject.send(:chef_search).must_equal subject.send(:chef_search)
42
+ end
43
+ end
44
+
45
+ describe '#run_before' do
46
+ def setup
47
+ aws_sqs_list_queues
48
+ aws_sqs_set_queue_attributes
49
+ aws_ec2_describe_instances 'test/responses/aws/ec2-describe-instances-1.xml'
50
+ aws_sqs_receive_message 'test/responses/aws/sqs-receive-message-1-terminate.xml'
51
+ Qurd::Configuration.instance.configure('test/inputs/qurd_chef.yml')
52
+ end
53
+
54
+ it 'finds many nodes' do
55
+ chef_search(
56
+ 'test/responses/chef/search-node-instance-n.json',
57
+ 'node',
58
+ "instance_id:#{qurd_message.instance_id}"
59
+ )
60
+ chef_search(
61
+ 'test/responses/chef/search-client-name-n.json',
62
+ 'client',
63
+ 'name:test-414.staging.example.com'
64
+ )
65
+ subject.run_before
66
+ subject.message.chef_node.must_equal nil
67
+ subject.message.context[:chef_name].must_equal nil
68
+ end
69
+
70
+ it 'finds a node and client' do
71
+ chef_search(
72
+ 'test/responses/chef/search-node-instance-1.json',
73
+ 'node',
74
+ "instance_id:#{qurd_message.instance_id}"
75
+ )
76
+ chef_search(
77
+ 'test/responses/chef/search-client-name-1.json',
78
+ 'client',
79
+ 'name:test-414.staging.example.com'
80
+ )
81
+ subject.run_before
82
+ subject.message.chef_node.must_be_kind_of Chef::Node
83
+ subject.message.context[:chef_name].must_equal 'test-414.staging.example.com'
84
+ subject.message.chef_client.must_be_kind_of Chef::ApiClient
85
+ subject.message.context[:chef_client_name].must_equal 'test-414.staging.example.com'
86
+ end
87
+
88
+ it 'does not find a node' do
89
+ chef_search(
90
+ 'test/responses/chef/search-node-instance-0.json',
91
+ 'node',
92
+ "instance_id:#{qurd_message.instance_id}"
93
+ )
94
+ chef_search(
95
+ 'test/responses/chef/search-client-name-0.json',
96
+ 'client',
97
+ 'name:test-414.staging.example.com'
98
+ )
99
+ subject.run_before
100
+ subject.message.chef_node.must_equal nil
101
+ subject.message.context[:chef_name].must_equal nil
102
+ end
103
+
104
+ end
105
+
106
+ describe '#terminate' do
107
+ def setup
108
+ aws_sqs_list_queues
109
+ aws_sqs_set_queue_attributes
110
+ aws_ec2_describe_instances 'test/responses/aws/ec2-describe-instances-1.xml'
111
+ aws_sqs_receive_message 'test/responses/aws/sqs-receive-message-1-terminate.xml'
112
+ chef_node_delete
113
+ chef_client_delete
114
+ chef_search(
115
+ 'test/responses/chef/search-node-instance-1.json',
116
+ 'node',
117
+ "instance_id:#{qurd_message.instance_id}"
118
+ )
119
+ chef_search(
120
+ 'test/responses/chef/search-client-name-1.json',
121
+ 'client',
122
+ 'name:test-414.staging.example.com'
123
+ )
124
+ Qurd::Configuration.instance.configure('test/inputs/qurd_chef.yml')
125
+ end
126
+ let(:mock) { Minitest::Mock.new }
127
+ let(:node_mock) { Minitest::Mock.new }
128
+ let(:client_mock) { Minitest::Mock.new }
129
+
130
+ it 'saves a node; dry_run' do
131
+ mock.expect :debug, nil, ['Chef node found']
132
+ mock.expect :debug, nil, ['Dry run; missing node']
133
+
134
+ Qurd::Configuration.instance.config.dry_run = true
135
+ subject.run_before
136
+ subject.stub :qurd_logger, mock do
137
+ subject.terminate
138
+ end
139
+ mock.verify
140
+ end
141
+
142
+ it 'destroys a node; not dry_run; not failed' do
143
+ mock.expect :debug, nil, [String]
144
+ node_mock.expect :destroy, nil
145
+ node_mock.expect :nil?, false
146
+ client_mock.expect :destroy, nil
147
+ client_mock.expect :nil?, false
148
+
149
+ Qurd::Configuration.instance.config.dry_run = false
150
+ subject.run_before
151
+ qurd_message.stub :chef_client, client_mock do
152
+ qurd_message.stub :chef_node, node_mock do
153
+ subject.stub :qurd_logger, mock do
154
+ subject.terminate
155
+ end
156
+ end
157
+ end
158
+ mock.verify
159
+ node_mock.verify
160
+ client_mock.verify
161
+ end
162
+
163
+ it 'keeps a node; failed' do
164
+ mock.expect :warn, nil, ['Not deleting, message failed to process']
165
+ Qurd::Configuration.instance.config.dry_run = false
166
+ subject.run_before
167
+ qurd_message.stub :failed?, true do
168
+ subject.stub :qurd_logger, mock do
169
+ subject.terminate
170
+ end
171
+ end
172
+ mock.verify
173
+ end
174
+
175
+ end
176
+
177
+ describe '#test' do
178
+ def setup
179
+ aws_sqs_list_queues
180
+ aws_sqs_set_queue_attributes
181
+ aws_ec2_describe_instances 'test/responses/aws/ec2-describe-instances-1.xml'
182
+ aws_sqs_receive_message 'test/responses/aws/sqs-receive-message-1-test.xml'
183
+ chef_search(
184
+ 'test/responses/chef/search-node-instance-1.json',
185
+ 'node',
186
+ "instance_id:#{qurd_message.instance_id}"
187
+ )
188
+ chef_search(
189
+ 'test/responses/chef/search-client-name-1.json',
190
+ 'client',
191
+ 'name:test-414.staging.example.com'
192
+ )
193
+ Qurd::Configuration.instance.configure('test/inputs/qurd_chef.yml')
194
+ end
195
+ let(:mock) { Minitest::Mock.new }
196
+
197
+ it 'logs Test' do
198
+ mock.expect :info, nil, ['Test']
199
+ subject.run_before
200
+ subject.stub :qurd_logger, mock do
201
+ subject.test
202
+ end
203
+ mock.verify
204
+ end
205
+ end
206
+ end