qurd 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +367 -0
- data/Rakefile +9 -0
- data/bin/qurd +10 -0
- data/lib/hash.rb +6 -0
- data/lib/qurd.rb +49 -0
- data/lib/qurd/action.rb +92 -0
- data/lib/qurd/action/chef.rb +128 -0
- data/lib/qurd/action/dummy.rb +27 -0
- data/lib/qurd/action/route53.rb +168 -0
- data/lib/qurd/configuration.rb +182 -0
- data/lib/qurd/listener.rb +207 -0
- data/lib/qurd/message.rb +231 -0
- data/lib/qurd/mixins.rb +8 -0
- data/lib/qurd/mixins/aws_clients.rb +37 -0
- data/lib/qurd/mixins/configuration.rb +29 -0
- data/lib/qurd/mixins/configuration_helpers.rb +79 -0
- data/lib/qurd/processor.rb +97 -0
- data/lib/qurd/version.rb +5 -0
- data/lib/string.rb +12 -0
- data/qurd.gemspec +32 -0
- data/test/action_test.rb +115 -0
- data/test/chef_test.rb +206 -0
- data/test/configuration_test.rb +333 -0
- data/test/dummy_action_test.rb +51 -0
- data/test/inputs/foo.pem +27 -0
- data/test/inputs/knife.rb +9 -0
- data/test/inputs/qurd.yml +32 -0
- data/test/inputs/qurd_chef.yml +35 -0
- data/test/inputs/qurd_chef_route53.yml +43 -0
- data/test/inputs/qurd_route53.yml +39 -0
- data/test/inputs/qurd_route53_wrong.yml +37 -0
- data/test/inputs/validator.pem +27 -0
- data/test/listener_test.rb +135 -0
- data/test/message_test.rb +187 -0
- data/test/mixin_aws_clients_test.rb +28 -0
- data/test/mixin_configuration_test.rb +36 -0
- data/test/processor_test.rb +41 -0
- data/test/responses/aws/ec2-describe-instances-0.xml +2 -0
- data/test/responses/aws/ec2-describe-instances-1.xml +127 -0
- data/test/responses/aws/error-response.xml +1 -0
- data/test/responses/aws/route53-change-resource-record-sets.xml +2 -0
- data/test/responses/aws/route53-list-hosted-zones-by-name-0.xml +3 -0
- data/test/responses/aws/route53-list-hosted-zones-by-name-1.xml +4 -0
- data/test/responses/aws/route53-list-hosted-zones-by-name-n.xml +5 -0
- data/test/responses/aws/route53-list-resource-record-sets-0.xml +2 -0
- data/test/responses/aws/route53-list-resource-record-sets-1.xml +4 -0
- data/test/responses/aws/route53-list-resource-record-sets-n.xml +6 -0
- data/test/responses/aws/sqs-list-queues-0.xml +1 -0
- data/test/responses/aws/sqs-list-queues-n.xml +4 -0
- data/test/responses/aws/sqs-receive-message-1-launch.xml +6 -0
- data/test/responses/aws/sqs-receive-message-1-launch_error.xml +6 -0
- data/test/responses/aws/sqs-receive-message-1-other.xml +12 -0
- data/test/responses/aws/sqs-receive-message-1-terminate.xml +6 -0
- data/test/responses/aws/sqs-receive-message-1-terminate_error.xml +6 -0
- data/test/responses/aws/sqs-receive-message-1-test.xml +12 -0
- data/test/responses/aws/sqs-set-queue-attributes.xml +1 -0
- data/test/responses/aws/sts-assume-role.xml +17 -0
- data/test/responses/chef/search-client-name-0.json +6 -0
- data/test/responses/chef/search-client-name-1.json +7 -0
- data/test/responses/chef/search-client-name-n.json +8 -0
- data/test/responses/chef/search-node-instance-0.json +5 -0
- data/test/responses/chef/search-node-instance-1.json +784 -0
- data/test/responses/chef/search-node-instance-n.json +1565 -0
- data/test/responses/ec2/latest-meta-data-iam-security-credentials-client.txt +9 -0
- data/test/responses/ec2/latest-meta-data-iam-security-credentials.txt +1 -0
- data/test/route53_test.rb +231 -0
- data/test/support/web_mock_stubs.rb +109 -0
- data/test/test_helper.rb +10 -0
- metadata +307 -0
data/lib/qurd/mixins.rb
ADDED
@@ -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
|
data/lib/qurd/version.rb
ADDED
data/lib/string.rb
ADDED
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
|
data/test/action_test.rb
ADDED
@@ -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
|