MovableInkAWS 0.1.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/.travis.yml +8 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +42 -0
- data/MovableInkAWS.gemspec +20 -0
- data/README.md +27 -0
- data/lib/movable_ink/aws/autoscaling.rb +68 -0
- data/lib/movable_ink/aws/ec2.rb +128 -0
- data/lib/movable_ink/aws/errors.rb +8 -0
- data/lib/movable_ink/aws/route53.rb +58 -0
- data/lib/movable_ink/aws/sns.rb +41 -0
- data/lib/movable_ink/aws/ssm.rb +41 -0
- data/lib/movable_ink/aws.rb +73 -0
- data/lib/movable_ink/version.rb +5 -0
- data/spec/autoscaling_spec.rb +56 -0
- data/spec/aws_spec.rb +54 -0
- data/spec/ec2_spec.rb +223 -0
- data/spec/route53_spec.rb +115 -0
- data/spec/sns_spec.rb +31 -0
- data/spec/ssm_spec.rb +66 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 669c0c6bef77e8bfb4b4eda8971acb83794abd1b
|
4
|
+
data.tar.gz: b9b7805f3aeb75e9e6162522b5f94d5e2e57dcb8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eb0abc4ce9cbff973ca3bf729f29fba02ed2ea91507bef521e2c7df147184da8aec5ffdb61462ce98133d01708adef6fcaed5898c700b1e0c9c68bd892cad6f6
|
7
|
+
data.tar.gz: 9b24941a0844d01f41d30b926b6c79e74c44a7bb367d572ee36146d9da20366f7ff49ae6ef81b1f94ceeacaec27c980ce0bbf0ae2b15a8adb343224e7167090a
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
MovableInkAWS (0.1.1)
|
5
|
+
aws-sdk (~> 2.10, >= 2.10.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
aws-sdk (2.10.125)
|
11
|
+
aws-sdk-resources (= 2.10.125)
|
12
|
+
aws-sdk-core (2.10.125)
|
13
|
+
aws-sigv4 (~> 1.0)
|
14
|
+
jmespath (~> 1.0)
|
15
|
+
aws-sdk-resources (2.10.125)
|
16
|
+
aws-sdk-core (= 2.10.125)
|
17
|
+
aws-sigv4 (1.0.2)
|
18
|
+
diff-lcs (1.3)
|
19
|
+
jmespath (1.3.1)
|
20
|
+
rspec (3.6.0)
|
21
|
+
rspec-core (~> 3.6.0)
|
22
|
+
rspec-expectations (~> 3.6.0)
|
23
|
+
rspec-mocks (~> 3.6.0)
|
24
|
+
rspec-core (3.6.0)
|
25
|
+
rspec-support (~> 3.6.0)
|
26
|
+
rspec-expectations (3.6.0)
|
27
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
28
|
+
rspec-support (~> 3.6.0)
|
29
|
+
rspec-mocks (3.6.0)
|
30
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
31
|
+
rspec-support (~> 3.6.0)
|
32
|
+
rspec-support (3.6.0)
|
33
|
+
|
34
|
+
PLATFORMS
|
35
|
+
ruby
|
36
|
+
|
37
|
+
DEPENDENCIES
|
38
|
+
MovableInkAWS!
|
39
|
+
rspec (~> 3.6)
|
40
|
+
|
41
|
+
BUNDLED WITH
|
42
|
+
1.16.0
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "movable_ink/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'MovableInkAWS'
|
6
|
+
s.version = MovableInk::AWS::VERSION
|
7
|
+
s.summary = 'AWS Utility methods for MovableInk'
|
8
|
+
s.description = 'AWS Utility methods for MovableInk'
|
9
|
+
s.authors = ['Matt Chesler']
|
10
|
+
s.email = 'mchesler@movableink.com'
|
11
|
+
|
12
|
+
s.add_runtime_dependency 'aws-sdk', '~> 2.10', '>= 2.10.0'
|
13
|
+
|
14
|
+
all_files = `git ls-files`.split("\n")
|
15
|
+
test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
|
17
|
+
s.files = all_files - test_files
|
18
|
+
s.test_files = test_files
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# MovableInk::AWS gem
|
2
|
+
|
3
|
+
## Building
|
4
|
+
|
5
|
+
`gem build MovableInkAWS.gemspec`
|
6
|
+
|
7
|
+
## "Publishing"
|
8
|
+
|
9
|
+
`s3cmd put -P MovableInkAWS-<VERSION>.gem s3://movableink-chef/MovableInkAWS/`
|
10
|
+
|
11
|
+
## Installing
|
12
|
+
|
13
|
+
`gem install ./MovableInkAWS-<VERSION>.gem`
|
14
|
+
|
15
|
+
## Using
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'movable_ink/aws'
|
19
|
+
|
20
|
+
miaws = MovableInk::AWS.new
|
21
|
+
|
22
|
+
miaws.datacenter
|
23
|
+
|
24
|
+
miaws.instance_ip_addresses_by_role(role: 'varnish').map { |m| "http://#{m}:8080" }
|
25
|
+
|
26
|
+
...
|
27
|
+
```
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module MovableInk
|
2
|
+
class AWS
|
3
|
+
module Autoscaling
|
4
|
+
def autoscaling(region: my_region)
|
5
|
+
@autoscaling_client ||= {}
|
6
|
+
@autoscaling_client[region] ||= Aws::AutoScaling::Client.new(region: region)
|
7
|
+
end
|
8
|
+
|
9
|
+
def mark_me_as_unhealthy
|
10
|
+
run_with_backoff do
|
11
|
+
autoscaling.set_instance_health({
|
12
|
+
health_status: "Unhealthy",
|
13
|
+
instance_id: instance_id,
|
14
|
+
should_respect_grace_period: false
|
15
|
+
})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def mark_me_as_healthy(role:)
|
20
|
+
run_with_backoff do
|
21
|
+
ec2.create_tags({
|
22
|
+
resources: [instance_id],
|
23
|
+
tags: [
|
24
|
+
{
|
25
|
+
key: "mi:roles",
|
26
|
+
value: role
|
27
|
+
}
|
28
|
+
]
|
29
|
+
})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete_role_tag(role:)
|
34
|
+
run_with_backoff do
|
35
|
+
ec2.delete_tags({
|
36
|
+
resources: [instance_id],
|
37
|
+
tags: [
|
38
|
+
{
|
39
|
+
key: "mi:roles",
|
40
|
+
value: role
|
41
|
+
}
|
42
|
+
]
|
43
|
+
})
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def complete_lifecycle_action(hook_name:, group_name:, token:)
|
48
|
+
run_with_backoff do
|
49
|
+
autoscaling.complete_lifecycle_action({
|
50
|
+
lifecycle_hook_name: hook_name,
|
51
|
+
auto_scaling_group_name: group_name,
|
52
|
+
lifecycle_action_token: token,
|
53
|
+
lifecycle_action_result: 'CONTINUE'
|
54
|
+
})
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def record_lifecycle_action_heartbeat(hook_name:, group_name:)
|
59
|
+
run_with_backoff do
|
60
|
+
autoscaling.record_lifecycle_action_heartbeat({
|
61
|
+
lifecycle_hook_name: hook_name,
|
62
|
+
auto_scaling_group_name: group_name
|
63
|
+
})
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module MovableInk
|
2
|
+
class AWS
|
3
|
+
module EC2
|
4
|
+
def ec2(region: my_region)
|
5
|
+
@ec2_client ||= {}
|
6
|
+
@ec2_client[region] ||= Aws::EC2::Client.new(region: region)
|
7
|
+
end
|
8
|
+
|
9
|
+
def mi_env
|
10
|
+
@mi_env ||= load_mi_env
|
11
|
+
end
|
12
|
+
|
13
|
+
def load_mi_env
|
14
|
+
run_with_backoff do
|
15
|
+
ec2.describe_tags(filters: [
|
16
|
+
{
|
17
|
+
name: 'resource-id',
|
18
|
+
values: [instance_id]
|
19
|
+
}
|
20
|
+
])
|
21
|
+
.tags
|
22
|
+
.detect { |tag| tag.key == 'mi:env' }
|
23
|
+
.value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def thopter_instance
|
28
|
+
@thopter_instance ||= load_thopter_instance
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_thopter_instance
|
32
|
+
run_with_backoff do
|
33
|
+
ec2(region: 'us-east-1').describe_instances(filters: [
|
34
|
+
{
|
35
|
+
name: 'tag:mi:roles',
|
36
|
+
values: ['*thopter*']
|
37
|
+
},
|
38
|
+
{
|
39
|
+
name: 'tag:mi:env',
|
40
|
+
values: [mi_env]
|
41
|
+
},
|
42
|
+
{
|
43
|
+
name: 'instance-state-name',
|
44
|
+
values: ['running']
|
45
|
+
}
|
46
|
+
])
|
47
|
+
.reservations
|
48
|
+
.flat_map(&:instances)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def all_instances(region: my_region, no_filter: false)
|
53
|
+
@all_instances ||= {}
|
54
|
+
@all_instances[region] ||= load_all_instances(region, no_filter: no_filter)
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_all_instances(region, no_filter: false)
|
58
|
+
filters = if no_filter
|
59
|
+
nil
|
60
|
+
else
|
61
|
+
[{
|
62
|
+
name: 'instance-state-name',
|
63
|
+
values: ['running']
|
64
|
+
},
|
65
|
+
{
|
66
|
+
name: 'tag:mi:env',
|
67
|
+
values: [mi_env]
|
68
|
+
}]
|
69
|
+
end
|
70
|
+
run_with_backoff do
|
71
|
+
ec2(region: region).describe_instances(filters: filters).flat_map do |resp|
|
72
|
+
resp.reservations.flat_map(&:instances)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def instances(role:, region: my_region, availability_zone: nil)
|
78
|
+
role_pattern = mi_env == 'production' ? "^#{role}$" : "^*#{role}*$"
|
79
|
+
role_pattern = role_pattern.gsub('**','*').gsub('*','.*')
|
80
|
+
instances = all_instances(region: region).select { |instance|
|
81
|
+
instance.tags.detect { |tag|
|
82
|
+
tag.key == 'mi:roles'&&
|
83
|
+
Regexp.new(role_pattern).match(tag.value) &&
|
84
|
+
!tag.value.include?('decommissioned')
|
85
|
+
}
|
86
|
+
}
|
87
|
+
if availability_zone
|
88
|
+
instances.select { |instance|
|
89
|
+
instance.placement.availability_zone == availability_zone
|
90
|
+
}
|
91
|
+
else
|
92
|
+
instances
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def thopter
|
97
|
+
private_ip_addresses(thopter_instance).first
|
98
|
+
end
|
99
|
+
|
100
|
+
def statsd_host
|
101
|
+
instance_ip_addresses_by_role(role: 'statsd', availability_zone: availability_zone).sample
|
102
|
+
end
|
103
|
+
|
104
|
+
def private_ip_addresses(instances)
|
105
|
+
instances.map(&:private_ip_address)
|
106
|
+
end
|
107
|
+
|
108
|
+
def instance_ip_addresses_by_role(role:, availability_zone: nil)
|
109
|
+
private_ip_addresses(instances(role: role, availability_zone: availability_zone))
|
110
|
+
end
|
111
|
+
|
112
|
+
def instance_ip_addresses_by_role_ordered(role:)
|
113
|
+
instances = instances(role: role)
|
114
|
+
instances_in_my_az = instances.select { |instance| instance.placement.availability_zone == availability_zone }
|
115
|
+
ordered_instances = instances_in_my_az.shuffle + (instances - instances_in_my_az).shuffle
|
116
|
+
private_ip_addresses(ordered_instances)
|
117
|
+
end
|
118
|
+
|
119
|
+
def redis_by_role(role, port)
|
120
|
+
instance_ip_addresses_by_role(role: role)
|
121
|
+
.shuffle
|
122
|
+
.inject([]) { |redii, instance|
|
123
|
+
redii.push({"host" => instance, "port" => port})
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module MovableInk
|
2
|
+
class AWS
|
3
|
+
module Route53
|
4
|
+
def route53
|
5
|
+
@route53_client ||= Aws::Route53::Client.new(region: 'us-east-1')
|
6
|
+
end
|
7
|
+
|
8
|
+
def elastic_ips
|
9
|
+
@all_elastic_ips ||= load_all_elastic_ips
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_all_elastic_ips
|
13
|
+
run_with_backoff do
|
14
|
+
ec2.describe_addresses.flat_map(&:addresses)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def unassigned_elastic_ips
|
19
|
+
@unassigned_elastic_ips ||= elastic_ips.select { |address| address.association_id.nil? }
|
20
|
+
end
|
21
|
+
|
22
|
+
def reserved_elastic_ips
|
23
|
+
@reserved_elastic_ips ||= s3.get_object({bucket: 'movableink-chef', key: 'reserved_ips.json'}).body
|
24
|
+
.map { |line| JSON.parse(line) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def available_elastic_ips(role:)
|
28
|
+
reserved_elastic_ips.select do |ip|
|
29
|
+
ip["datacenter"] == datacenter &&
|
30
|
+
ip["role"] == role &&
|
31
|
+
unassigned_elastic_ips.map(&:allocation_id).include?(ip["allocation_id"])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def assign_ip_address(role:)
|
36
|
+
run_with_backoff do
|
37
|
+
ec2.associate_address({
|
38
|
+
instance_id: instance_id,
|
39
|
+
allocation_id: available_elastic_ips(role: role).sample["allocation_id"]
|
40
|
+
})
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def resource_record_sets(hosted_zone_id)
|
45
|
+
@resource_record_sets ||= {}
|
46
|
+
@resource_record_sets[hosted_zone_id] ||= list_all_r53_resource_record_sets(hosted_zone_id)
|
47
|
+
end
|
48
|
+
|
49
|
+
def list_all_r53_resource_record_sets(hosted_zone_id)
|
50
|
+
run_with_backoff do
|
51
|
+
route53.list_resource_record_sets({
|
52
|
+
hosted_zone_id: hosted_zone_id
|
53
|
+
}).flat_map(&:resource_record_sets)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module MovableInk
|
2
|
+
class AWS
|
3
|
+
module SNS
|
4
|
+
def sns(region: my_region)
|
5
|
+
@sns_client ||= {}
|
6
|
+
@sns_client[region] ||= Aws::SNS::Client.new(region: region)
|
7
|
+
end
|
8
|
+
|
9
|
+
def sns_slack_topic_arn
|
10
|
+
run_with_backoff do
|
11
|
+
sns.list_topics.each do |resp|
|
12
|
+
resp.topics.each do |topic|
|
13
|
+
return topic.topic_arn if topic.topic_arn.include? "slack-aws-alerts"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def notify_and_sleep(seconds, error_class)
|
20
|
+
message = "Throttled by AWS. Sleeping #{seconds} seconds, (#{error_class})"
|
21
|
+
notify_slack(subject: 'API Throttled',
|
22
|
+
message: message)
|
23
|
+
puts message
|
24
|
+
sleep seconds
|
25
|
+
end
|
26
|
+
|
27
|
+
def notify_nsq_can_not_be_drained
|
28
|
+
notify_slack(subject: 'NSQ not drained',
|
29
|
+
message: 'Unable to drain NSQ')
|
30
|
+
end
|
31
|
+
|
32
|
+
def notify_slack(subject:, message:)
|
33
|
+
run_with_backoff do
|
34
|
+
sns.publish(topic_arn: sns_slack_topic_arn,
|
35
|
+
subject: "#{subject} (#{instance_id}, #{my_region})",
|
36
|
+
message: message)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module MovableInk
|
2
|
+
class AWS
|
3
|
+
module SSM
|
4
|
+
def ssm
|
5
|
+
@ssm_client ||= Aws::SSM::Client.new(region: 'us-east-1')
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_secret(environment: mi_env, role:, attribute:)
|
9
|
+
run_with_backoff do
|
10
|
+
begin
|
11
|
+
resp = ssm.get_parameter(
|
12
|
+
name: "/#{environment}/#{role}/#{attribute}",
|
13
|
+
with_decryption: true
|
14
|
+
)
|
15
|
+
resp.parameter.value
|
16
|
+
rescue Aws::SSM::Errors::ParameterNotFound => e
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_role_secrets(environment: mi_env, role:)
|
23
|
+
path = "/#{environment}/#{role}"
|
24
|
+
run_with_backoff do
|
25
|
+
ssm.get_parameters_by_path(
|
26
|
+
path: path,
|
27
|
+
with_decryption: true
|
28
|
+
).inject({}) do |secrets, resp|
|
29
|
+
extract_parameters(resp.parameters, path)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def extract_parameters(parameters, path)
|
35
|
+
parameters.map do |param|
|
36
|
+
[ param.name.gsub("#{path}/", ''), param.value ]
|
37
|
+
end.to_h
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
require_relative 'aws/errors'
|
4
|
+
require_relative 'aws/ec2'
|
5
|
+
require_relative 'aws/sns'
|
6
|
+
require_relative 'aws/autoscaling'
|
7
|
+
require_relative 'aws/route53'
|
8
|
+
require_relative 'aws/ssm'
|
9
|
+
|
10
|
+
module MovableInk
|
11
|
+
class AWS
|
12
|
+
include EC2
|
13
|
+
include SNS
|
14
|
+
include Autoscaling
|
15
|
+
include Route53
|
16
|
+
include SSM
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def regions
|
20
|
+
{
|
21
|
+
'iad' => 'us-east-1',
|
22
|
+
'rld' => 'us-west-2',
|
23
|
+
'dub' => 'eu-west-1',
|
24
|
+
'ord' => 'us-east-2'
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(environment: nil)
|
30
|
+
@mi_env = environment
|
31
|
+
end
|
32
|
+
|
33
|
+
def run_with_backoff
|
34
|
+
9.times do |num|
|
35
|
+
begin
|
36
|
+
return yield
|
37
|
+
rescue Aws::EC2::Errors::RequestLimitExceeded,
|
38
|
+
Aws::SNS::Errors::ThrottledException,
|
39
|
+
Aws::AutoScaling::Errors::ThrottledException,
|
40
|
+
Aws::S3::Errors::SlowDown,
|
41
|
+
Aws::Route53::Errors::ThrottlingException,
|
42
|
+
Aws::SSM::Errors::TooManyUpdates
|
43
|
+
notify_and_sleep((num+1)**2 + rand(10), $!.class)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
raise MovableInk::AWS::Errors::FailedWithBackoff
|
47
|
+
end
|
48
|
+
|
49
|
+
def regions
|
50
|
+
self.class.regions
|
51
|
+
end
|
52
|
+
|
53
|
+
def availability_zone
|
54
|
+
@availability_zone ||= `ec2metadata --availability-zone`.chomp rescue raise(MovableInk::AWS::Errors::EC2Required)
|
55
|
+
end
|
56
|
+
|
57
|
+
def my_region
|
58
|
+
@my_region ||= availability_zone.chop
|
59
|
+
end
|
60
|
+
|
61
|
+
def instance_id
|
62
|
+
@instance_id ||= `ec2metadata --instance-id`.chomp rescue raise(MovableInk::AWS::Errors::EC2Required)
|
63
|
+
end
|
64
|
+
|
65
|
+
def datacenter(region: my_region)
|
66
|
+
regions.key(region)
|
67
|
+
end
|
68
|
+
|
69
|
+
def s3
|
70
|
+
@s3_client ||= Aws::S3::Client.new(region: 'us-east-1')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative '../lib/movable_ink/aws'
|
3
|
+
|
4
|
+
describe MovableInk::AWS::Autoscaling do
|
5
|
+
let(:aws) { MovableInk::AWS.new }
|
6
|
+
let(:autoscaling) { Aws::AutoScaling::Client.new(stub_responses: true) }
|
7
|
+
let(:ec2) { Aws::EC2::Client.new(stub_responses: true) }
|
8
|
+
let(:set_instance_health_data) { autoscaling.stub_data(:set_instance_health, {}) }
|
9
|
+
let(:complete_lifecycle_action_data) { autoscaling.stub_data(:complete_lifecycle_action, {}) }
|
10
|
+
let(:record_lifecycle_action_heartbeat_data) { autoscaling.stub_data(:record_lifecycle_action_heartbeat, {}) }
|
11
|
+
let(:create_tags_data) { ec2.stub_data(:create_tags, {}) }
|
12
|
+
let(:delete_tags_data) { ec2.stub_data(:delete_tags, {}) }
|
13
|
+
|
14
|
+
it "should mark an instance as unhealthy" do
|
15
|
+
autoscaling.stub_responses(:set_instance_health, set_instance_health_data)
|
16
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
17
|
+
allow(aws).to receive(:instance_id).and_return('i-12345')
|
18
|
+
allow(aws).to receive(:autoscaling).and_return(autoscaling)
|
19
|
+
|
20
|
+
expect(aws.mark_me_as_unhealthy).to eq(Aws::EmptyStructure.new)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should mark an instance as healthy" do
|
24
|
+
ec2.stub_responses(:create_tags, create_tags_data)
|
25
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
26
|
+
allow(aws).to receive(:instance_id).and_return('i-12345')
|
27
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
28
|
+
|
29
|
+
expect(aws.mark_me_as_healthy(role: 'some_role')).to eq(Aws::EmptyStructure.new)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should remove role tags" do
|
33
|
+
ec2.stub_responses(:delete_tags, delete_tags_data)
|
34
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
35
|
+
allow(aws).to receive(:instance_id).and_return('i-12345')
|
36
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
37
|
+
|
38
|
+
expect(aws.delete_role_tag(role: 'some_role')).to eq(Aws::EmptyStructure.new)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should complete lifecycle actions" do
|
42
|
+
autoscaling.stub_responses(:complete_lifecycle_action, complete_lifecycle_action_data)
|
43
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
44
|
+
allow(aws).to receive(:autoscaling).and_return(autoscaling)
|
45
|
+
|
46
|
+
expect(aws.complete_lifecycle_action(hook_name: 'hook', group_name: 'group', token: 'token')).to eq(Aws::EmptyStructure.new)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should record lifecycle action heartbeats" do
|
50
|
+
autoscaling.stub_responses(:record_lifecycle_action_heartbeat, record_lifecycle_action_heartbeat_data)
|
51
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
52
|
+
allow(aws).to receive(:autoscaling).and_return(autoscaling)
|
53
|
+
|
54
|
+
expect(aws.record_lifecycle_action_heartbeat(hook_name: 'hook', group_name: 'group')).to eq(Aws::EmptyStructure.new)
|
55
|
+
end
|
56
|
+
end
|
data/spec/aws_spec.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative '../lib/movable_ink/aws'
|
3
|
+
|
4
|
+
describe MovableInk::AWS do
|
5
|
+
context "outside EC2" do
|
6
|
+
it "should raise an error if EC2 is required" do
|
7
|
+
aws = MovableInk::AWS.new
|
8
|
+
expect{ aws.instance_id }.to raise_error(MovableInk::AWS::Errors::EC2Required)
|
9
|
+
expect{ aws.availability_zone }.to raise_error(MovableInk::AWS::Errors::EC2Required)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "inside EC2" do
|
14
|
+
it "should call ec2metadata to get the instance ID" do
|
15
|
+
aws = MovableInk::AWS.new
|
16
|
+
expect(aws).to receive(:`).with('ec2metadata --instance-id').and_return('i-12345')
|
17
|
+
expect(aws.instance_id).to eq('i-12345')
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should call ec2metadata to get the availability zone" do
|
21
|
+
aws = MovableInk::AWS.new
|
22
|
+
expect(aws).to receive(:`).with('ec2metadata --availability-zone').and_return("us-east-1a\n")
|
23
|
+
expect(aws.availability_zone).to eq('us-east-1a')
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should find the datacenter by region" do
|
27
|
+
aws = MovableInk::AWS.new
|
28
|
+
expect(aws).to receive(:`).with('ec2metadata --availability-zone').and_return("us-east-1a\n")
|
29
|
+
expect(aws.datacenter).to eq('iad')
|
30
|
+
end
|
31
|
+
|
32
|
+
context "MovableInk::AWS#run_with_backoff" do
|
33
|
+
it "should retry when throttled with increasing timeouts" do
|
34
|
+
aws = MovableInk::AWS.new(environment: 'test')
|
35
|
+
ec2 = Aws::EC2::Client.new(stub_responses: true)
|
36
|
+
ec2.stub_responses(:describe_instances, 'RequestLimitExceeded')
|
37
|
+
|
38
|
+
expect(aws).to receive(:notify_slack).exactly(9).times
|
39
|
+
expect(aws).to receive(:sleep).exactly(9).times.and_return(true)
|
40
|
+
|
41
|
+
aws.run_with_backoff { ec2.describe_instances } rescue nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise an error after too many timeouts" do
|
45
|
+
aws = MovableInk::AWS.new(environment: 'test')
|
46
|
+
ec2 = Aws::EC2::Client.new(stub_responses: true)
|
47
|
+
ec2.stub_responses(:describe_instances, 'RequestLimitExceeded')
|
48
|
+
|
49
|
+
expect(aws).to receive(:notify_and_sleep).exactly(9).times
|
50
|
+
expect{ aws.run_with_backoff { ec2.describe_instances } }.to raise_error(MovableInk::AWS::Errors::FailedWithBackoff)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/spec/ec2_spec.rb
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative '../lib/movable_ink/aws'
|
3
|
+
|
4
|
+
describe MovableInk::AWS::EC2 do
|
5
|
+
context "outside EC2" do
|
6
|
+
it "should raise an error if trying to load mi_env outside of EC2" do
|
7
|
+
aws = MovableInk::AWS.new
|
8
|
+
expect{ aws.mi_env }.to raise_error(MovableInk::AWS::Errors::EC2Required)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should use the provided environment" do
|
12
|
+
aws = MovableInk::AWS.new(environment: 'test')
|
13
|
+
expect(aws.mi_env).to eq('test')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "inside EC2" do
|
18
|
+
let(:aws) { MovableInk::AWS.new }
|
19
|
+
let(:ec2) { Aws::EC2::Client.new(stub_responses: true) }
|
20
|
+
let(:tag_data) { ec2.stub_data(:describe_tags, tags: [
|
21
|
+
{
|
22
|
+
key: 'mi:env',
|
23
|
+
value: 'test'
|
24
|
+
}
|
25
|
+
])
|
26
|
+
}
|
27
|
+
|
28
|
+
it "should find the environment from the current instance's tags" do
|
29
|
+
ec2.stub_responses(:describe_tags, tag_data)
|
30
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
31
|
+
allow(aws).to receive(:instance_id).and_return('i-12345')
|
32
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
33
|
+
|
34
|
+
expect(aws.mi_env).to eq('test')
|
35
|
+
end
|
36
|
+
|
37
|
+
context "thopter" do
|
38
|
+
let(:thopter_data) { ec2.stub_data(:describe_instances, reservations: [
|
39
|
+
instances: [
|
40
|
+
{
|
41
|
+
tags: [
|
42
|
+
{
|
43
|
+
key: 'mi:env',
|
44
|
+
value: 'test'
|
45
|
+
},
|
46
|
+
{
|
47
|
+
key: 'mi:roles',
|
48
|
+
value: 'thopter'
|
49
|
+
},
|
50
|
+
{
|
51
|
+
key: 'Name',
|
52
|
+
value: 'thopter'
|
53
|
+
}
|
54
|
+
],
|
55
|
+
private_ip_address: '1.2.3.4'
|
56
|
+
}
|
57
|
+
]])
|
58
|
+
}
|
59
|
+
|
60
|
+
it "should find the thopter instance" do
|
61
|
+
ec2.stub_responses(:describe_instances, thopter_data)
|
62
|
+
allow(aws).to receive(:mi_env).and_return('test')
|
63
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
64
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
65
|
+
|
66
|
+
expect(aws.thopter_instance.count).to eq(1)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should find the thopter instance's private IP" do
|
70
|
+
ec2.stub_responses(:describe_instances, thopter_data)
|
71
|
+
allow(aws).to receive(:mi_env).and_return('test')
|
72
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
73
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
74
|
+
|
75
|
+
expect(aws.thopter).to eq('1.2.3.4')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "statsd" do
|
80
|
+
let(:availability_zone) { 'us-east-1a' }
|
81
|
+
let(:statsd_data) { ec2.stub_data(:describe_instances, reservations: [
|
82
|
+
instances: [
|
83
|
+
{
|
84
|
+
tags: [
|
85
|
+
{
|
86
|
+
key: 'mi:roles',
|
87
|
+
value: 'statsd'
|
88
|
+
}
|
89
|
+
],
|
90
|
+
private_ip_address: '10.0.0.1',
|
91
|
+
placement: {
|
92
|
+
availability_zone: availability_zone
|
93
|
+
}
|
94
|
+
},
|
95
|
+
{
|
96
|
+
tags: [
|
97
|
+
{
|
98
|
+
key: 'mi:roles',
|
99
|
+
value: 'statsd'
|
100
|
+
}
|
101
|
+
],
|
102
|
+
private_ip_address: '10.0.0.2',
|
103
|
+
placement: {
|
104
|
+
availability_zone: availability_zone
|
105
|
+
}
|
106
|
+
},
|
107
|
+
{
|
108
|
+
tags: [
|
109
|
+
{
|
110
|
+
key: 'mi:roles',
|
111
|
+
value: 'something_else'
|
112
|
+
}
|
113
|
+
],
|
114
|
+
private_ip_address: '10.0.0.3',
|
115
|
+
placement: {
|
116
|
+
availability_zone: availability_zone
|
117
|
+
}
|
118
|
+
}
|
119
|
+
]])
|
120
|
+
}
|
121
|
+
|
122
|
+
it "should find one of the statsd hosts" do
|
123
|
+
ec2.stub_responses(:describe_instances, statsd_data)
|
124
|
+
allow(aws).to receive(:mi_env).and_return('test')
|
125
|
+
allow(aws).to receive(:availability_zone).and_return(availability_zone)
|
126
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
127
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
128
|
+
|
129
|
+
expect(['10.0.0.1', '10.0.0.2']).to include(aws.statsd_host)
|
130
|
+
expect(['10.0.0.1', '10.0.0.2']).to include(aws.statsd_host)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "ordered roles" do
|
135
|
+
let(:my_availability_zone) { 'us-east-1a' }
|
136
|
+
let(:other_availability_zone) { 'us-east-1b' }
|
137
|
+
let(:instance_data) { ec2.stub_data(:describe_instances, reservations: [
|
138
|
+
instances: [
|
139
|
+
{
|
140
|
+
tags: [
|
141
|
+
{
|
142
|
+
key: 'mi:roles',
|
143
|
+
value: 'app_db_replica'
|
144
|
+
}
|
145
|
+
],
|
146
|
+
private_ip_address: '10.0.0.1',
|
147
|
+
placement: {
|
148
|
+
availability_zone: my_availability_zone
|
149
|
+
}
|
150
|
+
},
|
151
|
+
{
|
152
|
+
tags: [
|
153
|
+
{
|
154
|
+
key: 'mi:roles',
|
155
|
+
value: 'app_db_replica'
|
156
|
+
}
|
157
|
+
],
|
158
|
+
private_ip_address: '10.0.0.2',
|
159
|
+
placement: {
|
160
|
+
availability_zone: other_availability_zone
|
161
|
+
}
|
162
|
+
}
|
163
|
+
]])
|
164
|
+
}
|
165
|
+
|
166
|
+
it "should find hosts and order them with my AZ first" do
|
167
|
+
ec2.stub_responses(:describe_instances, instance_data)
|
168
|
+
allow(aws).to receive(:mi_env).and_return('test')
|
169
|
+
allow(aws).to receive(:availability_zone).and_return(my_availability_zone)
|
170
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
171
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
172
|
+
|
173
|
+
expect(aws.instance_ip_addresses_by_role_ordered(role: 'app_db_replica').count).to eq(2)
|
174
|
+
expect(aws.instance_ip_addresses_by_role_ordered(role: 'app_db_replica').first).to eq('10.0.0.1')
|
175
|
+
expect(aws.instance_ip_addresses_by_role_ordered(role: 'app_db_replica').last).to eq('10.0.0.2')
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context "redii" do
|
180
|
+
let(:port) { 6379 }
|
181
|
+
let(:availability_zone) { 'us-east-1a' }
|
182
|
+
let(:redis_data) { ec2.stub_data(:describe_instances, reservations: [
|
183
|
+
instances: [
|
184
|
+
{
|
185
|
+
tags: [
|
186
|
+
{
|
187
|
+
key: 'mi:roles',
|
188
|
+
value: 'visitor_redis'
|
189
|
+
}
|
190
|
+
],
|
191
|
+
private_ip_address: '10.0.0.1',
|
192
|
+
placement: {
|
193
|
+
availability_zone: availability_zone
|
194
|
+
}
|
195
|
+
},
|
196
|
+
{
|
197
|
+
tags: [
|
198
|
+
{
|
199
|
+
key: 'mi:roles',
|
200
|
+
value: 'visitor_redis'
|
201
|
+
}
|
202
|
+
],
|
203
|
+
private_ip_address: '10.0.0.2',
|
204
|
+
placement: {
|
205
|
+
availability_zone: availability_zone
|
206
|
+
}
|
207
|
+
}
|
208
|
+
]])
|
209
|
+
}
|
210
|
+
let(:redii) { [{"host" => '10.0.0.1', "port" => 6379},{"host" => '10.0.0.2', "port" => 6379}] }
|
211
|
+
|
212
|
+
it "should return redis IPs and ports" do
|
213
|
+
ec2.stub_responses(:describe_instances, redis_data)
|
214
|
+
allow(aws).to receive(:mi_env).and_return('test')
|
215
|
+
allow(aws).to receive(:availability_zone).and_return(availability_zone)
|
216
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
217
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
218
|
+
|
219
|
+
expect(aws.redis_by_role('visitor_redis', port)).to match_array(redii)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative '../lib/movable_ink/aws'
|
3
|
+
|
4
|
+
describe MovableInk::AWS::Route53 do
|
5
|
+
let(:aws) { MovableInk::AWS.new }
|
6
|
+
|
7
|
+
context "elastic IPs" do
|
8
|
+
let(:ec2) { Aws::EC2::Client.new(stub_responses: true) }
|
9
|
+
let(:s3) { Aws::S3::Client.new(stub_responses: true) }
|
10
|
+
let(:elastic_ip_data) { ec2.stub_data(:describe_addresses, addresses: [
|
11
|
+
{
|
12
|
+
allocation_id: "eipalloc-1",
|
13
|
+
association_id: "eipassoc-1",
|
14
|
+
domain: "vpc",
|
15
|
+
public_ip: "1.1.1.1"
|
16
|
+
},
|
17
|
+
{
|
18
|
+
allocation_id: "eipalloc-2",
|
19
|
+
association_id: "eipassoc-2",
|
20
|
+
domain: "vpc",
|
21
|
+
public_ip: "1.1.1.2"
|
22
|
+
},
|
23
|
+
{
|
24
|
+
allocation_id: "eipalloc-3",
|
25
|
+
association_id: nil,
|
26
|
+
domain: "vpc",
|
27
|
+
public_ip: "1.1.1.3"
|
28
|
+
}
|
29
|
+
])
|
30
|
+
}
|
31
|
+
let(:associate_address_data) { ec2.stub_data(:associate_address, association_id: 'eipassoc-3') }
|
32
|
+
let(:reserved_ips) { StringIO.new(
|
33
|
+
"{\"datacenter\":\"iad\",\"allocation_id\":\"eipalloc-1\",\"public_ip\":\"1.1.1.1\",\"role\":\"some_role\"}
|
34
|
+
{\"datacenter\":\"iad\",\"allocation_id\":\"eipalloc-2\",\"public_ip\":\"1.1.1.2\",\"role\":\"some_role\"}
|
35
|
+
{\"datacenter\":\"iad\",\"allocation_id\":\"eipalloc-3\",\"public_ip\":\"1.1.1.3\",\"role\":\"some_role\"}"
|
36
|
+
)}
|
37
|
+
let(:s3_reserved_ips_data) { s3.stub_data(:get_object, body: reserved_ips) }
|
38
|
+
|
39
|
+
it "should find all elastic IPs" do
|
40
|
+
ec2.stub_responses(:describe_addresses, elastic_ip_data)
|
41
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
42
|
+
allow(aws).to receive(:instance_id).and_return('i-12345')
|
43
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
44
|
+
|
45
|
+
expect(aws.elastic_ips.count).to eq(3)
|
46
|
+
expect(aws.elastic_ips.first.public_ip).to eq('1.1.1.1')
|
47
|
+
expect(aws.elastic_ips.last.public_ip).to eq('1.1.1.3')
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should find unassigned elastic IPs" do
|
51
|
+
ec2.stub_responses(:describe_addresses, elastic_ip_data)
|
52
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
53
|
+
allow(aws).to receive(:instance_id).and_return('i-12345')
|
54
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
55
|
+
|
56
|
+
expect(aws.unassigned_elastic_ips.count).to eq(1)
|
57
|
+
expect(aws.unassigned_elastic_ips.first.public_ip).to eq('1.1.1.3')
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should load reserved elastic IPs from S3" do
|
61
|
+
s3.stub_responses(:get_object, s3_reserved_ips_data)
|
62
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
63
|
+
allow(aws).to receive(:instance_id).and_return('i-12345')
|
64
|
+
allow(aws).to receive(:s3).and_return(s3)
|
65
|
+
|
66
|
+
expect(aws.reserved_elastic_ips.count).to eq(3)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should filter reserved IPs to get available IPs" do
|
70
|
+
ec2.stub_responses(:describe_addresses, elastic_ip_data)
|
71
|
+
s3.stub_responses(:get_object, s3_reserved_ips_data)
|
72
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
73
|
+
allow(aws).to receive(:instance_id).and_return('i-12345')
|
74
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
75
|
+
allow(aws).to receive(:s3).and_return(s3)
|
76
|
+
|
77
|
+
expect(aws.available_elastic_ips(role: 'some_role').count).to eq(1)
|
78
|
+
expect(aws.available_elastic_ips(role: 'some_role').first['public_ip']).to eq('1.1.1.3')
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should assign an elastic IP" do
|
82
|
+
ec2.stub_responses(:describe_addresses, elastic_ip_data)
|
83
|
+
ec2.stub_responses(:associate_address, associate_address_data)
|
84
|
+
s3.stub_responses(:get_object, s3_reserved_ips_data)
|
85
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
86
|
+
allow(aws).to receive(:instance_id).and_return('i-12345')
|
87
|
+
allow(aws).to receive(:ec2).and_return(ec2)
|
88
|
+
allow(aws).to receive(:s3).and_return(s3)
|
89
|
+
|
90
|
+
expect(aws.assign_ip_address(role: 'some_role').association_id).to eq('eipassoc-3')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "resource record sets" do
|
95
|
+
let(:route53) { Aws::Route53::Client.new(stub_responses: true) }
|
96
|
+
let(:rrset_data) { route53.stub_data(:list_resource_record_sets, resource_record_sets: [
|
97
|
+
{
|
98
|
+
name: 'host1.domain.tld.'
|
99
|
+
},
|
100
|
+
{
|
101
|
+
name: 'host2.domain.tld.'
|
102
|
+
}
|
103
|
+
])
|
104
|
+
}
|
105
|
+
|
106
|
+
it "should retrieve all rrsets for zone" do
|
107
|
+
route53.stub_responses(:list_resource_record_sets, rrset_data)
|
108
|
+
allow(aws).to receive(:route53).and_return(route53)
|
109
|
+
|
110
|
+
expect(aws.resource_record_sets('Z123').count).to eq(2)
|
111
|
+
expect(aws.resource_record_sets('Z123').first.name).to eq('host1.domain.tld.')
|
112
|
+
expect(aws.resource_record_sets('Z123').last.name).to eq('host2.domain.tld.')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/spec/sns_spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative '../lib/movable_ink/aws'
|
3
|
+
|
4
|
+
describe MovableInk::AWS::SNS do
|
5
|
+
let(:aws) { MovableInk::AWS.new }
|
6
|
+
let(:sns) { Aws::SNS::Client.new(stub_responses: true) }
|
7
|
+
let(:topic_data) { sns.stub_data(:list_topics, topics: [
|
8
|
+
{
|
9
|
+
topic_arn: 'slack-aws-alerts'
|
10
|
+
}
|
11
|
+
])
|
12
|
+
}
|
13
|
+
let(:publish_response) { sns.stub_data(:publish, message_id: 'messageId')}
|
14
|
+
|
15
|
+
it "should find the slack sns topic" do
|
16
|
+
sns.stub_responses(:list_topics, topic_data)
|
17
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
18
|
+
allow(aws).to receive(:sns).and_return(sns)
|
19
|
+
|
20
|
+
expect(aws.sns_slack_topic_arn).to eq('slack-aws-alerts')
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should notify with the specified subject and message" do
|
24
|
+
sns.stub_responses(:list_topics, topic_data)
|
25
|
+
allow(aws).to receive(:my_region).and_return('us-east-1')
|
26
|
+
allow(aws).to receive(:instance_id).and_return('test instance')
|
27
|
+
allow(aws).to receive(:sns).and_return(sns)
|
28
|
+
|
29
|
+
expect(aws.notify_slack(subject: 'Test subject', message: 'Test message').message_id).to eq('messageId')
|
30
|
+
end
|
31
|
+
end
|
data/spec/ssm_spec.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative '../lib/movable_ink/aws'
|
3
|
+
|
4
|
+
describe MovableInk::AWS::SSM do
|
5
|
+
let(:aws) { MovableInk::AWS.new }
|
6
|
+
let(:ssm) { Aws::SSM::Client.new(stub_responses: true) }
|
7
|
+
let(:parameter) { ssm.stub_data(:get_parameter, parameter: {
|
8
|
+
name: '/test/sneakers/setec-astronomy',
|
9
|
+
type: 'SecureString',
|
10
|
+
value: 'too-many-secrets'
|
11
|
+
})
|
12
|
+
}
|
13
|
+
let(:parameters) { ssm.stub_data(:get_parameters_by_path, parameters: [
|
14
|
+
{
|
15
|
+
name: '/test/zelda/Its',
|
16
|
+
type: 'SecureString',
|
17
|
+
value: "It's"
|
18
|
+
},
|
19
|
+
{
|
20
|
+
name: '/test/zelda/a',
|
21
|
+
type: 'SecureString',
|
22
|
+
value: "dangerous"
|
23
|
+
},
|
24
|
+
{
|
25
|
+
name: '/test/zelda/secret',
|
26
|
+
type: 'SecureString',
|
27
|
+
value: "to"
|
28
|
+
},
|
29
|
+
{
|
30
|
+
name: '/test/zelda/to',
|
31
|
+
type: 'SecureString',
|
32
|
+
value: "go"
|
33
|
+
},
|
34
|
+
{
|
35
|
+
name: '/test/zelda/everyone',
|
36
|
+
type: 'SecureString',
|
37
|
+
value: "alone"
|
38
|
+
}
|
39
|
+
])
|
40
|
+
}
|
41
|
+
let(:zelda_secrets) {
|
42
|
+
{
|
43
|
+
"Its" => "It's",
|
44
|
+
"a" => "dangerous",
|
45
|
+
"secret" => "to",
|
46
|
+
"to" => "go",
|
47
|
+
"everyone" => "alone"
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
it "should retrieve a decrypted secret" do
|
52
|
+
ssm.stub_responses(:get_parameter, parameter)
|
53
|
+
allow(aws).to receive(:mi_env).and_return('test')
|
54
|
+
allow(aws).to receive(:ssm).and_return(ssm)
|
55
|
+
|
56
|
+
expect(aws.get_secret(role: 'sneakers', attribute: 'setec-astronomy')).to eq('too-many-secrets')
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should retrieve all secrets for a role" do
|
60
|
+
ssm.stub_responses(:get_parameters_by_path, parameters)
|
61
|
+
allow(aws).to receive(:mi_env).and_return('test')
|
62
|
+
allow(aws).to receive(:ssm).and_return(ssm)
|
63
|
+
|
64
|
+
expect(aws.get_role_secrets(role: 'zelda')).to eq(zelda_secrets)
|
65
|
+
end
|
66
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: MovableInkAWS
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Chesler
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-02-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.10'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.10.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.10'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.10.0
|
33
|
+
description: AWS Utility methods for MovableInk
|
34
|
+
email: mchesler@movableink.com
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
extra_rdoc_files: []
|
38
|
+
files:
|
39
|
+
- ".travis.yml"
|
40
|
+
- Gemfile
|
41
|
+
- Gemfile.lock
|
42
|
+
- MovableInkAWS.gemspec
|
43
|
+
- README.md
|
44
|
+
- lib/movable_ink/aws.rb
|
45
|
+
- lib/movable_ink/aws/autoscaling.rb
|
46
|
+
- lib/movable_ink/aws/ec2.rb
|
47
|
+
- lib/movable_ink/aws/errors.rb
|
48
|
+
- lib/movable_ink/aws/route53.rb
|
49
|
+
- lib/movable_ink/aws/sns.rb
|
50
|
+
- lib/movable_ink/aws/ssm.rb
|
51
|
+
- lib/movable_ink/version.rb
|
52
|
+
- spec/autoscaling_spec.rb
|
53
|
+
- spec/aws_spec.rb
|
54
|
+
- spec/ec2_spec.rb
|
55
|
+
- spec/route53_spec.rb
|
56
|
+
- spec/sns_spec.rb
|
57
|
+
- spec/ssm_spec.rb
|
58
|
+
homepage:
|
59
|
+
licenses: []
|
60
|
+
metadata: {}
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.6.14
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: AWS Utility methods for MovableInk
|
81
|
+
test_files:
|
82
|
+
- spec/autoscaling_spec.rb
|
83
|
+
- spec/aws_spec.rb
|
84
|
+
- spec/ec2_spec.rb
|
85
|
+
- spec/route53_spec.rb
|
86
|
+
- spec/sns_spec.rb
|
87
|
+
- spec/ssm_spec.rb
|