awspec 1.24.1 → 1.25.0
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 +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
|
+
}
|