awspec 1.24.2 → 1.25.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.
@@ -1,29 +1,132 @@
1
+ require 'singleton'
2
+
1
3
  module Awspec::Helper
2
4
  module Finder
3
5
  module Subnet
4
- def find_subnet(subnet_id)
5
- res = ec2_client.describe_subnets({
6
- filters: [{ name: 'subnet-id', values: [subnet_id] }]
7
- })
8
- resource = res.subnets.single_resource(subnet_id)
9
- return resource if resource
10
- res = ec2_client.describe_subnets({
11
- filters: [{ name: 'tag:Name', values: [subnet_id] }]
12
- })
13
- resource = res.subnets.single_resource(subnet_id)
14
- return resource if resource
15
- res = ec2_client.describe_subnets({
16
- filters: [{ name: 'cidrBlock', values: [subnet_id] }]
17
- })
18
- res.subnets.single_resource(subnet_id)
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
+ # Check if a IPv4 CIDR (as a symbol) exists in the cache.
53
+ def has_cidr?(cidr_symbol)
54
+ @by_cidr.key?(cidr_symbol)
55
+ end
56
+
57
+ # Return a +Aws::EC2::Types::Subnet+ that matches the given CIDR.
58
+ def subnet_by_cidr(cidr_symbol)
59
+ @subnet_ids[@by_cidr[cidr_symbol]]
60
+ end
61
+
62
+ # Return a +Aws::EC2::Types::Subnet+ that matches the given tag.
63
+ def subnet_by_tag(tag_symbol)
64
+ @subnet_ids[@by_tag_name[tag_symbol]]
65
+ end
66
+
67
+ # Return a +Aws::EC2::Types::Subnet+ that matches the given subnet ID.
68
+ def subnet_by_id(subnet_id_symbol)
69
+ @subnet_ids[subnet_id_symbol]
70
+ end
71
+
72
+ # Check if a given string looks like a IPv4 CIDR.
73
+ def is_cidr?(subnet_id)
74
+ @ip_matcher.match(subnet_id)
75
+ end
76
+
77
+ # Check if the cache was already initialized or not.
78
+ def empty?
79
+ @subnet_ids.empty?
80
+ end
81
+
82
+ # Return the cache as a string.
83
+ def to_s
84
+ "by tag name: #{@by_tag_name}, by CIDR: #{@by_cidr}"
85
+ end
19
86
  end
20
87
 
21
- def select_subnet_by_vpc_id(vpc_id)
22
- res = ec2_client.describe_subnets({
23
- filters: [{ name: 'vpc-id', values: [vpc_id] }]
24
- })
25
- res.subnets
88
+ # Try to locate a +Aws::EC2::Types::Subnet+ with a given subnet ID.
89
+ #
90
+ # A subnet ID might be multiple things, like the
91
+ # +Aws::EC2::Types::Subnet.subnet_id+, or a IPv4 CIDR or the value for the
92
+ # +Name+ tag associated with the subnet.
93
+ #
94
+ # Returns a instance of +Aws::EC2::Types::Subnet+ or +nil+.
95
+ def find_subnet(subnet_id)
96
+ cache = SubnetCache.instance
97
+
98
+ if cache.empty?
99
+ res = ec2_client.describe_subnets
100
+
101
+ res.subnets.each do |sub|
102
+ cache.add_by_cidr(sub.cidr_block, sub.subnet_id)
103
+ cache.add_subnet(sub)
104
+ next if sub.tags.empty?
105
+
106
+ sub.tags.each do |tag|
107
+ if tag[:key].eql?('Name')
108
+ cache.add_by_tag(tag[:value], sub.subnet_id)
109
+ break
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ id_key = subnet_id.to_sym
116
+ return cache.subnet_by_id(id_key) if subnet_id.start_with?('subnet-') && cache.has_subnet?(id_key)
117
+ return cache.subnet_by_cidr(id_key) if cache.is_cidr?(subnet_id) && cache.has_cidr?(id_key)
118
+ cache.subnet_by_tag(id_key)
26
119
  end
27
120
  end
121
+
122
+ # Search for the subnets associated with a given VPC ID.
123
+ #
124
+ # Returns an array of +Aws::EC2::Types::Subnet+ instances.
125
+ def select_subnet_by_vpc_id(vpc_id)
126
+ res = ec2_client.describe_subnets({
127
+ filters: [{ name: 'vpc-id', values: [vpc_id] }]
128
+ })
129
+ res.subnets
130
+ end
28
131
  end
29
132
  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
@@ -0,0 +1,9 @@
1
+ RSpec::Matchers.define :have_metric_filter do |filter_name|
2
+ match do |log_group_name|
3
+ log_group_name.has_metric_filter?(filter_name, @pattern)
4
+ end
5
+
6
+ chain :filter_pattern do |pattern|
7
+ @pattern = pattern
8
+ end
9
+ end
@@ -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
@@ -18,7 +18,7 @@ EOF
18
18
  dir = 'spec'
19
19
  if File.exist? dir
20
20
  unless File.directory? dir
21
- $stderr.puts '!! #{dir} already exists and is not a directory'
21
+ $stderr.puts "!! #{dir} already exists and is not a directory"
22
22
  end
23
23
  else
24
24
  FileUtils.mkdir dir
@@ -18,7 +18,8 @@ Aws.config[:cloudwatchlogs] = {
18
18
  describe_metric_filters: {
19
19
  metric_filters: [
20
20
  {
21
- filter_name: 'my-cloudwatch-logs-metric-filter'
21
+ filter_name: 'my-cloudwatch-logs-metric-filter',
22
+ filter_pattern: '[date, error]'
22
23
  }
23
24
  ]
24
25
  },
@@ -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
+ }
@@ -1,4 +1,3 @@
1
-
2
1
  Aws.config[:elasticsearchservice] = {
3
2
  stub_responses: {
4
3
  list_domain_names: {
@@ -13,6 +13,14 @@ Aws.config[:rds] = {
13
13
  {
14
14
  parameter_name: 'max_allowed_packet',
15
15
  parameter_value: '16777216'
16
+ },
17
+ {
18
+ parameter_name: 'rds.logical_replication',
19
+ parameter_value: '1'
20
+ },
21
+ {
22
+ parameter_name: 'rds.accepted_password_auth_method',
23
+ parameter_value: 'md5+scram'
16
24
  }
17
25
  ]
18
26
  }
@@ -1,26 +1,33 @@
1
+ OWNER = '123456789'
2
+ REGION = 'us-east-1'
3
+ TOPIC_ARN = "arn:aws:sns:#{REGION}:#{OWNER}:foobar"
4
+ DISPLAY_NAME = 'Useless'
5
+ SUBSCRIBED = "arn:aws:sns:#{REGION}:#{OWNER}:Foobar:3dbf4999-b3e2-4345-bd11-c34c9784ecca"
6
+ ENDPOINT = "arn:aws:lambda:#{REGION}:#{OWNER}:function:foobar"
7
+
1
8
  Aws.config[:sns] = {
2
9
  stub_responses: {
3
10
  get_topic_attributes: {
4
11
  attributes: {
5
12
  # rubocop:disable LineLength
6
- 'Policy' => '{\"Version\":\"2008-10-17\",\"Id\":\"__default_policy_ID\",\"Statement\":[{\"Sid\":\"__default_statement_ID\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":[\"SNS:GetTopicAttributes\",\"SNS:SetTopicAttributes\",\"SNS:AddPermission\",\"SNS:RemovePermission\",\"SNS:DeleteTopic\",\"SNS:Subscribe\",\"SNS:ListSubscriptionsByTopic\",\"SNS:Publish\",\"SNS:Receive\"],\"Resource\":\"arn:aws:sns:us-east-1:123456789:foobar-lambda-sample\",\"Condition\":{\"StringEquals\":{\"AWS:SourceOwner\":\"123456789\"}}}]}',
7
- 'Owner' => '123456789',
13
+ 'Policy' => "{\"Version\":\"2008-10-17\",\"Id\":\"__default_policy_ID\",\"Statement\":[{\"Sid\":\"__default_statement_ID\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":[\"SNS:GetTopicAttributes\",\"SNS:SetTopicAttributes\",\"SNS:AddPermission\",\"SNS:RemovePermission\",\"SNS:DeleteTopic\",\"SNS:Subscribe\",\"SNS:ListSubscriptionsByTopic\",\"SNS:Publish\",\"SNS:Receive\"],\"Resource\":\"arn:aws:sns:#{REGION}:#{OWNER}:foobar-lambda-sample\",\"Condition\":{\"StringEquals\":{\"AWS:SourceOwner\":\"#{OWNER}\"}}}]}",
14
+ 'Owner' => OWNER,
8
15
  'SubscriptionsPending' => '0',
9
- 'TopicArn' => 'arn:aws:sns:us-east-1:123456789:foobar',
16
+ 'TopicArn' => TOPIC_ARN,
10
17
  'EffectiveDeliveryPolicy' => '{\"http\":{\"defaultHealthyRetryPolicy\":{\"minDelayTarget\":20,\"maxDelayTarget\":20,\"numRetries\":3,\"numMaxDelayRetries\":0,\"numNoDelayRetries\":0,\"numMinDelayRetries\":0,\"backoffFunction\":\"linear\"},\"disableSubscriptionOverrides\":false}}',
11
18
  'SubscriptionsConfirmed' => '1',
12
- 'DisplayName' => 'Useless',
19
+ 'DisplayName' => DISPLAY_NAME,
13
20
  'SubscriptionsDeleted' => '0'
14
21
  }
15
22
  },
16
23
  list_subscriptions_by_topic: {
17
24
  subscriptions: [
18
25
  {
19
- subscription_arn: 'arn:aws:sns:us-east-1:123456789:Foobar:3dbf4999-b3e2-4345-bd11-c34c9784ecca',
20
- owner: '123456789',
26
+ subscription_arn: SUBSCRIBED,
27
+ owner: OWNER,
21
28
  protocol: 'lambda',
22
- endpoint: 'arn:aws:lambda:us-east-1:123456789:function:foobar',
23
- topic_arn: 'arn:aws:sns:us-east-1:123456789:foobar'
29
+ endpoint: ENDPOINT,
30
+ topic_arn: TOPIC_ARN
24
31
  }
25
32
  ],
26
33
  next_token: nil
@@ -0,0 +1,13 @@
1
+ OWNER = '123456789'
2
+ REGION = 'us-east-1'
3
+ TOPIC_ARN = "arn:aws:sns:#{REGION}:#{OWNER}:invalid"
4
+ TOPIC_SUBS_ARN = "arn:aws:sns:us-east-1:#{OWNER}:Foobar:3dbf4999-b3e2-4345-bd11-c34c9784ecca"
5
+
6
+ Aws.config[:sns] = {
7
+ stub_responses: {
8
+ get_topic_attributes: Aws::SNS::Errors::NotFound.new(
9
+ TOPIC_ARN, 'no such topic'),
10
+ list_subscriptions_by_topic: Aws::SNS::Errors::NotFound.new(
11
+ TOPIC_SUBS_ARN, 'no such topic')
12
+ }
13
+ }
@@ -13,9 +13,14 @@ module Awspec::Type
13
13
  return true if ret == stream_name
14
14
  end
15
15
 
16
- def has_metric_filter?(filter_name)
17
- ret = find_cloudwatch_logs_metric_fileter_by_log_group_name(@id, filter_name).filter_name
18
- return true if ret == filter_name
16
+ def has_metric_filter?(filter_name, pattern = nil)
17
+ ret = find_cloudwatch_logs_metric_fileter_by_log_group_name(@id, filter_name)
18
+ if pattern.nil?
19
+ return true if ret.filter_name == filter_name
20
+ else
21
+ return false unless ret.filter_pattern == pattern
22
+ end
23
+ return true if ret.filter_name == filter_name
19
24
  end
20
25
 
21
26
  def has_subscription_filter?(filter_name, pattern = nil)
@@ -1,10 +1,47 @@
1
+ require 'set'
2
+
1
3
  module Awspec::Type
4
+ class EksNodeEC2
5
+ attr_reader :state, :subnet_id, :sec_groups
6
+
7
+ def initialize(id, state, subnet_id, sec_groups)
8
+ @id = id
9
+ @state = state
10
+ @subnet_id = subnet_id
11
+ @sec_groups = sec_groups
12
+ end
13
+
14
+ def to_s
15
+ "ID: #{@id}, State: #{@state}, Subnet ID: #{@subnet_id}, Security Groups: #{@sec_groups}"
16
+ end
17
+ end
18
+
19
+ class EksNodeSecGroup
20
+ attr_reader :name, :id
21
+
22
+ def initialize(id, name)
23
+ @id = id
24
+ @name = name
25
+ end
26
+
27
+ def to_s
28
+ "ID: #{@id}, Name: #{@name}"
29
+ end
30
+ end
31
+
2
32
  class EksNodegroup < ResourceBase
33
+ # the tags below are standard for EKS node groups instances
34
+ EKS_CLUSTER_TAG = 'tag:eks:cluster-name'
35
+ EKS_NODEGROUP_TAG = 'tag:eks:nodegroup-name'
36
+
3
37
  attr_accessor :cluster
4
38
 
5
39
  def initialize(group_name)
6
40
  super
7
41
  @group_name = group_name
42
+ @ec2_instances = []
43
+ @sec_groups = Set.new
44
+ @sec_groups_ids = Set.new
8
45
  end
9
46
 
10
47
  def resource_via_client
@@ -19,6 +56,39 @@ module Awspec::Type
19
56
  @cluster || 'default'
20
57
  end
21
58
 
59
+ def subnets
60
+ ec2_instances = find_nodes
61
+ Set.new(ec2_instances.map { |ec2| ec2.subnet_id })
62
+ end
63
+
64
+ def has_security_group?(sec_group)
65
+ if @sec_groups.empty? || @sec_groups_ids.empty?
66
+ ec2_instances = find_nodes
67
+
68
+ ec2_instances.each do |ec2|
69
+ ec2.sec_groups.each do |sg|
70
+ @sec_groups.add(sg.name)
71
+ @sec_groups_ids.add(sg.id)
72
+ end
73
+ end
74
+ end
75
+
76
+ @sec_groups.member?(sec_group) || @sec_groups_ids.member?(sec_group)
77
+ end
78
+
79
+ def ready?
80
+ min_expected = resource_via_client.scaling_config.min_size
81
+ ec2_instances = find_nodes
82
+ running_counter = 0
83
+
84
+ ec2_instances.each do |ec2|
85
+ running_counter += 1 if ec2.state.eql?('running')
86
+ break if running_counter == min_expected
87
+ end
88
+
89
+ running_counter >= min_expected
90
+ end
91
+
22
92
  STATES = %w(ACTIVE INACTIVE)
23
93
 
24
94
  STATES.each do |state|
@@ -26,5 +96,40 @@ module Awspec::Type
26
96
  resource_via_client.status == state
27
97
  end
28
98
  end
99
+
100
+ private
101
+
102
+ def find_nodes
103
+ return @ec2_instances unless @ec2_instances.empty?
104
+
105
+ result = ec2_client.describe_instances(
106
+ {
107
+ filters: [
108
+ { name: EKS_CLUSTER_TAG, values: [cluster] },
109
+ { name: EKS_NODEGROUP_TAG, values: [@group_name] }
110
+ ]
111
+ }
112
+ )
113
+ result.reservations.each do |reservation|
114
+ reservation.instances.each do |instance|
115
+ sec_groups = []
116
+
117
+ instance.security_groups.map do |sg|
118
+ sec_groups.push(EksNodeSecGroup.new(sg.group_id, sg.group_name))
119
+ end
120
+
121
+ @ec2_instances.push(
122
+ EksNodeEC2.new(
123
+ instance.instance_id,
124
+ instance.state.name,
125
+ instance.subnet_id,
126
+ sec_groups
127
+ )
128
+ )
129
+ end
130
+ end
131
+
132
+ @ec2_instances
133
+ end
29
134
  end
30
135
  end
@@ -1,4 +1,39 @@
1
1
  module Awspec::Type
2
+ class InvalidRdsDbParameter < StandardError
3
+ ##
4
+ # Overrides the superclass initialize method to include more information
5
+ # and default error message.
6
+ # Expected parameters:
7
+ # - parameter_name: the name of the parameter.
8
+
9
+ def initialize(parameter_name)
10
+ @param_name = parameter_name
11
+ message = "There is no such parameter \"rds.#{parameter_name}\""
12
+ super message
13
+ end
14
+ end
15
+
16
+ class RdsDBParameters
17
+ ##
18
+ # Thanks to AWS for creating parameters names like
19
+ # 'rds.accepted_password_auth_method', which would be caught as method 'rds'
20
+ # by method_missing in RdsDbParameterGroup class, this class was created
21
+ # See https://github.com/k1LoW/awspec/issues/527 for more details
22
+ def initialize(params)
23
+ @params = params
24
+ end
25
+
26
+ def to_s
27
+ return "RdsDBParameters = #{@params}"
28
+ end
29
+
30
+ def method_missing(name)
31
+ param_name = name.to_sym
32
+ return @params[param_name] if @params.include?(param_name)
33
+ raise InvalidRdsDbParameter, name
34
+ end
35
+ end
36
+
2
37
  class RdsDbParameterGroup < ResourceBase
3
38
  def resource_via_client
4
39
  return @resource_via_client if @resource_via_client
@@ -11,11 +46,30 @@ module Awspec::Type
11
46
 
12
47
  def method_missing(name)
13
48
  param_name = name.to_s
49
+ return create_rds_params if param_name == 'rds'
50
+
14
51
  if resource_via_client.include?(param_name)
15
52
  resource_via_client[param_name].to_s
16
53
  else
17
54
  super
18
55
  end
19
56
  end
57
+
58
+ private
59
+
60
+ def create_rds_params
61
+ return @rds_params if @rds_params
62
+
63
+ rds_params_keys = resource_via_client.keys.select { |key| key.to_s.start_with?('rds.') }
64
+ rds_params = {}
65
+
66
+ rds_params_keys.each do |key|
67
+ new_key = key.split('.')[-1]
68
+ rds_params[new_key.to_sym] = resource_via_client[key]
69
+ end
70
+
71
+ @rds_params = RdsDBParameters.new(rds_params)
72
+ @rds_params
73
+ end
20
74
  end
21
75
  end
@@ -108,8 +108,8 @@ module Awspec::Type
108
108
  if value.is_a?(Array)
109
109
  return false if r[key].map(&:to_h) != value
110
110
  end
111
- true
112
111
  end
112
+ true
113
113
  end
114
114
  end
115
115
 
@@ -1,3 +1,3 @@
1
1
  module Awspec
2
- VERSION = '1.24.2'
2
+ VERSION = '1.25.1'
3
3
  end