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,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