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