ami_spec 1.2.0 → 1.4.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/README.md +6 -3
- data/lib/ami_spec.rb +41 -8
- data/lib/ami_spec/aws_instance.rb +6 -1
- data/lib/ami_spec/aws_key_pair.rb +36 -0
- data/lib/ami_spec/aws_security_group.rb +72 -0
- data/lib/ami_spec/server_spec.rb +1 -1
- data/lib/ami_spec/version.rb +1 -1
- data/lib/ami_spec/wait_for_rc.rb +1 -1
- data/lib/ami_spec/wait_for_ssh.rb +1 -1
- metadata +6 -39
- data/.gitignore +0 -2
- data/.ruby-version +0 -1
- data/.travis.yml +0 -7
- data/Gemfile +0 -3
- data/Rakefile +0 -8
- data/ami_spec.gemspec +0 -29
- data/spec/ami_spec_spec.rb +0 -74
- data/spec/aws_instance_spec.rb +0 -142
- data/spec/containers/Dockerfile.amazon_linux +0 -9
- data/spec/containers/Dockerfile.trusty +0 -8
- data/spec/containers/Dockerfile.xenial +0 -22
- data/spec/containers/README.md +0 -5
- data/spec/containers/ami-spec +0 -27
- data/spec/containers/ami-spec.pub +0 -1
- data/spec/containers/docker-compose.yml +0 -28
- data/spec/containers/rc.conf +0 -17
- data/spec/containers/sshd_config +0 -17
- data/spec/spec_helper.rb +0 -2
- data/spec/wait_for_rc_spec.rb +0 -25
- data/spec/wait_for_ssh_spec.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87eebf7f488963ef90dec3e34678f6c75bd36eaf896467edaeb1651271ad8d8a
|
4
|
+
data.tar.gz: 6425586e1732b2e78cb4099047f1ba6e5e99d08deb0dbd5aa4031daa24beb597
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93dd91143f960c39da8cfdf24522a9daf271d4aaf3a69db345d4621e40c9a10ca43158abe19a0e2268cca9bbc699267fe9c54b954275d5e7bccb404eb2e487f7
|
7
|
+
data.tar.gz: 7e1a7762ed90bfd5266ab9a869fe1a78af16e065fe8d5c8ac0663376195e9029d7b23c8d8d7ab818cba7fdf5d2ee821eab723b8b176459880685851e4a055ebe
|
data/README.md
CHANGED
@@ -36,21 +36,24 @@ Options:
|
|
36
36
|
web_server,ami-id.
|
37
37
|
-s, --specs=<s> The directory to find ServerSpecs
|
38
38
|
-u, --subnet-id=<s> The subnet to start the instance in
|
39
|
-
-k, --key-name=<s> The SSH key name to assign to instances
|
39
|
+
-k, --key-name=<s> The SSH key name to assign to instances. If not provided a
|
40
|
+
temporary key pair will be generated in AWS
|
40
41
|
-e, --key-file=<s> The SSH private key file associated to the key_name
|
41
42
|
-h, --ssh-user=<s> The user to ssh to the instance as
|
42
43
|
-w, --aws-region=<s> The AWS region, defaults to AWS_DEFAULT_REGION environment
|
43
44
|
variable
|
44
45
|
-i, --aws-instance-type=<s> The ec2 instance type, defaults to t2.micro (default:
|
45
46
|
t2.micro)
|
46
|
-
-c, --aws-security-groups=<s
|
47
|
-
specified multiple times
|
47
|
+
-c, --aws-security-groups=<s> Security groups to associate to the launched instances. May be
|
48
|
+
specified multiple times. If not provided a temporary security
|
49
|
+
group will be generated in AWS
|
48
50
|
-p, --aws-public-ip Launch instances with a public IP
|
49
51
|
-t, --ssh-retries=<i> The number of times we should try sshing to the ec2 instance
|
50
52
|
before giving up. Defaults to 30 (default: 30)
|
51
53
|
-g, --tags=<s> Additional tags to add to launched instances in the form of
|
52
54
|
comma separated key=value pairs. i.e. Name=AmiSpec (default: )
|
53
55
|
-d, --debug Don't terminate instances on exit
|
56
|
+
-b, --buildkite Output section separators for buildkite
|
54
57
|
-f, --wait-for-rc Wait for oldschool SystemV scripts to run before conducting
|
55
58
|
tests. Currently only supports Ubuntu with upstart
|
56
59
|
-l, --user-data-file=<s> File path for aws ec2 user data
|
data/lib/ami_spec.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'ami_spec/aws_instance'
|
2
2
|
require 'ami_spec/aws_instance_options'
|
3
|
+
require 'ami_spec/aws_key_pair'
|
4
|
+
require 'ami_spec/aws_security_group'
|
3
5
|
require 'ami_spec/server_spec'
|
4
6
|
require 'ami_spec/server_spec_options'
|
5
7
|
require 'ami_spec/wait_for_ssh'
|
6
8
|
require 'ami_spec/wait_for_rc'
|
7
9
|
require 'optimist'
|
10
|
+
require 'logger'
|
8
11
|
|
9
12
|
module AmiSpec
|
10
13
|
class InstanceConnectionTimeout < StandardError; end
|
@@ -19,7 +22,7 @@ module AmiSpec
|
|
19
22
|
# subnet_id::
|
20
23
|
# The subnet_id to start instances in.
|
21
24
|
# key_name::
|
22
|
-
# The SSH key name to assign to instances.
|
25
|
+
# The SSH key name to assign to instances. If not provided a temporary key pair will be generated in AWS
|
23
26
|
# key_file::
|
24
27
|
# The SSH key file to use to connect to the host.
|
25
28
|
# aws_region::
|
@@ -27,7 +30,9 @@ module AmiSpec
|
|
27
30
|
# Defaults to AWS_DEFAULT_REGION
|
28
31
|
# aws_security_group_ids::
|
29
32
|
# AWS Security groups to assign to the instances
|
30
|
-
#
|
33
|
+
# If not provided a temporary security group will be generated in AWS
|
34
|
+
# allow_any_temporary_security_group::
|
35
|
+
# The temporary security group will allow SSH connections from any IP address (0.0.0.0/0)
|
31
36
|
# aws_instance_type::
|
32
37
|
# AWS ec2 instance type
|
33
38
|
# aws_public_ip::
|
@@ -45,6 +50,26 @@ module AmiSpec
|
|
45
50
|
# == Returns:
|
46
51
|
# Boolean - The result of all the server specs.
|
47
52
|
def self.run(options)
|
53
|
+
logger = Logger.new(STDOUT, formatter: proc { |_sev, _time, _name, message| "#{message}\n" })
|
54
|
+
|
55
|
+
ec2 = Aws::EC2::Resource.new(options[:aws_region] ? {region: options[:aws_region]} : {})
|
56
|
+
|
57
|
+
unless options[:key_name]
|
58
|
+
key_pair = AwsKeyPair.create(ec2: ec2, logger: logger)
|
59
|
+
options[:key_name] = key_pair.key_name
|
60
|
+
options[:key_file] = key_pair.key_file
|
61
|
+
end
|
62
|
+
|
63
|
+
if options[:aws_security_groups].nil? || options[:aws_security_groups].empty?
|
64
|
+
temporary_security_group = AwsSecurityGroup.create(
|
65
|
+
ec2: ec2,
|
66
|
+
subnet_id: options[:subnet_id],
|
67
|
+
allow_any_ip: options[:allow_any_temporary_security_group],
|
68
|
+
logger: logger
|
69
|
+
)
|
70
|
+
options[:aws_security_groups] = [temporary_security_group.group_id]
|
71
|
+
end
|
72
|
+
|
48
73
|
instances = []
|
49
74
|
options[:amis].each_pair do |role, ami|
|
50
75
|
aws_instance_options = AwsInstanceOptions.new(options.merge(role: role, ami: ami))
|
@@ -64,6 +89,8 @@ module AmiSpec
|
|
64
89
|
results.all?
|
65
90
|
ensure
|
66
91
|
stop_instances(instances, options[:debug])
|
92
|
+
key_pair.delete if key_pair
|
93
|
+
temporary_security_group.delete if temporary_security_group
|
67
94
|
end
|
68
95
|
|
69
96
|
def self.stop_instances(instances, debug)
|
@@ -90,13 +117,15 @@ module AmiSpec
|
|
90
117
|
type: :string
|
91
118
|
opt :specs, "The directory to find ServerSpecs", type: :string, required: true
|
92
119
|
opt :subnet_id, "The subnet to start the instance in", type: :string, required: true
|
93
|
-
opt :key_name, "The SSH key name to assign to instances
|
94
|
-
|
120
|
+
opt :key_name, "The SSH key name to assign to instances. If not provided a temporary key pair will be generated in AWS",
|
121
|
+
type: :string
|
122
|
+
opt :key_file, "The SSH private key file associated to the key_name", type: :string
|
95
123
|
opt :ssh_user, "The user to ssh to the instance as", type: :string, required: true
|
96
124
|
opt :aws_region, "The AWS region, defaults to AWS_DEFAULT_REGION environment variable", type: :string
|
97
125
|
opt :aws_instance_type, "The ec2 instance type, defaults to t2.micro", type: :string, default: 't2.micro'
|
98
|
-
opt :aws_security_groups, "Security groups to associate to the launched instances. May be specified multiple times",
|
126
|
+
opt :aws_security_groups, "Security groups to associate to the launched instances. May be specified multiple times. If not provided a temporary security group will be generated in AWS",
|
99
127
|
type: :string, default: nil, multi: true
|
128
|
+
opt :allow_any_temporary_security_group, "The temporary security group will allow SSH connections from any IP address (0.0.0.0/0)"
|
100
129
|
opt :aws_public_ip, "Launch instances with a public IP"
|
101
130
|
opt :ssh_retries, "The number of times we should try sshing to the ec2 instance before giving up. Defaults to 30",
|
102
131
|
type: :int, default: 30
|
@@ -121,9 +150,7 @@ module AmiSpec
|
|
121
150
|
fail "You must specify either role and ami or role_ami_file"
|
122
151
|
end
|
123
152
|
|
124
|
-
|
125
|
-
fail "Key file #{options[:key_file]} not found"
|
126
|
-
end
|
153
|
+
fail "Key file #{options[:key_file]} not found" if options[:key_name] && !File.exist?(options.fetch(:key_file))
|
127
154
|
|
128
155
|
if options[:user_data_file] and !File.exist? options[:user_data_file]
|
129
156
|
fail "User Data file #{options[:user_data_file]} not found"
|
@@ -131,6 +158,12 @@ module AmiSpec
|
|
131
158
|
|
132
159
|
options[:tags] = parse_tags(options[:tags])
|
133
160
|
|
161
|
+
options[:amis].each_pair do |role, _|
|
162
|
+
unless Dir.exist?("#{options[:specs]}/#{role}")
|
163
|
+
fail "Role directory #{options[:specs]}/#{role} does not exist. If you'd like to skip the role '#{role}', create the directory but leave it empty (except for a .gitignore file)."
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
134
167
|
exit run(options)
|
135
168
|
end
|
136
169
|
|
@@ -26,7 +26,7 @@ module AmiSpec
|
|
26
26
|
@iam_instance_profile_arn = options.fetch(:iam_instance_profile_arn, nil)
|
27
27
|
end
|
28
28
|
|
29
|
-
def_delegators :@instance, :instance_id, :tags, :
|
29
|
+
def_delegators :@instance, :instance_id, :tags, :private_ip_address, :public_ip_address
|
30
30
|
|
31
31
|
def start
|
32
32
|
client = Aws::EC2::Client.new(client_options)
|
@@ -37,6 +37,11 @@ module AmiSpec
|
|
37
37
|
tag_instance
|
38
38
|
end
|
39
39
|
|
40
|
+
def terminate
|
41
|
+
@instance.terminate
|
42
|
+
@instance.wait_until_terminated
|
43
|
+
end
|
44
|
+
|
40
45
|
private
|
41
46
|
|
42
47
|
def client_options
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'aws-sdk-ec2'
|
2
|
+
require 'logger'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
module AmiSpec
|
8
|
+
class AwsKeyPair
|
9
|
+
|
10
|
+
def self.create(**args)
|
11
|
+
new(**args).tap(&:create)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(ec2: Aws::EC2::Resource.new, key_name_prefix: 'ami-spec-', logger: Logger.new(STDOUT))
|
15
|
+
@ec2 = ec2
|
16
|
+
@key_name = "#{key_name_prefix}#{SecureRandom.uuid}"
|
17
|
+
@logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :key_name, :key_file
|
21
|
+
|
22
|
+
def create
|
23
|
+
@logger.info "Creating temporary AWS key pair: #{@key_name}"
|
24
|
+
@key_pair = @ec2.create_key_pair(key_name: @key_name)
|
25
|
+
@temp_file = Tempfile.new('key')
|
26
|
+
@temp_file.write(@key_pair.key_material)
|
27
|
+
@temp_file.close
|
28
|
+
@key_file = Pathname.new(@temp_file.path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete
|
32
|
+
@logger.info "Deleting temporary AWS key pair: #{@key_name}"
|
33
|
+
@key_pair.delete
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'aws-sdk-ec2'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module AmiSpec
|
6
|
+
class AwsSecurityGroup
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def self.create(**args)
|
10
|
+
new(**args).tap(&:create)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(ec2: Aws::EC2::Resource.new,
|
14
|
+
group_name_prefix: "ami-spec-",
|
15
|
+
connection_port: 22,
|
16
|
+
subnet_id:,
|
17
|
+
allow_any_ip: false,
|
18
|
+
logger: Logger.new(STDOUT))
|
19
|
+
@ec2 = ec2
|
20
|
+
@group_name = "#{group_name_prefix}#{SecureRandom.uuid}"
|
21
|
+
@connection_port = connection_port
|
22
|
+
@subnet_id = subnet_id
|
23
|
+
@allow_any_ip = allow_any_ip
|
24
|
+
@logger = logger
|
25
|
+
end
|
26
|
+
|
27
|
+
def_delegators :@security_group, :group_id
|
28
|
+
attr_reader :group_name
|
29
|
+
|
30
|
+
def create
|
31
|
+
@logger.info "Creating temporary AWS security group: #{@group_name}"
|
32
|
+
create_security_group
|
33
|
+
allow_ingress_via_connection_port
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete
|
37
|
+
@logger.info "Deleting temporary AWS security group: #{@group_name}"
|
38
|
+
@security_group.delete
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def create_security_group
|
44
|
+
@security_group = @ec2.create_security_group(
|
45
|
+
group_name: @group_name,
|
46
|
+
description: "A temporary security group for running AmiSpec",
|
47
|
+
vpc_id: subnet.vpc_id,
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def allow_ingress_via_connection_port
|
52
|
+
@security_group.authorize_ingress(
|
53
|
+
ip_permissions: [
|
54
|
+
{
|
55
|
+
ip_protocol: "tcp",
|
56
|
+
from_port: @connection_port,
|
57
|
+
to_port: @connection_port,
|
58
|
+
ip_ranges: [{cidr_ip: cidr_block}],
|
59
|
+
},
|
60
|
+
],
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def cidr_block
|
65
|
+
@allow_any_ip ? "0.0.0.0/0" : subnet.cidr_block
|
66
|
+
end
|
67
|
+
|
68
|
+
def subnet
|
69
|
+
@subnet ||= @ec2.subnet(@subnet_id)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/ami_spec/server_spec.rb
CHANGED
@@ -36,7 +36,7 @@ module AmiSpec
|
|
36
36
|
|
37
37
|
set :backend, :ssh
|
38
38
|
set :host, @ip
|
39
|
-
set :ssh_options, :user => @user, :keys => [@key_file], :
|
39
|
+
set :ssh_options, :user => @user, :keys => [@key_file], :verify_host_key => :never
|
40
40
|
|
41
41
|
RSpec.configuration.fail_fast = true if @debug
|
42
42
|
|
data/lib/ami_spec/version.rb
CHANGED
data/lib/ami_spec/wait_for_rc.rb
CHANGED
@@ -3,7 +3,7 @@ require 'net/ssh'
|
|
3
3
|
module AmiSpec
|
4
4
|
class WaitForRC
|
5
5
|
def self.wait(ip_address, user, key, port=22)
|
6
|
-
Net::SSH.start(ip_address, user, keys: [key],
|
6
|
+
Net::SSH.start(ip_address, user, keys: [key], :verify_host_key => :never, port: port) do |ssh|
|
7
7
|
distrib_stdout = ""
|
8
8
|
# Determine the OS family
|
9
9
|
ssh.exec!("source /etc/*release && echo -n $DISTRIB_ID && echo -n $ID") do |channel, stream, data|
|
@@ -8,7 +8,7 @@ module AmiSpec
|
|
8
8
|
|
9
9
|
while retries < max_retries
|
10
10
|
begin
|
11
|
-
Net::SSH.start(ip_address, user, keys: [key],
|
11
|
+
Net::SSH.start(ip_address, user, keys: [key], :verify_host_key => :never) { |ssh| ssh.exec 'echo boo!' }
|
12
12
|
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, Timeout::Error => error
|
13
13
|
last_error = error
|
14
14
|
sleep 1
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ami_spec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick Robinson
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-
|
12
|
+
date: 2019-07-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk-ec2
|
@@ -116,37 +116,19 @@ executables:
|
|
116
116
|
extensions: []
|
117
117
|
extra_rdoc_files: []
|
118
118
|
files:
|
119
|
-
- ".gitignore"
|
120
|
-
- ".ruby-version"
|
121
|
-
- ".travis.yml"
|
122
|
-
- Gemfile
|
123
119
|
- LICENSE.txt
|
124
120
|
- README.md
|
125
|
-
- Rakefile
|
126
|
-
- ami_spec.gemspec
|
127
121
|
- bin/ami_spec
|
128
122
|
- lib/ami_spec.rb
|
129
123
|
- lib/ami_spec/aws_instance.rb
|
130
124
|
- lib/ami_spec/aws_instance_options.rb
|
125
|
+
- lib/ami_spec/aws_key_pair.rb
|
126
|
+
- lib/ami_spec/aws_security_group.rb
|
131
127
|
- lib/ami_spec/server_spec.rb
|
132
128
|
- lib/ami_spec/server_spec_options.rb
|
133
129
|
- lib/ami_spec/version.rb
|
134
130
|
- lib/ami_spec/wait_for_rc.rb
|
135
131
|
- lib/ami_spec/wait_for_ssh.rb
|
136
|
-
- spec/ami_spec_spec.rb
|
137
|
-
- spec/aws_instance_spec.rb
|
138
|
-
- spec/containers/Dockerfile.amazon_linux
|
139
|
-
- spec/containers/Dockerfile.trusty
|
140
|
-
- spec/containers/Dockerfile.xenial
|
141
|
-
- spec/containers/README.md
|
142
|
-
- spec/containers/ami-spec
|
143
|
-
- spec/containers/ami-spec.pub
|
144
|
-
- spec/containers/docker-compose.yml
|
145
|
-
- spec/containers/rc.conf
|
146
|
-
- spec/containers/sshd_config
|
147
|
-
- spec/spec_helper.rb
|
148
|
-
- spec/wait_for_rc_spec.rb
|
149
|
-
- spec/wait_for_ssh_spec.rb
|
150
132
|
homepage: https://github.com/envato/ami-spec
|
151
133
|
licenses: []
|
152
134
|
metadata: {}
|
@@ -165,23 +147,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
147
|
- !ruby/object:Gem::Version
|
166
148
|
version: '0'
|
167
149
|
requirements: []
|
168
|
-
|
169
|
-
rubygems_version: 2.7.6
|
150
|
+
rubygems_version: 3.0.4
|
170
151
|
signing_key:
|
171
152
|
specification_version: 4
|
172
153
|
summary: Acceptance testing your AMIs
|
173
|
-
test_files:
|
174
|
-
- spec/ami_spec_spec.rb
|
175
|
-
- spec/aws_instance_spec.rb
|
176
|
-
- spec/containers/Dockerfile.amazon_linux
|
177
|
-
- spec/containers/Dockerfile.trusty
|
178
|
-
- spec/containers/Dockerfile.xenial
|
179
|
-
- spec/containers/README.md
|
180
|
-
- spec/containers/ami-spec
|
181
|
-
- spec/containers/ami-spec.pub
|
182
|
-
- spec/containers/docker-compose.yml
|
183
|
-
- spec/containers/rc.conf
|
184
|
-
- spec/containers/sshd_config
|
185
|
-
- spec/spec_helper.rb
|
186
|
-
- spec/wait_for_rc_spec.rb
|
187
|
-
- spec/wait_for_ssh_spec.rb
|
154
|
+
test_files: []
|
data/.gitignore
DELETED
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2.5.1
|
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
data/ami_spec.gemspec
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
lib = File.expand_path('../lib', __FILE__)
|
2
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
|
4
|
-
require 'ami_spec/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |gem|
|
7
|
-
gem.name = 'ami_spec'
|
8
|
-
gem.version = AmiSpec::VERSION
|
9
|
-
gem.authors = ['Patrick Robinson', 'Martin Jagusch']
|
10
|
-
gem.email = []
|
11
|
-
gem.description = 'Acceptance testing your AMIs'
|
12
|
-
gem.summary = gem.description
|
13
|
-
gem.homepage = 'https://github.com/envato/ami-spec'
|
14
|
-
|
15
|
-
gem.files = `git ls-files`.split($/)
|
16
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
-
gem.require_paths = ['lib']
|
19
|
-
|
20
|
-
gem.add_dependency 'aws-sdk-ec2', '~> 1'
|
21
|
-
gem.add_development_dependency 'rake'
|
22
|
-
gem.add_dependency 'serverspec', '~> 2'
|
23
|
-
gem.add_dependency 'specinfra', '>= 2.45'
|
24
|
-
gem.add_dependency 'optimist', '~> 3'
|
25
|
-
gem.add_dependency 'hashie'
|
26
|
-
gem.add_dependency 'net-ssh', '~> 5'
|
27
|
-
|
28
|
-
gem.required_ruby_version = '>= 2.2.6'
|
29
|
-
end
|
data/spec/ami_spec_spec.rb
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe AmiSpec do
|
4
|
-
let(:amis) { {'web_server' => 'ami-1234abcd', 'db_server' => 'ami-1234abcd'} }
|
5
|
-
let(:ec2_double) { instance_double(AmiSpec::AwsInstance) }
|
6
|
-
let(:state) { double(name: 'running') }
|
7
|
-
let(:test_result) { true }
|
8
|
-
let(:server_spec_double) { double(run: test_result) }
|
9
|
-
subject do
|
10
|
-
described_class.run(
|
11
|
-
amis: amis,
|
12
|
-
specs: '/tmp/foobar',
|
13
|
-
subnet_id: 'subnet-1234abcd',
|
14
|
-
key_name: 'key',
|
15
|
-
key_file: 'key.pem',
|
16
|
-
aws_public_ip: false,
|
17
|
-
aws_instance_type: 't2.micro',
|
18
|
-
ssh_user: 'ubuntu',
|
19
|
-
debug: false,
|
20
|
-
ssh_retries: 30,
|
21
|
-
)
|
22
|
-
end
|
23
|
-
|
24
|
-
describe '#invoke' do
|
25
|
-
it 'raises a system exit with no arguments' do
|
26
|
-
expect{ described_class.invoke }.to raise_error(SystemExit)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
describe '#run' do
|
31
|
-
before do
|
32
|
-
allow(AmiSpec::WaitForSSH).to receive(:wait).and_return(true)
|
33
|
-
allow(AmiSpec::AwsInstance).to receive(:start).and_return(ec2_double)
|
34
|
-
allow(AmiSpec::ServerSpec).to receive(:new).and_return(server_spec_double)
|
35
|
-
allow(ec2_double).to receive(:terminate).and_return(true)
|
36
|
-
allow(ec2_double).to receive(:private_ip_address).and_return('127.0.0.1')
|
37
|
-
allow_any_instance_of(Object).to receive(:sleep)
|
38
|
-
end
|
39
|
-
|
40
|
-
context 'successful tests' do
|
41
|
-
it 'calls aws instance for each ami' do
|
42
|
-
expect(AmiSpec::AwsInstance).to receive(:start).with(hash_including(role: 'web_server'))
|
43
|
-
expect(AmiSpec::AwsInstance).to receive(:start).with(hash_including(role: 'db_server'))
|
44
|
-
subject
|
45
|
-
end
|
46
|
-
|
47
|
-
it 'returns true' do
|
48
|
-
expect(subject).to be_truthy
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
context 'failed tests' do
|
53
|
-
let(:test_result) { false }
|
54
|
-
|
55
|
-
it 'returns false' do
|
56
|
-
expect(subject).to be_falsey
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
describe '#parse_tags' do
|
62
|
-
it 'parses a single key/value pair' do
|
63
|
-
expect(described_class.parse_tags("Name=AmiSpec")).to eq( { "Name"=>"AmiSpec" } )
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'parses multiple key/value pairs' do
|
67
|
-
expect(described_class.parse_tags("Name=AmiSpec,Owner=Me")).to eq( { "Name"=>"AmiSpec", "Owner"=>"Me" } )
|
68
|
-
end
|
69
|
-
|
70
|
-
it 'parses an empty string' do
|
71
|
-
expect(described_class.parse_tags("")).to eq({})
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
data/spec/aws_instance_spec.rb
DELETED
@@ -1,142 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'base64'
|
3
|
-
require 'tempfile'
|
4
|
-
|
5
|
-
describe AmiSpec::AwsInstance do
|
6
|
-
let(:role) { 'web_server' }
|
7
|
-
let(:sec_group_id) { nil }
|
8
|
-
let(:region) { nil }
|
9
|
-
let(:client_double) { instance_double(Aws::EC2::Client) }
|
10
|
-
let(:new_ec2_double) { instance_double(Aws::EC2::Types::Instance) }
|
11
|
-
let(:ec2_double) { instance_double(Aws::EC2::Instance) }
|
12
|
-
let(:tags) { {} }
|
13
|
-
let(:iam_instance_profile_arn) { nil }
|
14
|
-
let(:user_data_file) { nil }
|
15
|
-
|
16
|
-
subject(:aws_instance) do
|
17
|
-
described_class.new(
|
18
|
-
role: role,
|
19
|
-
ami: 'ami',
|
20
|
-
subnet_id: 'subnet',
|
21
|
-
key_name: 'key',
|
22
|
-
aws_instance_type: 't2.micro',
|
23
|
-
aws_public_ip: false,
|
24
|
-
aws_security_groups: sec_group_id,
|
25
|
-
aws_region: region,
|
26
|
-
tags: tags,
|
27
|
-
user_data_file: user_data_file,
|
28
|
-
iam_instance_profile_arn: iam_instance_profile_arn
|
29
|
-
)
|
30
|
-
end
|
31
|
-
|
32
|
-
before do
|
33
|
-
allow(Aws::EC2::Client).to receive(:new).and_return(client_double)
|
34
|
-
allow(client_double).to receive(:run_instances).and_return(double(instances: [new_ec2_double]))
|
35
|
-
allow(ec2_double).to receive(:create_tags).and_return(double)
|
36
|
-
allow(Aws::EC2::Instance).to receive(:new).and_return(ec2_double)
|
37
|
-
allow(new_ec2_double).to receive(:instance_id)
|
38
|
-
allow(ec2_double).to receive(:instance_id)
|
39
|
-
allow(ec2_double).to receive(:wait_until_running)
|
40
|
-
end
|
41
|
-
|
42
|
-
describe '#start' do
|
43
|
-
subject(:start) { aws_instance.start }
|
44
|
-
context 'without optional values' do
|
45
|
-
it 'does not include the security group' do
|
46
|
-
expect(client_double).to receive(:run_instances).with(
|
47
|
-
hash_excluding(:network_interfaces=>array_including(hash_including(:groups)))
|
48
|
-
)
|
49
|
-
start
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'does include the region' do
|
53
|
-
expect(Aws::EC2::Client).to receive(:new).with(
|
54
|
-
hash_excluding(:region => region)
|
55
|
-
)
|
56
|
-
start
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
context 'with security group' do
|
61
|
-
let(:sec_group_id) { ['1234'] }
|
62
|
-
|
63
|
-
it 'does include security groups' do
|
64
|
-
expect(client_double).to receive(:run_instances).with(
|
65
|
-
hash_including(:network_interfaces=>array_including(hash_including(:groups)))
|
66
|
-
)
|
67
|
-
start
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
context 'with region' do
|
72
|
-
let(:region) { 'us-east-1' }
|
73
|
-
|
74
|
-
it 'does include the region in the intial connection' do
|
75
|
-
expect(Aws::EC2::Client).to receive(:new).with(
|
76
|
-
hash_including(:region => region)
|
77
|
-
)
|
78
|
-
start
|
79
|
-
end
|
80
|
-
|
81
|
-
it 'does include the region in the subsequent connection' do
|
82
|
-
expect(Aws::EC2::Instance).to receive(:new).with(
|
83
|
-
anything,
|
84
|
-
hash_including(:region => region)
|
85
|
-
)
|
86
|
-
start
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
context 'with tags' do
|
91
|
-
let(:tags) { {"Name" => "AmiSpec"} }
|
92
|
-
|
93
|
-
it 'tags the instance' do
|
94
|
-
expect(ec2_double).to receive(:create_tags).with(
|
95
|
-
{tags: [{ key: 'AmiSpec', value: role}, { key: "Name", value: "AmiSpec"}]}
|
96
|
-
)
|
97
|
-
start
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
context 'with user_data' do
|
102
|
-
let(:user_data_file) {
|
103
|
-
file = Tempfile.new('user_data.txt')
|
104
|
-
file.write("my file\ncontent")
|
105
|
-
file.close
|
106
|
-
file.path
|
107
|
-
}
|
108
|
-
|
109
|
-
it 'does include user_data' do
|
110
|
-
expect(client_double).to receive(:run_instances).with(
|
111
|
-
hash_including(:user_data => Base64.encode64("my file\ncontent"))
|
112
|
-
)
|
113
|
-
start
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
context 'with iam_instance_profile_arn' do
|
118
|
-
let(:iam_instance_profile_arn) { "my_arn" }
|
119
|
-
|
120
|
-
it 'does include iam_instance_profile_arn' do
|
121
|
-
expect(client_double).to receive(:run_instances).with(
|
122
|
-
hash_including(:iam_instance_profile => { arn: 'my_arn'})
|
123
|
-
)
|
124
|
-
start
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
it 'tags the instance with a role' do
|
129
|
-
expect(ec2_double).to receive(:create_tags).with(
|
130
|
-
hash_including(tags: [{ key: 'AmiSpec', value: role}])
|
131
|
-
)
|
132
|
-
start
|
133
|
-
end
|
134
|
-
|
135
|
-
it 'delegates some methods to the instance variable' do
|
136
|
-
expect(ec2_double).to receive(:instance_id)
|
137
|
-
start
|
138
|
-
aws_instance.instance_id
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
FROM ubuntu:xenial
|
2
|
-
|
3
|
-
RUN cd /lib/systemd/system/sysinit.target.wants/; ls | grep -v systemd-tmpfiles-setup | xargs rm -f $1 \
|
4
|
-
rm -f /lib/systemd/system/multi-user.target.wants/*;\
|
5
|
-
rm -f /etc/systemd/system/*.wants/*;\
|
6
|
-
rm -f /lib/systemd/system/local-fs.target.wants/*; \
|
7
|
-
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
|
8
|
-
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
|
9
|
-
rm -f /lib/systemd/system/basic.target.wants/*;\
|
10
|
-
rm -f /lib/systemd/system/anaconda.target.wants/*; \
|
11
|
-
rm -f /lib/systemd/system/plymouth*; \
|
12
|
-
rm -f /lib/systemd/system/systemd-update-utmp*;
|
13
|
-
|
14
|
-
RUN apt-get update && apt-get install -y openssh-server dbus && apt-get clean
|
15
|
-
|
16
|
-
RUN systemctl set-default multi-user.target
|
17
|
-
|
18
|
-
COPY ami-spec.pub /root/.ssh/authorized_keys
|
19
|
-
|
20
|
-
EXPOSE 22
|
21
|
-
|
22
|
-
CMD ["/bin/bash", "-c", "exec /sbin/init --log-target=journal 3>&1"]
|
data/spec/containers/README.md
DELETED
@@ -1,5 +0,0 @@
|
|
1
|
-
## Integration test containers
|
2
|
-
|
3
|
-
This directory is used to create containers that can be used to test the `WaitForRC` class. Because they require upstart/systemd to exist we have to install and start the init environment. We also setup SSH so that we can simple call the `wait` function and have it SSH to our container to execute.
|
4
|
-
|
5
|
-
Refer to the [README](../../README.md#running-tests) for how to execute them.
|
data/spec/containers/ami-spec
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
-----BEGIN RSA PRIVATE KEY-----
|
2
|
-
MIIEowIBAAKCAQEAwWn2++lylp8RcHzy7H9QpYli3nxLDh769DDbnb9cw2UDd9OH
|
3
|
-
6JZKaT3xe3IbMr39SmkGlOygkBmeH43VxAkiVJv3awDPRU0UvDyUvCsbaYj1/cOS
|
4
|
-
8Vxr7ENExoiKkengcg6k3mFj65ooJ1pf8RoXuj+0+YU0fgejuR/M4x6V8GKFCJhU
|
5
|
-
wFmRs3mcoCx0EiJtTx40IW87uOQUruDX5HcgTUInRhyRxltNrXJaap1weMGpIA/o
|
6
|
-
Bo8foOx1Os9o3YKQlkPF4iqk2AVJ4FZGbMay0cIq3075Jeig6bdlIhRpYA+w+SAI
|
7
|
-
y/yT/K3U1ciQqKtPgahGEyihrh7Ks2F2FSLhdwIDAQABAoIBABWt/QNLrY54kgnb
|
8
|
-
15buxmlntu9dW0Rf8J1ChLtv4cP9JKBf05IcloapbNH7flT3utaGYzh6NZ0xYeoD
|
9
|
-
ifyJUZHOUbNqydDozPQ0ji9xXYc81OX28Beh1m8LM0BVucKVRpVCUvSiUgLsqqeO
|
10
|
-
l8Z8uEAmN/DoH3QpAw8TI3Ip0YC6OHA2aRV9PXuDnR5OTdBPOBj33Fdtf0rUAk41
|
11
|
-
UFe/BHFyACfTK05+bcQz9DvRV/H+SnBeOCqDie1eNDnEgza4NS2cnBUCogKsaCrY
|
12
|
-
gV06pivS2aHsK5CuNB1lcZi1tVf3DnDwPvFWqLLG9PIHaevPDpDURECirCrpCWJT
|
13
|
-
VSHm7KECgYEA4K5jSna4Jzo9FlHzF+yGEju5QwEJTjnhunNw1FpcgPAddFQ4hs3w
|
14
|
-
0EhyPlZyf3vwhfdH4vBhTLjRTrOF2SIvSSPwrkWlAhaluVvpVRFd/ncYW4kAVwhQ
|
15
|
-
15/ZBtvu8OQnKeeztsLlkEi4ik3cKjeXyeDQReb2Guvc6IM4fr6ZrlkCgYEA3F/S
|
16
|
-
uJr04UgzX0cQuNLX7uXz6oeyJupwFkTuAhvLcHDsDHFkP1M9zfFzg5aEcQungz/l
|
17
|
-
5s/vFJmfLBrzhSoYY1T9PDdLwEL/JKaxhKNEV9lExF4exMui6QPWdTMA8ndvB7r5
|
18
|
-
Ur85X8scH1qJo99fsEmNmG5O72PGXmltOB0sNE8CgYEApeuCPYIweh+C7xGzkE5F
|
19
|
-
r/9Uz4tbYN5TuMn5X4gfWcR4K+jqGXrJxDZLz4ctZMGVHIlBF/DmGa8+On1OccvR
|
20
|
-
2ZRl73xU35bz6U9bn0uE+x7d6PLiQmNMt/8+WNdfu5rw5PxLdcK1nnhldxUKak7F
|
21
|
-
k/qmM4jc44Kcj0QgG1EL0nkCgYAFbV61KSvKuIp7WDazNo4W1hbxubHLf46PHdd2
|
22
|
-
udSCymUl0U0UuioVflLH9NcCKbVQaCxzSL+slDP1VByXNPgwyhEKgJoe/Adokaph
|
23
|
-
h9vRBgrJgz/ivNkgP/XyIPVvAz36xMILJaZ2E3x30TT+kiu7HbSdAmpzPtPN027b
|
24
|
-
KOzDxQKBgEv2OvEtpvpv9DgPHs9Mq4haTh2o8c8JW7kwHqbbZOZjZ/4daEh89FhH
|
25
|
-
gjvJV5NjaNhFqBWTnNfjSr4o09WFDoQyVwEUrWNJXXZmsjOHqMDT/kwVoAsld1tO
|
26
|
-
N+JW6/4M+EMYvF39yWzdQn/U3A1gZIfzAC6S3HUCi9BgKLBMKEN3
|
27
|
-
-----END RSA PRIVATE KEY-----
|
@@ -1 +0,0 @@
|
|
1
|
-
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBafb76XKWnxFwfPLsf1CliWLefEsOHvr0MNudv1zDZQN304folkppPfF7chsyvf1KaQaU7KCQGZ4fjdXECSJUm/drAM9FTRS8PJS8KxtpiPX9w5LxXGvsQ0TGiIqR6eByDqTeYWPrmignWl/xGhe6P7T5hTR+B6O5H8zjHpXwYoUImFTAWZGzeZygLHQSIm1PHjQhbzu45BSu4NfkdyBNQidGHJHGW02tclpqnXB4wakgD+gGjx+g7HU6z2jdgpCWQ8XiKqTYBUngVkZsxrLRwirfTvkl6KDpt2UiFGlgD7D5IAjL/JP8rdTVyJCoq0+BqEYTKKGuHsqzYXYVIuF3
|
@@ -1,28 +0,0 @@
|
|
1
|
-
version: '3'
|
2
|
-
services:
|
3
|
-
xenial:
|
4
|
-
build:
|
5
|
-
context: .
|
6
|
-
dockerfile: Dockerfile.xenial
|
7
|
-
ports:
|
8
|
-
- "1122:22"
|
9
|
-
# --security-opt seccomp=unconfined --tmpfs /run --tmpfs /run/lock -v /sys/fs/cgroup:/sys/fs/cgroup:ro
|
10
|
-
security_opt:
|
11
|
-
- seccomp:unconfined
|
12
|
-
tmpfs:
|
13
|
-
- /run
|
14
|
-
- /run/lock
|
15
|
-
volumes:
|
16
|
-
- /sys/fs/cgroup:/sys/fs/cgroup:ro
|
17
|
-
trusty:
|
18
|
-
build:
|
19
|
-
context: .
|
20
|
-
dockerfile: Dockerfile.trusty
|
21
|
-
ports:
|
22
|
-
- "1123:22"
|
23
|
-
amazon_linux:
|
24
|
-
build:
|
25
|
-
context: .
|
26
|
-
dockerfile: Dockerfile.amazon_linux
|
27
|
-
ports:
|
28
|
-
- "1124:22"
|
data/spec/containers/rc.conf
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
# rc - System V runlevel compatibility
|
2
|
-
#
|
3
|
-
# This task runs the old sysv-rc runlevel scripts. It
|
4
|
-
# is usually started by the telinit compatibility wrapper.
|
5
|
-
#
|
6
|
-
# Do not edit this file directly. If you want to change the behaviour,
|
7
|
-
# please create a file rc.override and put your changes there.
|
8
|
-
|
9
|
-
start on runlevel [0123456]
|
10
|
-
|
11
|
-
stop on runlevel [!$RUNLEVEL]
|
12
|
-
|
13
|
-
task
|
14
|
-
|
15
|
-
export RUNLEVEL
|
16
|
-
console output
|
17
|
-
exec /etc/rc.d/rc $RUNLEVEL
|
data/spec/containers/sshd_config
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
HostKey /etc/ssh/ssh_host_rsa_key
|
2
|
-
HostKey /etc/ssh/ssh_host_ecdsa_key
|
3
|
-
HostKey /etc/ssh/ssh_host_ed25519_key
|
4
|
-
SyslogFacility AUTHPRIV
|
5
|
-
AuthorizedKeysFile .ssh/authorized_keys
|
6
|
-
PasswordAuthentication no
|
7
|
-
ChallengeResponseAuthentication no
|
8
|
-
UsePAM yes
|
9
|
-
X11Forwarding yes
|
10
|
-
PrintLastLog yes
|
11
|
-
UsePrivilegeSeparation sandbox
|
12
|
-
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
|
13
|
-
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
|
14
|
-
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
|
15
|
-
AcceptEnv XMODIFIERS
|
16
|
-
Subsystem sftp /usr/libexec/openssh/sftp-server
|
17
|
-
PermitRootLogin yes
|
data/spec/spec_helper.rb
DELETED
data/spec/wait_for_rc_spec.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe AmiSpec::WaitForRC, integration: true do
|
4
|
-
let(:private_key_file) { File.expand_path(File.join('..', 'containers', 'ami-spec'), __FILE__) }
|
5
|
-
context 'xenial server' do
|
6
|
-
let(:ssh_port) { 1122 }
|
7
|
-
it 'executes without printing any errors' do
|
8
|
-
expect { described_class.wait("localhost", "root", private_key_file, ssh_port) }.to_not output.to_stdout
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
context 'trusty server' do
|
13
|
-
let(:ssh_port) { 1123 }
|
14
|
-
it 'executes without printing any errors' do
|
15
|
-
expect { described_class.wait("localhost", "root", private_key_file, ssh_port) }.to_not output.to_stdout
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
context 'amazon linux server' do
|
20
|
-
let(:ssh_port) { 1124 }
|
21
|
-
it 'executes without printing any errors' do
|
22
|
-
expect { described_class.wait("localhost", "root", private_key_file, ssh_port) }.to_not output.to_stdout
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
data/spec/wait_for_ssh_spec.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe AmiSpec::WaitForSSH do
|
4
|
-
describe '#wait' do
|
5
|
-
let(:retries) { 30 }
|
6
|
-
subject { described_class.wait('127.0.0.1', 'ubuntu', 'key.pem', 30) }
|
7
|
-
|
8
|
-
before do
|
9
|
-
allow_any_instance_of(Object).to receive(:sleep)
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'returns after one attempt if ssh connection succeeds' do
|
13
|
-
expect(Net::SSH).to receive(:start)
|
14
|
-
|
15
|
-
subject
|
16
|
-
end
|
17
|
-
|
18
|
-
context 'ssh fails' do
|
19
|
-
before do
|
20
|
-
allow(Net::SSH).to receive(:start).and_raise(Errno::ECONNREFUSED, 'ssh failed')
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'raises an exception' do
|
24
|
-
expect{subject}.to raise_error(AmiSpec::InstanceConnectionTimeout)
|
25
|
-
end
|
26
|
-
|
27
|
-
it 'returns the last error' do
|
28
|
-
expect(Net::SSH).to receive(:start).and_raise(Errno::ECONNREFUSED, 'some other error')
|
29
|
-
expect{subject}.to raise_error(AmiSpec::InstanceConnectionTimeout, /ssh failed/)
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'tries the number of retries specified' do
|
33
|
-
expect(Net::SSH).to receive(:start).exactly(retries).times
|
34
|
-
|
35
|
-
expect{subject}.to raise_error(AmiSpec::InstanceConnectionTimeout)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|