cirrus-aws 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []