awspec 1.24.1 → 1.25.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +69 -0
- data/.github/workflows/doc.yml +29 -0
- data/.rubocop.yml +47 -2
- data/Gemfile +2 -0
- data/README.md +2 -4
- data/Rakefile +0 -1
- data/awspec.gemspec +3 -5
- data/doc/_resource_types/cloudwatch_logs.md +9 -0
- data/doc/_resource_types/eks_nodegroup.md +53 -0
- data/doc/resource_types.md +62 -9
- data/lib/awspec/command/generate.rb +1 -1
- data/lib/awspec/generator/spec/cloudwatch_logs.rb +5 -1
- data/lib/awspec/generator/spec/elasticache.rb +43 -0
- data/lib/awspec/generator.rb +1 -0
- data/lib/awspec/helper/finder/ec2.rb +41 -16
- data/lib/awspec/helper/finder/ecs.rb +1 -1
- data/lib/awspec/helper/finder/subnet.rb +118 -20
- data/lib/awspec/helper/finder.rb +6 -0
- data/lib/awspec/helper/states.rb +16 -0
- data/lib/awspec/matcher/belong_to_subnets.rb +14 -0
- data/lib/awspec/matcher/have_metric_filter.rb +9 -0
- data/lib/awspec/matcher.rb +4 -0
- data/lib/awspec/setup.rb +1 -1
- data/lib/awspec/stub/cloudwatch_logs.rb +2 -1
- data/lib/awspec/stub/ec2_non_existing.rb +7 -0
- data/lib/awspec/stub/eks_nodegroup.rb +61 -1
- data/lib/awspec/stub/elasticsearch.rb +0 -1
- data/lib/awspec/stub/rds_db_parameter_group.rb +8 -0
- data/lib/awspec/stub/sns_topic.rb +15 -8
- data/lib/awspec/stub/sns_topic_error.rb +13 -0
- data/lib/awspec/type/cloudwatch_logs.rb +8 -3
- data/lib/awspec/type/ec2.rb +21 -16
- data/lib/awspec/type/eks_nodegroup.rb +105 -0
- data/lib/awspec/type/rds_db_parameter_group.rb +54 -0
- data/lib/awspec/type/s3_bucket.rb +1 -1
- data/lib/awspec/version.rb +1 -1
- metadata +26 -20
- data/.tachikoma.yml +0 -1
- data/.travis.yml +0 -23
@@ -51,7 +51,7 @@ module Awspec
|
|
51
51
|
types_for_generate_all = %w(
|
52
52
|
cloudwatch_alarm cloudwatch_event directconnect ebs efs
|
53
53
|
elasticsearch iam_group iam_policy iam_role iam_user kms lambda
|
54
|
-
acm cloudwatch_logs eip codebuild
|
54
|
+
acm cloudwatch_logs eip codebuild elasticache
|
55
55
|
)
|
56
56
|
|
57
57
|
types_for_generate_all.each do |type|
|
@@ -23,7 +23,11 @@ module Awspec::Generator
|
|
23
23
|
metric_filters = select_all_cloudwatch_logs_metric_filter(log_group)
|
24
24
|
metric_filter_lines = []
|
25
25
|
metric_filters.each do |metric_filter|
|
26
|
-
line = "it { should have_metric_filter('#{metric_filter.filter_name}')
|
26
|
+
line = "it { should have_metric_filter('#{metric_filter.filter_name}')"
|
27
|
+
unless metric_filter.filter_pattern.empty?
|
28
|
+
line += ".filter_pattern('#{metric_filter.filter_pattern}')"
|
29
|
+
end
|
30
|
+
line += ' }'
|
27
31
|
metric_filter_lines.push(line)
|
28
32
|
end
|
29
33
|
metric_filter_lines
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Awspec::Generator
|
2
|
+
module Spec
|
3
|
+
class Elasticache
|
4
|
+
include Awspec::Helper::Finder
|
5
|
+
def generate_all
|
6
|
+
opt = {}
|
7
|
+
clusters = []
|
8
|
+
loop do
|
9
|
+
res = elasticache_client.describe_cache_clusters(opt)
|
10
|
+
clusters.push(*res.cache_clusters)
|
11
|
+
break if res.marker.nil?
|
12
|
+
opt = { marker: res.marker }
|
13
|
+
end
|
14
|
+
raise 'Not Found Cache Clusters' if clusters.empty?
|
15
|
+
ERB.new(cache_clusters_spec_template, nil, '-').result(binding).gsub(/^\n/, '')
|
16
|
+
end
|
17
|
+
|
18
|
+
def cache_clusters_spec_template
|
19
|
+
template = <<-'EOF'
|
20
|
+
<% clusters.each do |cluster| %>
|
21
|
+
describe elasticache('<%= cluster.cache_cluster_id %>') do
|
22
|
+
it { should exist }
|
23
|
+
it { should be_available }
|
24
|
+
it { should have_cache_parameter_group('<%= cluster.cache_parameter_group.cache_parameter_group_name %>') }
|
25
|
+
it { should belong_to_cache_subnet_group('<%= cluster.cache_subnet_group_name %>') }
|
26
|
+
<% unless cluster.replication_group_id.nil? %>
|
27
|
+
its(:replication_group_id) { should eq '<%= cluster.replication_group_id %>' }
|
28
|
+
<% end %>
|
29
|
+
its(:engine) { should eq '<%= cluster.engine %>' }
|
30
|
+
its(:engine_version) { should eq '<%= cluster.engine_version %>' }
|
31
|
+
its(:cache_node_type) { should eq '<%= cluster.cache_node_type %>' }
|
32
|
+
<% unless cluster.snapshot_retention_limit.nil? %>
|
33
|
+
its(:snapshot_retention_limit) { should eq <%= cluster.snapshot_retention_limit %> }
|
34
|
+
its(:snapshot_window) { should eq '<%= cluster.snapshot_window %>' }
|
35
|
+
<% end %>
|
36
|
+
end
|
37
|
+
<% end %>
|
38
|
+
EOF
|
39
|
+
template
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/awspec/generator.rb
CHANGED
@@ -27,6 +27,7 @@ require 'awspec/generator/spec/cloudwatch_logs'
|
|
27
27
|
require 'awspec/generator/spec/alb'
|
28
28
|
require 'awspec/generator/spec/nlb'
|
29
29
|
require 'awspec/generator/spec/internet_gateway'
|
30
|
+
require 'awspec/generator/spec/elasticache'
|
30
31
|
require 'awspec/generator/spec/elasticsearch'
|
31
32
|
require 'awspec/generator/spec/eip'
|
32
33
|
require 'awspec/generator/spec/rds_db_parameter_group'
|
@@ -3,23 +3,48 @@ module Awspec::Helper
|
|
3
3
|
module Ec2
|
4
4
|
def find_ec2(id)
|
5
5
|
# instance_id or tag:Name
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
|
7
|
+
# First tries to search by using an educated guess, based on the
|
8
|
+
# references below:
|
9
|
+
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/resource-ids.html
|
10
|
+
# https://docs.chef.io/inspec/resources/aws_ec2_instance/
|
11
|
+
# This should be faster then just first trying ID when the parameter is
|
12
|
+
# clearly not one
|
13
|
+
|
14
|
+
# https://medium.com/@Bakku1505/ruby-start-with-end-with-vs-regular-expressions-59728be0859e
|
15
|
+
if id.start_with?('i-') && id.length == 19 && id =~ /^i-[0-9a-f]/
|
16
|
+
begin
|
17
|
+
res = ec2_client.describe_instances({
|
18
|
+
instance_ids: [id]
|
19
|
+
})
|
20
|
+
rescue Aws::EC2::Errors::InvalidInstanceIDNotFound, Aws::EC2::Errors::InvalidInstanceIDMalformed => e
|
21
|
+
res = ec2_client.describe_instances({
|
22
|
+
filters: [{ name: 'tag:Name', values: [id] }]
|
23
|
+
})
|
24
|
+
end
|
25
|
+
else
|
26
|
+
begin
|
27
|
+
res = ec2_client.describe_instances({
|
28
|
+
filters: [{ name: 'tag:Name', values: [id] }]
|
29
|
+
})
|
30
|
+
rescue Aws::EC2::Errors::InvalidInstanceIDNotFound, Aws::EC2::Errors::InvalidInstanceIDMalformed => e
|
31
|
+
res = ec2_client.describe_instances({
|
32
|
+
instance_ids: [id]
|
33
|
+
})
|
34
|
+
if res.reservations.count > 1
|
35
|
+
STDERR.puts "Warning: '#{id}' unexpectedly identified as a valid instance ID during fallback search"
|
36
|
+
end
|
37
|
+
end
|
22
38
|
end
|
39
|
+
|
40
|
+
return nil if res.reservations.count == 0
|
41
|
+
return res.reservations.first.instances.single_resource(id) if res.reservations.count == 1
|
42
|
+
raise Awspec::DuplicatedResourceTypeError, dup_ec2_instance(id) if res.reservations.count > 1
|
43
|
+
raise "Unexpected condition of having reservations = #{res.reservations.count}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def dup_ec2_instance(id)
|
47
|
+
"Duplicate instances matching id or tag #{id}"
|
23
48
|
end
|
24
49
|
|
25
50
|
def find_ec2_attribute(id, attribute)
|
@@ -36,7 +36,7 @@ module Awspec::Helper
|
|
36
36
|
# deprecated method
|
37
37
|
def find_ecs_container_instances(cluster, container_instances)
|
38
38
|
res = ecs_client.describe_container_instances(cluster: cluster, container_instances: container_instances)
|
39
|
-
res.container_instances if res.container_instances
|
39
|
+
res.container_instances if res.container_instances # rubocop:disable Style/UnneededCondition
|
40
40
|
end
|
41
41
|
|
42
42
|
alias_method :list_ecs_container_instances, :select_ecs_container_instance_arn_by_cluster_name # deprecated method
|
@@ -1,29 +1,127 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
1
3
|
module Awspec::Helper
|
2
4
|
module Finder
|
3
5
|
module Subnet
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
6
|
+
# Implements in-memory cache for +AWS::Ec2::Client+ +describe_subnets+
|
7
|
+
# method.
|
8
|
+
|
9
|
+
# == Usage
|
10
|
+
# Includes {Singleton}[https://ruby-doc.org/stdlib-2.7.3/libdoc/singleton/rdoc/index.html]
|
11
|
+
# module, so use +instance+ instead of +new+ to get a instance.
|
12
|
+
#
|
13
|
+
# It is intended to be used internally by the +find_subnet+ function only.
|
14
|
+
#
|
15
|
+
# Many of the methods expect a symbol to search through the cache to
|
16
|
+
# avoid having to call +to_sym+ multiple times.
|
17
|
+
|
18
|
+
class SubnetCache
|
19
|
+
include Singleton
|
20
|
+
|
21
|
+
def initialize # :nodoc:
|
22
|
+
@by_tag_name = {}
|
23
|
+
@by_cidr = {}
|
24
|
+
@subnet_ids = {}
|
25
|
+
@ip_matcher = Regexp.new('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}$')
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add a mapping of a CIDR to the respective subnet ID
|
29
|
+
def add_by_cidr(cidr, subnet_id)
|
30
|
+
key_sym = cidr.to_sym
|
31
|
+
@by_cidr[key_sym] = subnet_id.to_sym unless @by_cidr.key?(key_sym)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Add a mapping of a tag to the respective subnet ID
|
35
|
+
def add_by_tag(tag, subnet_id)
|
36
|
+
key_sym = tag.to_sym
|
37
|
+
@by_tag_name[key_sym] = subnet_id.to_sym unless @by_tag_name.key?(key_sym)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add a +Aws::EC2::Types::Subnet+ instance to the cache, mapping it's ID
|
41
|
+
# to the instance itself.
|
42
|
+
def add_subnet(subnet)
|
43
|
+
key_sym = subnet.subnet_id.to_sym
|
44
|
+
@subnet_ids[key_sym] = subnet unless @subnet_ids.key?(key_sym)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Check if a subnet ID (as a symbol) exists in the cache.
|
48
|
+
def has_subnet?(subnet_id_symbol)
|
49
|
+
@subnet_ids.key?(subnet_id_symbol)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return a +Aws::EC2::Types::Subnet+ that matches the given CIDR.
|
53
|
+
def subnet_by_cidr(cidr_symbol)
|
54
|
+
@subnet_ids[@by_cidr[cidr_symbol]]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return a +Aws::EC2::Types::Subnet+ that matches the given tag.
|
58
|
+
def subnet_by_tag(tag_symbol)
|
59
|
+
@subnet_ids[@by_tag_name[tag_symbol]]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Return a +Aws::EC2::Types::Subnet+ that matches the given subnet ID.
|
63
|
+
def subnet_by_id(subnet_id_symbol)
|
64
|
+
@subnet_ids[subnet_id_symbol]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check if a given string looks like a IPv4 CIDR.
|
68
|
+
def is_cidr?(subnet_id)
|
69
|
+
@ip_matcher.match(subnet_id)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Check if the cache was already initialized or not.
|
73
|
+
def empty?
|
74
|
+
@subnet_ids.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return the cache as a string.
|
78
|
+
def to_s
|
79
|
+
"by tag name: #{@by_tag_name}, by CIDR: #{@by_cidr}"
|
80
|
+
end
|
19
81
|
end
|
20
82
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
83
|
+
# Try to locate a +Aws::EC2::Types::Subnet+ with a given subnet ID.
|
84
|
+
#
|
85
|
+
# A subnet ID might be multiple things, like the
|
86
|
+
# +Aws::EC2::Types::Subnet.subnet_id+, or a IPv4 CIDR or the value for the
|
87
|
+
# +Name+ tag associated with the subnet.
|
88
|
+
#
|
89
|
+
# Returns a instance of +Aws::EC2::Types::Subnet+ or +nil+.
|
90
|
+
def find_subnet(subnet_id)
|
91
|
+
cache = SubnetCache.instance
|
92
|
+
|
93
|
+
if cache.empty?
|
94
|
+
res = ec2_client.describe_subnets
|
95
|
+
|
96
|
+
res.subnets.each do |sub|
|
97
|
+
cache.add_by_cidr(sub.cidr_block, sub.subnet_id)
|
98
|
+
cache.add_subnet(sub)
|
99
|
+
next if sub.tags.empty?
|
100
|
+
|
101
|
+
sub.tags.each do |tag|
|
102
|
+
if tag[:key].eql?('Name')
|
103
|
+
cache.add_by_tag(tag[:value], sub.subnet_id)
|
104
|
+
break
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
id_key = subnet_id.to_sym
|
111
|
+
return cache.subnet_by_id(id_key) if subnet_id.start_with?('subnet-') && cache.has_subnet?(id_key)
|
112
|
+
return cache.subnet_by_cidr(id_key) if cache.is_cidr?(subnet_id) && cache.by_cidr.key?(id_key)
|
113
|
+
cache.subnet_by_tag(id_key)
|
26
114
|
end
|
27
115
|
end
|
116
|
+
|
117
|
+
# Search for the subnets associated with a given VPC ID.
|
118
|
+
#
|
119
|
+
# Returns an array of +Aws::EC2::Types::Subnet+ instances.
|
120
|
+
def select_subnet_by_vpc_id(vpc_id)
|
121
|
+
res = ec2_client.describe_subnets({
|
122
|
+
filters: [{ name: 'vpc-id', values: [vpc_id] }]
|
123
|
+
})
|
124
|
+
res.subnets
|
125
|
+
end
|
28
126
|
end
|
29
127
|
end
|
data/lib/awspec/helper/finder.rb
CHANGED
@@ -166,6 +166,12 @@ module Awspec::Helper
|
|
166
166
|
http_wire_trace: ENV['http_wire_trace'] || false
|
167
167
|
}
|
168
168
|
|
169
|
+
check_configuration = ENV['DISABLE_AWS_CLIENT_CHECK'] != 'true' if ENV.key?('DISABLE_AWS_CLIENT_CHECK')
|
170
|
+
|
171
|
+
# define_method below will "hide" any exception that comes from bad
|
172
|
+
# setup of AWS client, so let's try first to create a instance
|
173
|
+
Awsecrets.load if check_configuration
|
174
|
+
|
169
175
|
CLIENTS.each do |method_name, client|
|
170
176
|
define_method method_name do
|
171
177
|
unless self.methods.include? "@#{method_name}"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Awspec::Helper
|
2
|
+
module States
|
3
|
+
EC2_STATES = %w(pending running shutting-down terminated stopping stopped)
|
4
|
+
|
5
|
+
def self.ec2_states_checks
|
6
|
+
Enumerator.new do |yielder|
|
7
|
+
n = 0
|
8
|
+
while n < EC2_STATES.size
|
9
|
+
method_name = EC2_STATES[n].tr('-', '_') + '?'
|
10
|
+
yielder.yield(method_name, EC2_STATES[n])
|
11
|
+
n += 1
|
12
|
+
end
|
13
|
+
end.lazy
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
RSpec::Matchers.define :belong_to_subnets do |*subnets|
|
2
|
+
match do |nodegroup|
|
3
|
+
superset = Set.new(subnets)
|
4
|
+
nodegroup.subnets.subset?(superset)
|
5
|
+
end
|
6
|
+
failure_message { |nodegroup| super() + failure_reason(nodegroup) }
|
7
|
+
failure_message_when_negated { |nodegroup| super() + failure_reason(nodegroup) }
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def failure_reason(nodegroup)
|
13
|
+
", but the nodes are allocated to the subnets #{nodegroup.subnets.to_a}"
|
14
|
+
end
|
data/lib/awspec/matcher.rb
CHANGED
@@ -4,6 +4,9 @@ require 'awspec/matcher/belong_to_subnet'
|
|
4
4
|
require 'awspec/matcher/have_tag'
|
5
5
|
require 'awspec/matcher/have_network_interface'
|
6
6
|
|
7
|
+
# EKS
|
8
|
+
require 'awspec/matcher/belong_to_subnets'
|
9
|
+
|
7
10
|
# RDS
|
8
11
|
require 'awspec/matcher/belong_to_db_subnet_group'
|
9
12
|
require 'awspec/matcher/have_db_parameter_group'
|
@@ -53,6 +56,7 @@ require 'awspec/matcher/have_rule'
|
|
53
56
|
|
54
57
|
# CloudWatch Logs
|
55
58
|
require 'awspec/matcher/have_subscription_filter'
|
59
|
+
require 'awspec/matcher/have_metric_filter'
|
56
60
|
|
57
61
|
# DynamoDB
|
58
62
|
require 'awspec/matcher/have_attribute_definition'
|
data/lib/awspec/setup.rb
CHANGED
@@ -9,8 +9,68 @@ Aws.config[:eks] = {
|
|
9
9
|
nodegroup_arn: 'arn:aws:eks:us-west-2:012345678910:nodegroup/my-cluster/my-nodegroup/08bd000a',
|
10
10
|
created_at: Time.parse('2018-10-28 00:23:32 -0400'),
|
11
11
|
node_role: 'arn:aws:iam::012345678910:role/eks-nodegroup-role',
|
12
|
-
status: 'ACTIVE'
|
12
|
+
status: 'ACTIVE',
|
13
|
+
scaling_config: { min_size: 1, desired_size: 2, max_size: 3 }
|
13
14
|
}
|
14
15
|
}
|
15
16
|
}
|
16
17
|
}
|
18
|
+
|
19
|
+
Aws.config[:ec2] = {
|
20
|
+
stub_responses: {
|
21
|
+
describe_instances: {
|
22
|
+
reservations: [
|
23
|
+
{
|
24
|
+
instances: [
|
25
|
+
{
|
26
|
+
instance_id: 'i-ec12345a',
|
27
|
+
image_id: 'ami-abc12def',
|
28
|
+
vpc_id: 'vpc-ab123cde',
|
29
|
+
subnet_id: 'subnet-1234a567',
|
30
|
+
public_ip_address: '123.0.456.789',
|
31
|
+
private_ip_address: '10.0.1.1',
|
32
|
+
instance_type: 't2.small',
|
33
|
+
state: {
|
34
|
+
name: 'running'
|
35
|
+
},
|
36
|
+
security_groups: [
|
37
|
+
{
|
38
|
+
group_id: 'sg-1a2b3cd4',
|
39
|
+
group_name: 'my-security-group-name'
|
40
|
+
}
|
41
|
+
],
|
42
|
+
iam_instance_profile: {
|
43
|
+
arn: 'arn:aws:iam::123456789012:instance-profile/Ec2IamProfileName',
|
44
|
+
id: 'ABCDEFGHIJKLMNOPQRSTU'
|
45
|
+
},
|
46
|
+
block_device_mappings: [
|
47
|
+
{
|
48
|
+
device_name: '/dev/sda',
|
49
|
+
ebs: {
|
50
|
+
volume_id: 'vol-123a123b'
|
51
|
+
}
|
52
|
+
}
|
53
|
+
],
|
54
|
+
network_interfaces: [
|
55
|
+
{
|
56
|
+
network_interface_id: 'eni-12ab3cde',
|
57
|
+
subnet_id: 'subnet-1234a567',
|
58
|
+
vpc_id: 'vpc-ab123cde',
|
59
|
+
attachment: {
|
60
|
+
device_index: 1
|
61
|
+
}
|
62
|
+
}
|
63
|
+
],
|
64
|
+
tags: [
|
65
|
+
{
|
66
|
+
key: 'Name',
|
67
|
+
value: 'my-ec2'
|
68
|
+
}
|
69
|
+
]
|
70
|
+
}
|
71
|
+
]
|
72
|
+
}
|
73
|
+
]
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|