cirrus-aws 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cfe296c12d483dc3cfe49b9e7d1c6d12b03d9f6a
4
+ data.tar.gz: 67e5d6ef681de3f98ee9730fafde36698c069d38
5
+ SHA512:
6
+ metadata.gz: 540e9f5eff1d4f146bbf4e3ff9498cbaba05e433d62fa8eb22dfa5ce9c4a487e0ba594ea71d53b53c36f21f0f600e8292df30ddc17cbd0d80fa23bb6e7a67462
7
+ data.tar.gz: 74c83ec54402ab291fe231c5280349a898281053e974b071aa442a3f405300664b8a88cc5a216549af7be717525550d48ae7462986a56d3611846d086d0ce877
data/.gitignore ADDED
@@ -0,0 +1,70 @@
1
+ # bundler state
2
+ /.bundle
3
+ /vendor/bundle/
4
+ /vendor/ruby/
5
+
6
+ # minimal Rails specific artifacts
7
+ db/*.sqlite3
8
+ /db/*.sqlite3-journal
9
+ /log/*
10
+ /tmp/*
11
+
12
+ # various artifacts
13
+ **.war
14
+ *.rbc
15
+ *.sassc
16
+ .rspec
17
+ .redcar/
18
+ .sass-cache
19
+ /config/config.yml
20
+ /config/database.yml
21
+ /config/application.yml
22
+ /config/secrets.yml
23
+ /coverage.data
24
+ /coverage/
25
+ /db/*.javadb/
26
+ /db/*.sqlite3
27
+ /doc/api/
28
+ /doc/app/
29
+ /doc/features.html
30
+ /doc/specs.html
31
+ /public/cache
32
+ /public/stylesheets/compiled
33
+ /public/system/*
34
+ /spec/tmp/*
35
+ /cache
36
+ /capybara*
37
+ /capybara-*.html
38
+ /gems
39
+ /specifications
40
+ rerun.txt
41
+ pickle-email-*.html
42
+ .zeus.sock
43
+ tags
44
+ gems.tags
45
+
46
+ # If you find yourself ignoring temporary files generated by your text editor
47
+ # or operating system, you probably want to add a global ignore instead:
48
+ # git config --global core.excludesfile ~/.gitignore_global
49
+ #
50
+ # Here are some files you may want to ignore globally:
51
+
52
+ # scm revert files
53
+ **.orig
54
+
55
+ # Mac finder artifacts
56
+ .DS_Store
57
+
58
+ # Netbeans project directory
59
+ /nbproject/
60
+
61
+ # RubyMine project files
62
+ .idea
63
+
64
+ # Textmate project files
65
+ /*.tmproj
66
+
67
+ # vim artifacts
68
+ **.swp
69
+
70
+ dump.rdb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cirrus.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrew Berls
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # Cirrus
2
+
3
+ A command-line tool to launch and provision EC2 instances. Cirrus is an experimental work-in-progress and should not be considered production-ready.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'cirrus'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install cirrus
20
+
21
+ ## Usage
22
+
23
+ The CLI ships with two commands: `launch` and `teardown`.
24
+
25
+ ### Launching an instance
26
+ Usage: `cirrus launch IMAGE_ID AWS_ACCESS_KEY AWS_SECRET_KEY REGION`
27
+
28
+ This will automatically create a default Cirrus key pair and security group
29
+ (authorizing inbound SSH and HTTP traffic), and launch an instance
30
+
31
+ For example, `cirrus launch ami-ee4f77ab <access_key> <secret_key> us-west-1` will launch an instance running Ubuntu 14.04 in us-west-1 (N. California).
32
+
33
+
34
+ ### Tearing down an instance
35
+ Usage: `cirrus teardown INSTANCE_ID AWS_ACCESS_KEY AWS_SECRET_KEY REGION`
36
+
37
+ Example: `cirrus teardown i-bdc5b377 <access_key> <secret_key> us-west-1`
38
+
39
+ This will terminate the specified instance and destroy its associated security groups and keypairs.
40
+
41
+ That's it!
42
+
43
+ ## Contributing
44
+
45
+ 1. Fork it ( https://github.com/andrewberls/cirrus/fork )
46
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
47
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
48
+ 4. Push to the branch (`git push origin my-new-feature`)
49
+ 5. Create a new Pull Request
data/bin/cirrus ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cirrus'
4
+
5
+ USAGE =
6
+ %Q{\
7
+ Usage: cirrus <command> <args>
8
+
9
+ Commands:
10
+ launch IMAGE_ID AWS_ACCESS_KEY AWS_SECRET_KEY REGION
11
+ teardown INSTANCE_ID AWS_ACCESS_KEY AWS_SECRET_KEY REGION}
12
+
13
+ command = ARGV[0]
14
+
15
+ case command
16
+ when 'launch'
17
+ ARGV.length == 5 or raise "Usage: cirrus launch IMAGE_ID AWS_ACCESS_KEY AWS_SECRET_KEY REGION"
18
+
19
+ image_id = ARGV[1]
20
+ aws_access_key = ARGV[2]
21
+ aws_secret_key = ARGV[3]
22
+ region = ARGV[4]
23
+
24
+ Cirrus.setup(
25
+ image_id,
26
+ access_key_id: aws_access_key,
27
+ secret_access_key: aws_secret_key,
28
+ region: region
29
+ )
30
+ when 'teardown'
31
+ ARGV.length == 5 or raise "Usage: cirrus teardown INSTANCE_ID AWS_ACCESS_KEY AWS_SECRET_KEY REGION"
32
+
33
+ instance_id = ARGV[1]
34
+ aws_access_key = ARGV[2]
35
+ aws_secret_key = ARGV[3]
36
+ region = ARGV[4]
37
+
38
+ Cirrus.teardown(instance_id, {
39
+ access_key_id: aws_access_key,
40
+ secret_access_key: aws_secret_key,
41
+ region: region
42
+ })
43
+ when '-h', '--help'
44
+ puts USAGE
45
+ else
46
+ raise USAGE
47
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cirrus/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cirrus-aws"
8
+ spec.version = Cirrus::VERSION
9
+ spec.authors = ["Andrew Berls"]
10
+ spec.email = ["andrew.berls@gmail.com"]
11
+ spec.summary = %q{A command-line tool to provision and launch AWS EC2 instances}
12
+ spec.description = %q{}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ end
@@ -0,0 +1,75 @@
1
+ module Cirrus::Instance
2
+ extend self
3
+
4
+ STATUS_PENDING = :pending
5
+ STATUS_RUNNING = :running
6
+ STATUS_SHUTTING_DOWN = :shutting_down
7
+ STATUS_TERMINATED = :terminated
8
+
9
+ class UnexpectedStatusError < StandardError; end
10
+
11
+ SSH_WAIT_TIME = 45 # seconds
12
+
13
+ # Launch an instance, optionally adding tags, and wait for it to
14
+ # run
15
+ #
16
+ # Returns AWS::EC2::Instance
17
+ def launch(ec2, launch_opts, tags={})
18
+ puts "Launching instance (#{launch_opts[:image_id]}) ..."
19
+ instance = ec2.instances.create(launch_opts)
20
+ sleep 5 while instance.status == STATUS_PENDING
21
+ puts "Launched instance #{instance.id}, status: #{instance.status}"
22
+
23
+ assert_instance_status!(instance, STATUS_RUNNING)
24
+
25
+ puts "Tagging instance ..."
26
+ tags.each { |k, v| instance.add_tag(k, value: v) }
27
+
28
+ puts "Waiting for SSH to become available ..."
29
+ sleep SSH_WAIT_TIME
30
+
31
+ instance
32
+ end
33
+
34
+
35
+ # Terminate an instance and destroy its associated security groups / key pair
36
+ # TODO: if we have a standard Cirrus sec group / keypair, don't want to destroy them
37
+ def teardown(ec2, instance_id)
38
+ instance = ec2.instances[instance_id]
39
+ terminate(instance)
40
+
41
+ puts "Destroying security groups ..."
42
+
43
+ instance.security_groups.each do |group|
44
+ next if group.name == 'default'
45
+ puts "\tDestroying group #{group.id} ..."
46
+ group.delete
47
+ end
48
+
49
+ key_pair = ec2.key_pairs[instance.key_name]
50
+ puts "Destroying keypair #{key_pair.name} ..."
51
+ key_pair.delete
52
+ end
53
+
54
+
55
+ private
56
+
57
+ # Terminate an instance and wait for completion
58
+ def terminate(instance)
59
+ instance.terminate
60
+ puts "Instance terminated, waiting for shutdown completion ..."
61
+ sleep 10 while instance.status == STATUS_SHUTTING_DOWN
62
+
63
+ assert_instance_status!(instance, STATUS_TERMINATED)
64
+ end
65
+
66
+
67
+ def assert_instance_status!(instance, expected_status)
68
+ actual_status = instance.status
69
+ unless actual_status == expected_status
70
+ raise UnexpectedStatusError, "Expected #{expected_status}, was #{actual_status} for instance #{instance.id}"
71
+ end
72
+ nil
73
+ end
74
+
75
+ end
@@ -0,0 +1,46 @@
1
+ require 'fileutils'
2
+
3
+ module Cirrus::KeyPair
4
+ extend self
5
+
6
+ DEFAULT_NAME = 'cirrus'
7
+
8
+ PRIVATE_KEY_PERMISSIONS = 0600
9
+
10
+ def find_or_create_default(ec2)
11
+ default_key_pair = ec2.key_pairs.filter("key-name", DEFAULT_NAME).first
12
+
13
+ if !default_key_pair.nil?
14
+ puts "Using default Cirrus key pair"
15
+ [default_key_pair, private_key_filename]
16
+ else
17
+ puts "Creating default Cirrus key pair ..."
18
+ create_default_key_pair!(ec2)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # Create the default key pair and write its private key to local disk
25
+ #
26
+ # Returns tuple of [AWS::EC2::KeyPair, string_private_key_filename]
27
+ def create_default_key_pair!(ec2)
28
+ key_pair_name = DEFAULT_NAME
29
+ key_pair = ec2.key_pairs.create(key_pair_name)
30
+ filename = private_key_filename
31
+
32
+ File.open(filename, 'w') do |file|
33
+ file.write(key_pair.private_key)
34
+ end
35
+ FileUtils.chmod(PRIVATE_KEY_PERMISSIONS, filename)
36
+ puts "Saved new keypair to #{filename}"
37
+
38
+ [key_pair, filename]
39
+ end
40
+
41
+
42
+ def private_key_filename
43
+ "#{ENV['HOME']}/.ssh/#{DEFAULT_NAME}"
44
+ end
45
+
46
+ end
data/lib/cirrus/scp.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'net/scp'
2
+ require 'cirrus/ssh'
3
+
4
+ module Cirrus
5
+ module SCP
6
+ extend self
7
+
8
+ class SCPUnavailable < StandardError; end
9
+
10
+ # Opens an SCP connection and yields it so that you can download
11
+ # and upload files.
12
+ def connect(host, username)
13
+ Cirrus::SSH.connect(host, username) do |connection|
14
+ scp = Net::SCP.new(connection)
15
+ return yield scp
16
+ end
17
+ rescue Net::SCP::Error => e
18
+ # If we get the exit code of 127, then this means SCP is unavailable.
19
+ raise SCPUnavailable if e.message =~ /\(127\)/
20
+
21
+ # Otherwise, just raise the error up
22
+ raise e
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ module Cirrus
2
+ module SecurityGroup
3
+ extend self
4
+
5
+ DEFAULT_NAME = 'cirrus'
6
+
7
+ def find_or_create_default(ec2)
8
+ default_group = ec2.security_groups.filter('group-name', DEFAULT_NAME).first
9
+
10
+ if !default_group.nil?
11
+ puts "Using default Cirrus security group"
12
+ default_group
13
+ else
14
+ puts "Creating default Cirrus security group ..."
15
+ create_default_group!(ec2)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ # Create default security group + authorize SSH / HTTP
22
+ # TODO: configurable allowed IP range ?
23
+ def create_default_group!(ec2)
24
+ ec2.security_groups.create(DEFAULT_NAME).tap do |g|
25
+ g.authorize_ingress(:tcp, 22, "0.0.0.0/0")
26
+ g.authorize_ingress(:tcp, 80, "0.0.0.0/0")
27
+ end
28
+ end
29
+
30
+ end
31
+ end
data/lib/cirrus/ssh.rb ADDED
@@ -0,0 +1,110 @@
1
+ require 'tempfile'
2
+ require 'net/ssh'
3
+ require 'cirrus/scp'
4
+
5
+ $retry_count = 0
6
+
7
+ module Cirrus
8
+ module SSH
9
+ extend self
10
+
11
+ # Open an SSH connection and yield it
12
+ def connect(host, username)
13
+ timeout = 15
14
+
15
+ common_connect_opts = {
16
+ auth_methods: ["publickey"],
17
+ forward_agent: false,
18
+ port: 22,
19
+ timeout: 15,
20
+ user_known_hosts_file: [],
21
+ verbose: :debug
22
+ }
23
+
24
+ begin
25
+ connection =
26
+ Timeout.timeout(timeout) do
27
+ begin
28
+ connect_opts = common_connect_opts.dup
29
+ connect_opts[:logger] = Logger.new(StringIO.new)
30
+ Net::SSH.start(host, username, connect_opts)
31
+ end
32
+ end
33
+ rescue Errno::EACCES
34
+ # This happens on connect() for unknown reasons yet...
35
+ raise "eacces"
36
+ rescue Errno::ETIMEDOUT, Timeout::Error
37
+ # This happens if we continued to timeout when attempting to connect.
38
+ if $retry_count < 3
39
+ $retry_count += 1
40
+ retry
41
+ else
42
+ raise "timeout"
43
+ end
44
+ rescue Net::SSH::AuthenticationFailed
45
+ # This happens if authentication failed. We wrap the error in our
46
+ # own exception.
47
+ raise "authentication"
48
+ rescue Net::SSH::Disconnect
49
+ # This happens if the remote server unexpectedly closes the
50
+ # connection. This is usually raised when SSH is running on the
51
+ # other side but can't properly setup a connection. This is
52
+ # usually a server-side issue.
53
+ raise "disconnect"
54
+ rescue Errno::ECONNREFUSED
55
+ # This is raised if we failed to connect the max amount of times
56
+ raise "econnrefused"
57
+ rescue Errno::ECONNRESET
58
+ # This is raised if we failed to connect the max number of times
59
+ # due to an ECONNRESET.
60
+ raise "econnreset"
61
+ rescue Errno::EHOSTDOWN
62
+ # This is raised if we get an ICMP DestinationUnknown error.
63
+ raise "ehostdown"
64
+ rescue Errno::EHOSTUNREACH
65
+ # This is raised if we can't work out how to route traffic.
66
+ raise "ehostunreach"
67
+ rescue Net::SSH::Exception => e
68
+ # This is an internal error in Net::SSH
69
+ raise "internal net ssh error"
70
+ rescue NotImplementedError
71
+ # This is raised if a private key type that Net-SSH doesn't support
72
+ # is used. Show a nicer error.
73
+ raise "unsupported private key type"
74
+ end
75
+
76
+ yield connection
77
+ end
78
+
79
+
80
+ # Start ssh-agent and add a key to the keychain
81
+ # TODO: avoid starting if already running
82
+ def add_to_keychain(private_key_path)
83
+ puts "Starting ssh-agent, adding keypair private key ..."
84
+ puts `eval $(ssh-agent)`
85
+ puts `ssh-add #{private_key_path}`
86
+ end
87
+
88
+
89
+ # TODO: better key handling (bootstrap with public key)
90
+ def bootstrap(host:, username:, private_key_path:, source_path:)
91
+ return unless source_path
92
+
93
+ puts "Bootstrapping instance (#{username}@#{host}) ..."
94
+
95
+ add_to_keychain(private_key_path)
96
+
97
+ tf = Tempfile.new('boxer-shell')
98
+ tf.write(File.read(source_path))
99
+ tf.rewind
100
+
101
+ source_path = tf.path
102
+ dest_path = "/tmp/boxer-shell"
103
+
104
+ Cirrus::SCP.connect(host, username) do |scp|
105
+ scp.upload!(source_path, dest_path)
106
+ end
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,3 @@
1
+ module Cirrus
2
+ VERSION = "0.0.1"
3
+ end
data/lib/cirrus.rb ADDED
@@ -0,0 +1,75 @@
1
+ require 'aws-sdk'
2
+
3
+ module Cirrus
4
+ extend self
5
+
6
+ # Launch an instance with the default Cirrus key pair and security group,
7
+ # provision the instance, and wait for it to launch
8
+ #
9
+ # image_id - String identifier, ex: "ami-ee4f77ab"
10
+ # ec2_opts - Hash of
11
+ # access_key_id - String
12
+ # secret_access_key - String
13
+ # region - String, ex: "us-east-1"
14
+ #
15
+ #
16
+ # Returns nothing
17
+ def setup(image_id, ec2_opts)
18
+ puts "Starting setup for #{ec2_opts[:region]} ..."
19
+
20
+ ec2 = AWS::EC2.new(ec2_opts)
21
+
22
+ key_pair, private_key_filename = Cirrus::KeyPair.find_or_create_default(ec2)
23
+
24
+ security_group = Cirrus::SecurityGroup.find_or_create_default(ec2)
25
+
26
+ instance_type = "m1.small"
27
+ launch_opts = {
28
+ :image_id => image_id,
29
+ instance_type: instance_type,
30
+ :key_pair => key_pair,
31
+ :security_groups => security_group
32
+ # :associate_public_ip_address => true # Only for VPC launch
33
+ }
34
+ tags = { 'Name' => "cirrus-sample-#{Time.now.to_i}" }
35
+ instance = Cirrus::Instance.launch(ec2, launch_opts, tags)
36
+
37
+ host = instance.public_ip_address
38
+
39
+ Cirrus::SSH.bootstrap(
40
+ host: host,
41
+ username: 'ubuntu',
42
+ private_key_path: private_key_filename,
43
+ source_path: nil
44
+ )
45
+
46
+ puts "Done! Provisioned instance #{instance.id} host #{host}, key #{private_key_filename}"
47
+ end
48
+
49
+
50
+ # Terminate an instance and its associated security groups / key pairs
51
+ #
52
+ # instance_id - String id, ex: "i-74f86cbe"
53
+ # ec2_opts - Hash of
54
+ # access_key_id - String
55
+ # secret_access_key - String
56
+ # region - String, ex: "us-east-1"
57
+ #
58
+ # Returns nothing
59
+ def teardown(instance_id, ec2_opts)
60
+ puts "Tearing down #{instance_id} ..."
61
+
62
+ ec2 = AWS::EC2.new(ec2_opts)
63
+ Cirrus::Instance.teardown(ec2, instance_id)
64
+
65
+ puts "Teardown successful for #{instance_id}!"
66
+ end
67
+
68
+ end
69
+
70
+ require "cirrus/version"
71
+ require 'cirrus/instance'
72
+ require 'cirrus/key_pair'
73
+ require 'cirrus/scp'
74
+ require 'cirrus/security_group'
75
+ require 'cirrus/ssh'
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cirrus-aws
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Berls
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ description: ''
28
+ email:
29
+ - andrew.berls@gmail.com
30
+ executables:
31
+ - cirrus
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".gitignore"
36
+ - Gemfile
37
+ - LICENSE.txt
38
+ - README.md
39
+ - bin/cirrus
40
+ - cirrus-aws.gemspec
41
+ - lib/cirrus.rb
42
+ - lib/cirrus/instance.rb
43
+ - lib/cirrus/key_pair.rb
44
+ - lib/cirrus/scp.rb
45
+ - lib/cirrus/security_group.rb
46
+ - lib/cirrus/ssh.rb
47
+ - lib/cirrus/version.rb
48
+ homepage: ''
49
+ licenses:
50
+ - MIT
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 2.4.1
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: A command-line tool to provision and launch AWS EC2 instances
72
+ test_files: []