ami_spec 1.2.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|