ami_spec 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ *.gem
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p551
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) [year] [fullname]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # AmiSpec
2
+
3
+ Acceptance testing your AMIs.
4
+
5
+ AmiSpec is a RubyGem used to launch an Amazon Machine Image (AMI) and run ServerSpecs against it. It wraps around the AWS API and ServerSpec to spin up, test and tear down instances.
6
+
7
+ ## Project Goals
8
+
9
+ 1. To decouple the building of AMIs from testing them. Other approaches to this problem involve copying ServerSpec tests to an EC2 instance before it's converted to an AMI and running the tests there.
10
+ The problem with this approach is:
11
+
12
+ - It does not test the instance in the state it will be in when it's actually in production.
13
+ - It does makes it harder to replace the AMI builder software (i.e. [Packer](https://github.com/mitchellh/packer)).
14
+ - The software required to test the AMI must exist in the AMI.
15
+
16
+ 2. To run tests as fast as possible; this approach is slightly slower than the alternative listed above (about 1-2 minutes), but should not be onerous.
17
+
18
+ ## Installation
19
+
20
+ System-wide: gem install ami-spec
21
+
22
+ With bundler:
23
+
24
+ Add `gem 'ami-spec'` to your Gemfile.
25
+ Run `bundle install`
26
+
27
+ ## CLI Usage
28
+
29
+ ```cli
30
+ $ bundle exec ami_spec --help
31
+ Options:
32
+ -r, --role=<s> The role to test, this should map to a directory in the spec folder
33
+ -a, --ami=<s> The ami ID to run tests against
34
+ -o, --role-ami-file=<s> A file containing comma separated roles and amis. i.e.
35
+ web_server,ami-id.
36
+ -s, --specs=<s> The directory to find ServerSpecs
37
+ -u, --subnet-id=<s> The subnet to start the instance in
38
+ -k, --key-name=<s> The SSH key name to assign to instances
39
+ -e, --key-file=<s> The SSH private key file associated to the key_name
40
+ -h, --ssh-user=<s> The user to ssh to the instance as
41
+ -w, --aws-region=<s> The AWS region, defaults to AWS_DEFAULT_REGION environment variable
42
+ -i, --aws-instance-type=<s> The ec2 instance type, defaults to t2.micro (default: t2.micro)
43
+ -c, --aws-security-groups=<s+> Security groups to associate to the launched instances. May be specified
44
+ multiple times
45
+ -p, --aws-public-ip Launch instances with a public IP
46
+ -t, --ssh-retries=<i> The number of times we should try sshing to the ec2 instance before
47
+ giving up. Defaults to 30 (default: 30)
48
+ -d, --debug Don't terminate instances on exit
49
+ -l, --help Show this message
50
+ $ bundle exec ami_spec \
51
+ --role web_server \
52
+ --ami ami-12345678 \
53
+ --subnet-id subnet-abcdefgh \
54
+ --key-name ec2-key-pair \
55
+ --key-file ~/.ssh/ec2-key-pair.pem \
56
+ --ssh-user ubuntu \
57
+ --specs ./my_project/spec
58
+ ```
59
+
60
+ AmiSpec will launch an EC2 instance from the given AMI (`--ami`), in a subnet (`--subnet-id`) with a key-pair (`--key-name`)
61
+ and try to SSH to it (`--ssh-user` and `--key-file`).
62
+ When the instances becomes reachable it will run all Specs inside the role spec directory (`--role` i.e. `my_project/spec/web_server`).
63
+
64
+ Alternative to the `--ami` and `--role` variables, a file of comma separated roles and AMIs (`ROLE,AMI\n`) can be supplied to `--role-ami-file`.
65
+
66
+ ## Development Status
67
+
68
+ Active and ready for Production
69
+
70
+ ## Contributing
71
+
72
+ For bug fixes, documentation changes, and small features:
73
+ 1. Fork it ( https://github.com/envato/ami-spec/fork )
74
+ 2. Create your feature branch (git checkout -b my-new-feature)
75
+ 3. Commit your changes (git commit -am 'Add some feature')
76
+ 4. Push to the branch (git push origin my-new-feature)
77
+ 5. Create a new Pull Request
78
+
79
+ ## Maintainers
80
+
81
+ Patrick Robinson (@nemski)
82
+
83
+ ## License
84
+
85
+ AmiSpec uses the MIT license. See [LICENSE.txt](./LICENSE.txt)
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/ami_spec.gemspec ADDED
@@ -0,0 +1,27 @@
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', '~> 2'
21
+ gem.add_dependency 'rake'
22
+ gem.add_dependency 'serverspec'
23
+ gem.add_dependency 'specinfra', '>= 2.45'
24
+ gem.add_dependency 'trollop'
25
+ gem.add_dependency 'hashie'
26
+ gem.add_dependency 'net-ssh', '< 3.0'
27
+ end
data/bin/ami_spec ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'trollop'
4
+ require 'ami_spec'
5
+
6
+ AmiSpec.invoke
@@ -0,0 +1,67 @@
1
+ require 'aws-sdk'
2
+ require 'forwardable'
3
+
4
+ module AmiSpec
5
+ class AwsInstance
6
+ extend Forwardable
7
+
8
+ def self.start(args)
9
+ new(args).tap do |instance|
10
+ instance.start
11
+ end
12
+ end
13
+
14
+ def initialize(options)
15
+ @role = options.fetch(:role)
16
+ @ami = options.fetch(:ami)
17
+ @subnet_id = options.fetch(:subnet_id)
18
+ @key_name = options.fetch(:key_name)
19
+ @instance_type = options.fetch(:aws_instance_type)
20
+ @public_ip = options.fetch(:aws_public_ip)
21
+ @region = options.fetch(:aws_region)
22
+ @security_group_ids = options.fetch(:aws_security_groups)
23
+ end
24
+
25
+ def_delegators :@instance, :instance_id, :tags, :terminate, :private_ip_address, :public_ip_address
26
+
27
+ def start
28
+ client = Aws::EC2::Client.new(client_options)
29
+ placeholder_instance = client.run_instances(instances_options).instances.first
30
+
31
+ @instance = Aws::EC2::Instance.new(placeholder_instance.instance_id)
32
+ @instance.wait_until_running
33
+ tag_instance
34
+ end
35
+
36
+ private
37
+
38
+ def client_options
39
+ !@region.nil? ? {region: @region} : {}
40
+ end
41
+
42
+ def instances_options
43
+ params = {
44
+ image_id: @ami,
45
+ min_count: 1,
46
+ max_count: 1,
47
+ instance_type: @instance_type,
48
+ key_name: @key_name,
49
+ network_interfaces: [{
50
+ device_index: 0,
51
+ associate_public_ip_address: @public_ip,
52
+ subnet_id: @subnet_id,
53
+ }]
54
+ }
55
+
56
+ unless @security_group_ids.nil?
57
+ params[:network_interfaces][0][:groups] = @security_group_ids
58
+ end
59
+
60
+ params
61
+ end
62
+
63
+ def tag_instance
64
+ @instance.create_tags(tags: [{ key: 'AmiSpec', value: @role }])
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,16 @@
1
+ require 'hashie'
2
+
3
+ module AmiSpec
4
+ class AwsInstanceOptions < Hashie::Dash
5
+ include Hashie::Extensions::IgnoreUndeclared
6
+
7
+ property :ami
8
+ property :role
9
+ property :subnet_id
10
+ property :key_name
11
+ property :aws_instance_type
12
+ property :aws_public_ip
13
+ property :aws_region
14
+ property :aws_security_groups
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ # Loading serverspec first causes a weird error - stack level too deep
2
+ # Requiring rspec first fixes that *shrug*
3
+ require 'rspec'
4
+ require 'serverspec'
5
+
6
+ module AmiSpec
7
+ class ServerSpec
8
+ def initialize(options)
9
+ instance = options.fetch(:instance)
10
+ public_ip = options.fetch(:aws_public_ip)
11
+
12
+ @debug = options.fetch(:debug)
13
+ @ip = public_ip ? instance.public_ip_address : instance.private_ip_address
14
+ @role = instance.tags.find{ |tag| tag.key == 'AmiSpec' }.value
15
+ @spec = options.fetch(:specs)
16
+ @user = options.fetch(:ssh_user)
17
+ @key_file = options.fetch(:key_file)
18
+ end
19
+
20
+ def run
21
+ $LOAD_PATH.unshift(@spec) unless $LOAD_PATH.include?(@spec)
22
+ require File.join(@spec, 'spec_helper')
23
+
24
+ set :backend, :ssh
25
+ set :host, @ip
26
+ set :ssh_options, :user => @user, :keys => [@key_file], :paranoid => false
27
+
28
+ RSpec.configuration.fail_fast = true if @debug
29
+
30
+ RSpec::Core::Runner.disable_autorun!
31
+ result = RSpec::Core::Runner.run(Dir.glob("#{@spec}/#{@role}/*_spec.rb"))
32
+
33
+ # We can't use Rspec.clear_examples here because it also clears the shared_examples.
34
+ # As shared examples are loaded in via the spec_helper, we cannot reload them.
35
+ RSpec.world.example_groups.clear
36
+
37
+ Specinfra::Backend::Ssh.clear
38
+
39
+ result.zero?
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ require 'hashie'
2
+
3
+ module AmiSpec
4
+ class ServerSpecOptions < Hashie::Dash
5
+ include Hashie::Extensions::IgnoreUndeclared
6
+
7
+ property :instance
8
+ property :aws_public_ip
9
+ property :debug
10
+ property :key_file
11
+ property :specs
12
+ property :ssh_user
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module AmiSpec
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,27 @@
1
+ require 'net/ssh'
2
+
3
+ module AmiSpec
4
+ class WaitForSSH
5
+ def self.wait(ip_address, user, key, max_retries)
6
+ last_error = nil
7
+ retries = 0
8
+
9
+ while retries < max_retries
10
+ begin
11
+ Net::SSH.start(ip_address, user, keys: [key], paranoid: false) { |ssh| ssh.exec 'echo boo!' }
12
+ rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, Timeout::Error => error
13
+ last_error = error
14
+ sleep 1
15
+ else
16
+ break
17
+ end
18
+
19
+ retries = retries + 1
20
+ end
21
+
22
+ if retries > max_retries - 1
23
+ raise AmiSpec::InstanceConnectionTimeout.new("Timed out waiting for SSH to become available: #{last_error}")
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/ami_spec.rb ADDED
@@ -0,0 +1,115 @@
1
+ require 'ami_spec/aws_instance'
2
+ require 'ami_spec/aws_instance_options'
3
+ require 'ami_spec/server_spec'
4
+ require 'ami_spec/server_spec_options'
5
+ require 'ami_spec/wait_for_ssh'
6
+ require 'trollop'
7
+
8
+ module AmiSpec
9
+ class InstanceConnectionTimeout < StandardError; end
10
+ # == Parameters:
11
+ # amis::
12
+ # A hash of roles and amis in the format of:
13
+ # {role => ami_id}. i.e.
14
+ # {'web_server' => 'ami-abcd1234'}
15
+ # specs::
16
+ # A string of the directory to find ServerSpecs.
17
+ # There should be a folder in this directory for each role found in ::amis
18
+ # subnet_id::
19
+ # The subnet_id to start instances in.
20
+ # key_name::
21
+ # The SSH key name to assign to instances. This key name must exist on the executing host for passwordless login.
22
+ # key_file::
23
+ # The SSH key file to use to connect to the host.
24
+ # aws_region::
25
+ # AWS region to connect to
26
+ # Defaults to AWS_DEFAULT_REGION
27
+ # aws_security_group_ids::
28
+ # AWS Security groups to assign to the instances
29
+ # Defaults to the default security group for the VPC
30
+ # aws_instance_type::
31
+ # AWS ec2 instance type
32
+ # aws_public_ip::
33
+ # Should the instances get a public IP address
34
+ # ssh_user::
35
+ # The username to SSH to the AMI with.
36
+ # ssh_retries::
37
+ # Set the maximum number of ssh retries while waiting for the instance to boot.
38
+ # debug::
39
+ # Don't terminate the instances on exit
40
+ # == Returns:
41
+ # Boolean - The result of all the server specs.
42
+ def self.run(options)
43
+ instances = []
44
+ options[:amis].each_pair do |role, ami|
45
+ aws_instance_options = AwsInstanceOptions.new(options.merge(role: role, ami: ami))
46
+ instances << AwsInstance.start(aws_instance_options)
47
+ end
48
+
49
+ results = []
50
+ instances.each do |instance|
51
+ ip_address = options[:aws_public_ip] ? instance.public_ip_address : instance.private_ip_address
52
+ WaitForSSH.wait(ip_address, options[:ssh_user], options[:key_file], options[:ssh_retries])
53
+
54
+ server_spec_options = ServerSpecOptions.new(options.merge(instance: instance))
55
+ results << ServerSpec.new(server_spec_options).run
56
+ end
57
+
58
+ results.all?
59
+ ensure
60
+ stop_instances(instances, options[:debug])
61
+ end
62
+
63
+ def self.stop_instances(instances, debug)
64
+ instances.each do |instance|
65
+ begin
66
+ if debug
67
+ puts "EC2 instance ##{instance.instance_id} has not been stopped due to debug mode."
68
+ else
69
+ instance.terminate
70
+ end
71
+ rescue Aws::EC2::Errors::InvalidInstanceIDNotFound
72
+ puts "Failed to stop EC2 instance ##{instance.instance_id}"
73
+ end
74
+ end
75
+ end
76
+
77
+ private_class_method :stop_instances
78
+
79
+ def self.invoke
80
+ options = Trollop::options do
81
+ opt :role, "The role to test, this should map to a directory in the spec folder", type: :string
82
+ opt :ami, "The ami ID to run tests against", type: :string
83
+ opt :role_ami_file, "A file containing comma separated roles and amis. i.e.\nweb_server,ami-id.",
84
+ type: :string
85
+ opt :specs, "The directory to find ServerSpecs", type: :string, required: true
86
+ opt :subnet_id, "The subnet to start the instance in", type: :string, required: true
87
+ opt :key_name, "The SSH key name to assign to instances", type: :string, required: true
88
+ opt :key_file, "The SSH private key file associated to the key_name", type: :string, required: true
89
+ opt :ssh_user, "The user to ssh to the instance as", type: :string, required: true
90
+ opt :aws_region, "The AWS region, defaults to AWS_DEFAULT_REGION environment variable", type: :string
91
+ opt :aws_instance_type, "The ec2 instance type, defaults to t2.micro", type: :string, default: 't2.micro'
92
+ opt :aws_security_groups, "Security groups to associate to the launched instances. May be specified multiple times",
93
+ type: :strings, default: nil
94
+ opt :aws_public_ip, "Launch instances with a public IP"
95
+ opt :ssh_retries, "The number of times we should try sshing to the ec2 instance before giving up. Defaults to 30",
96
+ type: :int, default: 30
97
+ opt :debug, "Don't terminate instances on exit"
98
+ end
99
+
100
+ if options[:role] && options[:ami]
101
+ options[:amis] = { options[:role] => options[:ami] }
102
+ options.delete(:role)
103
+ options.delete(:ami)
104
+ elsif options[:role_ami_file]
105
+ file_lines = File.read(options[:role_ami_file]).split("\n")
106
+ file_array = file_lines.collect { |line| line.split(',') }.flatten
107
+ options[:amis] = Hash[*file_array]
108
+ options.delete(:role_ami_file)
109
+ else
110
+ fail "You must specify either role and ami or role_ami_file"
111
+ end
112
+
113
+ exit run(options)
114
+ end
115
+ end
@@ -0,0 +1,54 @@
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 '#run' do
25
+ before do
26
+ allow(AmiSpec::WaitForSSH).to receive(:wait).and_return(true)
27
+ allow(AmiSpec::AwsInstance).to receive(:start).and_return(ec2_double)
28
+ allow(AmiSpec::ServerSpec).to receive(:new).and_return(server_spec_double)
29
+ allow(ec2_double).to receive(:terminate).and_return(true)
30
+ allow(ec2_double).to receive(:private_ip_address).and_return('127.0.0.1')
31
+ allow_any_instance_of(Object).to receive(:sleep)
32
+ end
33
+
34
+ context 'successful tests' do
35
+ it 'calls aws instance for each ami' do
36
+ expect(AmiSpec::AwsInstance).to receive(:start).with(hash_including(role: 'web_server'))
37
+ expect(AmiSpec::AwsInstance).to receive(:start).with(hash_including(role: 'db_server'))
38
+ subject
39
+ end
40
+
41
+ it 'returns true' do
42
+ expect(subject).to be_truthy
43
+ end
44
+ end
45
+
46
+ context 'failed tests' do
47
+ let(:test_result) { false }
48
+
49
+ it 'returns false' do
50
+ expect(subject).to be_falsey
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe AmiSpec::AwsInstance do
4
+ let(:role) { 'web_server' }
5
+ let(:sec_group_id) { nil }
6
+ let(:region) { nil }
7
+ let(:client_double) { instance_double(Aws::EC2::Client) }
8
+ let(:new_ec2_double) { instance_double(Aws::EC2::Types::Instance) }
9
+ let(:ec2_double) { instance_double(Aws::EC2::Instance) }
10
+ subject(:aws_instance) do
11
+ described_class.new(
12
+ role: role,
13
+ ami: 'ami',
14
+ subnet_id: 'subnet',
15
+ key_name: 'key',
16
+ aws_instance_type: 't2.micro',
17
+ aws_public_ip: false,
18
+ aws_security_groups: sec_group_id,
19
+ aws_region: region
20
+ )
21
+ end
22
+
23
+ before do
24
+ allow(Aws::EC2::Client).to receive(:new).and_return(client_double)
25
+ allow(client_double).to receive(:run_instances).and_return(double(instances: [new_ec2_double]))
26
+ allow(ec2_double).to receive(:create_tags).and_return(double)
27
+ allow(Aws::EC2::Instance).to receive(:new).and_return(ec2_double)
28
+ allow(new_ec2_double).to receive(:instance_id)
29
+ allow(ec2_double).to receive(:instance_id)
30
+ allow(ec2_double).to receive(:wait_until_running)
31
+ end
32
+
33
+ describe '#start' do
34
+ subject(:start) { aws_instance.start }
35
+ context 'without optional values' do
36
+ it 'does not include the security group' do
37
+ expect(client_double).to receive(:run_instances).with(
38
+ hash_excluding(:network_interfaces=>array_including(hash_including(:groups)))
39
+ )
40
+ start
41
+ end
42
+
43
+ it 'does include the region' do
44
+ expect(Aws::EC2::Client).to receive(:new).with(
45
+ hash_excluding(:region => region)
46
+ )
47
+ start
48
+ end
49
+ end
50
+
51
+ context 'with security group' do
52
+ let(:sec_group_id) { ['1234'] }
53
+
54
+ it 'does include security groups' do
55
+ expect(client_double).to receive(:run_instances).with(
56
+ hash_including(:network_interfaces=>array_including(hash_including(:groups)))
57
+ )
58
+ start
59
+ end
60
+ end
61
+
62
+ context 'with region' do
63
+ let(:region) { 'us-east-1' }
64
+
65
+ it 'does include the region' do
66
+ expect(Aws::EC2::Client).to receive(:new).with(
67
+ hash_including(:region => region)
68
+ )
69
+ start
70
+ end
71
+ end
72
+
73
+ it 'tags the instance with a role' do
74
+ expect(ec2_double).to receive(:create_tags).with(
75
+ hash_including(tags: [{ key: 'AmiSpec', value: role}])
76
+ )
77
+ start
78
+ end
79
+
80
+ it 'delegates some methods to the instance variable' do
81
+ expect(ec2_double).to receive(:instance_id)
82
+ start
83
+ aws_instance.instance_id
84
+ end
85
+ end
86
+
87
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'ami_spec'
@@ -0,0 +1,39 @@
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
metadata ADDED
@@ -0,0 +1,182 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ami_spec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Patrick Robinson
9
+ - Martin Jagusch
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2015-12-18 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: aws-sdk
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: '2'
31
+ - !ruby/object:Gem::Dependency
32
+ name: rake
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: serverspec
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: specinfra
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '2.45'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '2.45'
79
+ - !ruby/object:Gem::Dependency
80
+ name: trollop
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: hashie
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: net-ssh
113
+ requirement: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - <
117
+ - !ruby/object:Gem::Version
118
+ version: '3.0'
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - <
125
+ - !ruby/object:Gem::Version
126
+ version: '3.0'
127
+ description: Acceptance testing your AMIs
128
+ email: []
129
+ executables:
130
+ - ami_spec
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - .gitignore
135
+ - .ruby-version
136
+ - Gemfile
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
140
+ - ami_spec.gemspec
141
+ - bin/ami_spec
142
+ - lib/ami_spec.rb
143
+ - lib/ami_spec/aws_instance.rb
144
+ - lib/ami_spec/aws_instance_options.rb
145
+ - lib/ami_spec/server_spec.rb
146
+ - lib/ami_spec/server_spec_options.rb
147
+ - lib/ami_spec/version.rb
148
+ - lib/ami_spec/wait_for_ssh.rb
149
+ - spec/ami_spec_spec.rb
150
+ - spec/aws_instance_spec.rb
151
+ - spec/spec_helper.rb
152
+ - spec/wait_for_ssh_spec.rb
153
+ homepage: https://github.com/envato/ami-spec
154
+ licenses: []
155
+ post_install_message:
156
+ rdoc_options: []
157
+ require_paths:
158
+ - lib
159
+ required_ruby_version: !ruby/object:Gem::Requirement
160
+ none: false
161
+ requirements:
162
+ - - ! '>='
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ none: false
167
+ requirements:
168
+ - - ! '>='
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ requirements: []
172
+ rubyforge_project:
173
+ rubygems_version: 1.8.23.2
174
+ signing_key:
175
+ specification_version: 3
176
+ summary: Acceptance testing your AMIs
177
+ test_files:
178
+ - spec/ami_spec_spec.rb
179
+ - spec/aws_instance_spec.rb
180
+ - spec/spec_helper.rb
181
+ - spec/wait_for_ssh_spec.rb
182
+ has_rdoc: