exos 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 93d4886d90a636e12a7970c05205cfaa113c8106
4
+ data.tar.gz: 5a53724348f649687a9fdecbe883474485f141f0
5
+ SHA512:
6
+ metadata.gz: 3534cc7a278bf63ba52fb85049b62793dfc73b88a3dbd5dff9961a9d39f2aac775c27a816399b0028b411e9ba90dd136b0e442d8dce854fb5c410080c6097375
7
+ data.tar.gz: a7ff4bc512cedd2d29f4b8b1efb48fd93c2508b6c2eca4d6997b9351c0288bc9750cca67c017f0c760b30297bf41a4c794b7a9bfb046f8d6c99aec1f33693e14
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in exos.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Piers Mainwaring
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.
@@ -0,0 +1,22 @@
1
+ VERSION=$(shell cat VERSION)
2
+
3
+ .PHONY: default all clean install
4
+ default: clean all
5
+ all: build install alias
6
+
7
+ build: lib/exos/version.rb
8
+ gem build exos.gemspec
9
+
10
+ lib/exos/version.rb:
11
+ mkdir -p $(@D)
12
+ @echo 'module Exos\n VERSION = "$(VERSION)"\nend' > $@
13
+
14
+ install:
15
+ gem install exos-$(VERSION).gem --no-rdoc --no-ri
16
+ rbenv rehash
17
+
18
+ alias:
19
+ alias exos=/Users/piers/.rbenv/shims/exos
20
+
21
+ clean:
22
+ rm -rf lib/exos/version.rb
@@ -0,0 +1,29 @@
1
+ # Exos
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'exos'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install exos
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/exos/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+
4
+ require "exos"
5
+ ::Exos::Application.new.run if $0 == __FILE__
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'exos/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "exos"
8
+ spec.version = Exos::VERSION
9
+ spec.authors = ["Piers Mainwaring"]
10
+ spec.email = ["piers@impossibly.org"]
11
+ spec.summary = %q{Write a short summary. Required.}
12
+ spec.description = %q{Write a longer description. Optional.}
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_runtime_dependency 'commander', '~> 4.2'
22
+ spec.add_runtime_dependency 'terminal-table', '~> 1.4'
23
+ spec.add_runtime_dependency 'fog', '~> 1.23'
24
+ spec.add_runtime_dependency 'mina', '~> 0.3'
25
+ spec.add_runtime_dependency 'colorize', '~> 0.7'
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.6"
28
+ spec.add_development_dependency "rake", "~> 10.1"
29
+ end
@@ -0,0 +1,25 @@
1
+ require "commander"
2
+ require "terminal-table"
3
+ require "colorize"
4
+ require "fog"
5
+
6
+ require "exos/version"
7
+
8
+ require "exos/commands"
9
+ require "exos/commands/status"
10
+ require "exos/commands/ssh"
11
+ require "exos/commands/keys"
12
+
13
+ module Exos
14
+ class Application
15
+ include ::Commander::Methods
16
+
17
+ def run
18
+ program :name, "Exos"
19
+ program :version, ::Exos::VERSION
20
+ program :description, "Exos controller."
21
+
22
+ run!
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ module Exos
2
+ module Commands
3
+ class Command
4
+
5
+ class << self
6
+ include ::Commander::Methods
7
+ end
8
+
9
+ attr_reader :args, :options
10
+
11
+ def initialize(args, options)
12
+ @args = args
13
+ @options = options
14
+ end
15
+
16
+ private
17
+
18
+ def ec2
19
+ @ec2 ||= ::Fog::Compute::AWS.new
20
+ end
21
+
22
+ def elb
23
+ @elb ||= ::Fog::AWS::ELB.new
24
+ end
25
+
26
+ def self.humanize(str)
27
+ str.gsub("_", " ").gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
28
+ end
29
+ end
30
+ end
31
+ end
File without changes
@@ -0,0 +1,288 @@
1
+ require 'json'
2
+
3
+ module Exos
4
+ module Commands
5
+ class Keys < Command
6
+
7
+ command :keys do |c|
8
+ c.syntax = "exos keys [--grant email_address] [--revoke email_address [--delete]] [--role bastion_role]"
9
+
10
+ c.option "--grant email_address", String, "Add access for user with email_address."
11
+ c.option "--revoke email_address", String, "Revoke access for user with email_address."
12
+ c.option "--delete", "Delete key entry permanently. Defaults to false (comments-out instead)."
13
+ c.option "--role bastion_role", String, "Value of 'Role' tag for bastion hosts. Defaults to 'ssh'."
14
+ c.option "--list", "Default action. Lists email addresses and public keys of users with access."
15
+
16
+ c.description = "Adds or removes access by email address and public key on bastion servers."
17
+ c.action do |args, options|
18
+ options.default role: "ssh", delete: false
19
+ new(args, options).run
20
+ end
21
+ end
22
+
23
+ PATTERN_KEY_LINE = /ssh-rsa/
24
+ PATTERN_ATTRS_LINE = /\A### exos-key (.*)/
25
+
26
+ LOCKFILE_NAME = "exos.keys.lock"
27
+
28
+ def run
29
+ authorized_keys.backup!
30
+ Bastions.role = options.role
31
+
32
+ if email = options.grant
33
+ add_key(email)
34
+ elsif email = options.revoke
35
+ remove_key(email, options.delete)
36
+ else
37
+ print_keys_list
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def authorized_keys
44
+ @authorized_keys ||= AuthorizedKeys.new
45
+ end
46
+
47
+ def print_keys_list
48
+ table = ::Terminal::Table.new({
49
+ title: "Users with access",
50
+ headings: ["Email address", "Status", "Created at", "Updated at", "Public key"]
51
+ })
52
+
53
+ authorized_keys.each do |_, auth_key|
54
+ # Allow administrative keys to be hidden.
55
+ next if auth_key.status == "IGNORE"
56
+
57
+ cells = [
58
+ auth_key.email,
59
+ auth_key.status,
60
+ auth_key.created_at,
61
+ auth_key.updated_at,
62
+ auth_key.key_fragment
63
+ ].map do |cell|
64
+ cell.to_s.send( auth_key.active? ? :green : :red )
65
+ end
66
+
67
+ table << cells
68
+ end
69
+
70
+ puts table
71
+ end
72
+
73
+ def add_key(email)
74
+ if found_key = authorized_keys[email]
75
+ found_key.activate
76
+ else
77
+ key_text = ask("Enter public key:")
78
+ authorized_keys.add( Key.new(email: email, key: key_text) )
79
+ end
80
+
81
+ authorized_keys.persist!
82
+
83
+ print_keys_list
84
+ end
85
+
86
+ def remove_key(email, delete)
87
+ if found_key = authorized_keys[email]
88
+ if delete
89
+ authorized_keys.remove(found_key)
90
+ else
91
+ found_key.deactivate
92
+ end
93
+
94
+ authorized_keys.persist!
95
+ else
96
+ abort "No key found."
97
+ end
98
+
99
+ print_keys_list
100
+ end
101
+
102
+
103
+ class AuthorizedKeys < Hash
104
+ def add(key)
105
+ self[key.email] = key
106
+ end
107
+
108
+ def remove(key)
109
+ delete(key.email)
110
+ end
111
+
112
+ def to_s
113
+ reduce("") do |out, (_, key)|
114
+ out << key.to_s
115
+ end
116
+ end
117
+
118
+ def persist!
119
+ Bastions.exec("echo '#{to_s}' > ~/.ssh/authorized_keys")
120
+ end
121
+
122
+ def backup!
123
+ File.open( File.expand_path("~/.exos-keys"), "w" ) { |file| file << raw_keys }
124
+ end
125
+
126
+ def initialize(*)
127
+ super
128
+
129
+ lines = raw_keys.lines.map(&:chomp).reject(&:empty?)
130
+
131
+ idx = 0
132
+ while idx < lines.count
133
+ attrs_line = lines[idx]
134
+
135
+ unless attrs_line.match(/\A### exos-key/)
136
+ idx += 1
137
+ next
138
+ end
139
+
140
+ key_line = lines[idx + 1]
141
+ add( Key.parse(attrs_line, key_line) )
142
+ idx += 2
143
+ end
144
+ end
145
+
146
+ private
147
+
148
+ def raw_keys
149
+ @raw_keys ||= Bastions.exec("cat ~/.ssh/authorized_keys").stdout
150
+ end
151
+
152
+ def reset!
153
+ @raw_keys = nil
154
+ @all = nil
155
+ end
156
+ end
157
+
158
+
159
+ class Key
160
+ attr_accessor :email, :key, :created_at, :updated_at, :status
161
+
162
+ def self.parse(attrs_line, key_line)
163
+ if attrs_line.match(PATTERN_ATTRS_LINE)
164
+ attrs = JSON.parse($1)
165
+
166
+ new_key = new(
167
+ email: attrs["email"],
168
+ status: attrs["status"],
169
+ created_at: attrs["created_at"],
170
+ updated_at: attrs["updated_at"]
171
+ )
172
+
173
+ if key_line.match(PATTERN_KEY_LINE)
174
+ new_key.key = key_line.chomp
175
+ end
176
+
177
+ new_key
178
+ end
179
+ end
180
+
181
+ def initialize(attrs = {})
182
+ self.email = attrs.fetch(:email)
183
+ self.key = attrs.fetch(:key, nil)
184
+ self.status = attrs.fetch(:status, "active")
185
+ self.created_at = attrs.fetch(:created_at, Time.now)
186
+ self.updated_at = attrs.fetch(:updated_at, Time.now)
187
+ end
188
+
189
+ def key=(text)
190
+ if text && !text.match(/\A(# )?ssh-rsa .+/)
191
+ abort "Invalid key."
192
+ end
193
+
194
+ @key = text
195
+ end
196
+
197
+ def key_fragment
198
+ "..." + uncomment(self.key).split[1][-20..-1] if self.key
199
+ end
200
+
201
+ def attributes
202
+ {
203
+ email: self.email,
204
+ status: self.status,
205
+ created_at: self.created_at,
206
+ updated_at: self.updated_at
207
+ }
208
+ end
209
+
210
+ def touch
211
+ self.updated_at = Time.now
212
+ end
213
+
214
+ def valid?
215
+ !!self.email && !!self.key
216
+ end
217
+
218
+ def active?
219
+ self.status == "active"
220
+ end
221
+
222
+ def activate
223
+ self.status = "active"
224
+ self.key = uncomment(self.key)
225
+ end
226
+
227
+ def deactivate
228
+ self.status = "inactive"
229
+ self.key = comment(self.key)
230
+ end
231
+
232
+ def comment(key); "# #{key}"; end
233
+ def uncomment(key); self.key.sub(/\A# /, ""); end
234
+
235
+ def to_s
236
+ escape( ["", "### exos-key #{ JSON.dump(attributes) }", self.key, ""].join("\n") )
237
+ end
238
+
239
+ def escape(text)
240
+ text.gsub("'", "\'")
241
+ end
242
+ end
243
+
244
+
245
+ class Bastions
246
+ class << self
247
+ attr_accessor :role
248
+
249
+ def all
250
+ @all ||= begin
251
+ bastions = ec2.servers.all("tag:Role" => role).map do |bastion|
252
+ bastion.username = "deploy"
253
+ bastion
254
+ end
255
+
256
+ abort "No bastions found with role '#{ options.role }'." unless bastions.any?
257
+ bastions
258
+ end
259
+ end
260
+
261
+ def exec(*cmds)
262
+ results = []
263
+
264
+ all.each do |bastion|
265
+ if bastion.ssh("ls #{ LOCKFILE_NAME }").first.status == 0
266
+ abort "Another `exos keys` operation is in progress."
267
+ end
268
+
269
+ commands = ["touch #{ LOCKFILE_NAME }", *cmds, "rm #{ LOCKFILE_NAME }"]
270
+ results << bastion.ssh(commands)
271
+ end
272
+
273
+ results.last[-2]
274
+ # Ensure to handle Fog::SSH raising on SSH connection errors.
275
+ ensure
276
+ all.each { |b| b.ssh("rm #{ LOCKFILE_NAME }") }
277
+ end
278
+
279
+ def ec2
280
+ @ec2 ||= ::Fog::Compute::AWS.new
281
+ end
282
+
283
+ end
284
+ end
285
+
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,21 @@
1
+ module Exos
2
+ module Commands
3
+ class Logs < Command
4
+
5
+ command :logs do |c|
6
+ c.syntax = "exos logs [--tail]"
7
+
8
+ c.option "-t", "--tail", "Stream logs."
9
+
10
+ c.description = "."
11
+ c.action do |args, options|
12
+ new(args, options).run
13
+ end
14
+ end
15
+
16
+ def run
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,64 @@
1
+ module Exos
2
+ module Commands
3
+ class SSH < Command
4
+
5
+ command :ssh do |c|
6
+ c.syntax = "exos ssh [-i instance_id] [-n instance_name] [-r instance_role] [-u user] [username@instance_name|instance_id]"
7
+
8
+ c.option "-i", "--id instance_id", String, "Connects to the instance with this ID."
9
+ c.option "-n", "--name instance_name", String, "Connects to the instance with this 'Name' tag."
10
+ c.option "-r", "--role instance_role", String, "Connects to an instance with this 'Role' tag. Default is 'ssh'."
11
+ c.option "-u", "--user username", String, "Remote username to connect with."
12
+ c.option "-p", "--proto protocol", String, "Forces protocol to either 'mosh' or 'ssh'."
13
+
14
+ c.description = "Connects to the the specified instance. Tries to connect with mosh, then SSH."
15
+ c.action do |args, options|
16
+ options.default role: "ssh"
17
+ new(args, options).run
18
+ end
19
+ end
20
+
21
+ PROTOCOLS = %w(mosh ssh).freeze
22
+
23
+ def run
24
+ # Handle "connection string" style parameter.
25
+ if conn = args.first
26
+ user, str = conn.split("@")
27
+
28
+ abort "Malformed parameter: user@[instance-name|instance-id]." if str.nil?
29
+
30
+ str.match(/i-[0-9a-f]+/i) ? options.id = str : options.name = str
31
+ else
32
+ user = options.user
33
+ end
34
+
35
+ abort "Must specify username with -u or 'user@instance-name' parameter." if user.nil?
36
+
37
+ if options.id
38
+ instance = ec2.servers.all("instance-id" => options.id).first
39
+ elsif options.name
40
+ instance = ec2.servers.all("tag:Name" => options.name).first
41
+ else
42
+ instance = ec2.servers.all("tag:Role" => options.role).first
43
+ end
44
+
45
+ abort "No instance found. Exiting." if instance.nil?
46
+
47
+ puts "Connecting to instance #{ instance.id }..."
48
+ host = "#{ user }@#{ instance.dns_name }"
49
+
50
+ if options.proto
51
+ abort "Unknown protocol '#{ options.proto }'." unless PROTOCOLS.include?(options.proto)
52
+ exec "#{ options.proto } #{ host }"
53
+ else
54
+ begin
55
+ exec "mosh #{ host }"
56
+ rescue Errno::ENOENT
57
+ exec "ssh #{ host }"
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,99 @@
1
+ module Exos
2
+ module Commands
3
+ class Status < Command
4
+
5
+ command :status do |c|
6
+ c.syntax = "exos status"
7
+ c.description = "Prints the current status of configured VPCs, subnets, and EC2 instances."
8
+ c.action do |args, options|
9
+ new(args, options).run
10
+ end
11
+ end
12
+
13
+ def run
14
+ @vpcs = ec2.vpcs
15
+ @subnets = ec2.subnets
16
+ @servers = ec2.servers
17
+ @elbs = elb.load_balancers
18
+
19
+ print_vpc_tables
20
+ print_servers_table
21
+ end
22
+
23
+ def print_vpc_tables
24
+ @vpcs.each do |vpc|
25
+ table = ::Terminal::Table.new(title: "VPC Status")
26
+
27
+ add_section_headers(table, ["VPC ID", "Name", "State", "CIDR Block", "Tenancy"])
28
+ color = vpc.state == "available" ? :green : :light_red
29
+ add_row(table, color, [
30
+ vpc.id,
31
+ vpc.tags["Name"] || vpc.tags["name"],
32
+ vpc.state,
33
+ vpc.cidr_block,
34
+ vpc.tenancy
35
+ ])
36
+
37
+ subnets = @subnets.select do |subnet|
38
+ subnet.vpc_id == vpc.id
39
+ end
40
+
41
+ return unless subnets.any?
42
+
43
+ add_section_headers(table, ["Subnet ID", "Name", "State", "CIDR Block", "Availability Zone"])
44
+ subnets.each do |subnet|
45
+ color = subnet.ready? ? :green : :light_red
46
+ add_row(table, color, [
47
+ subnet.subnet_id,
48
+ subnet.tag_set["Name"] || subnet.tag_set["name"],
49
+ subnet.state,
50
+ subnet.cidr_block,
51
+ subnet.availability_zone
52
+ ])
53
+ end
54
+
55
+ puts table
56
+ end
57
+ end
58
+
59
+ def print_servers_table
60
+ table = ::Terminal::Table.new(title: "Instances")
61
+ groups = @servers.group_by(&:subnet_id)
62
+
63
+ add_section_headers(table, ["ID", "Name", "Type", "State", "Public IP", "Private IP", "Subnet Name"])
64
+
65
+ groups.each_pair do |subnet_id, servers|
66
+ servers.each do |server|
67
+ color = server.ready? ? :green : :light_red
68
+ subnet = @subnets.find { |s| s.subnet_id == subnet_id }
69
+ subnet_name = subnet.tag_set["Name"] || subnet.tag_set["name"] if subnet
70
+
71
+ add_row(table, color, [
72
+ server.id,
73
+ server.tags["Name"] || server.tags["name"],
74
+ server.flavor_id,
75
+ server.state,
76
+ server.public_ip_address,
77
+ server.private_ip_address,
78
+ subnet_name
79
+ ])
80
+ end
81
+ end
82
+
83
+ puts table
84
+ end
85
+
86
+ private
87
+
88
+ def add_row(table, color_method, values)
89
+ table << values.map { |v| (v || "").send(color_method) }
90
+ end
91
+
92
+ def add_section_headers(table, headers)
93
+ table.add_separator if table.number_of_columns > 0
94
+ table << headers
95
+ table.add_separator
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ module Exos
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: exos
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Piers Mainwaring
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: commander
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: terminal-table
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fog
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.23'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.23'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mina
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: colorize
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.7'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.6'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.6'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '10.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '10.1'
111
+ description: Write a longer description. Optional.
112
+ email:
113
+ - piers@impossibly.org
114
+ executables:
115
+ - exos
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - Makefile
123
+ - README.md
124
+ - Rakefile
125
+ - VERSION
126
+ - bin/exos
127
+ - exos.gemspec
128
+ - lib/exos.rb
129
+ - lib/exos/commands.rb
130
+ - lib/exos/commands/env.rb
131
+ - lib/exos/commands/keys.rb
132
+ - lib/exos/commands/logs.rb
133
+ - lib/exos/commands/ssh.rb
134
+ - lib/exos/commands/status.rb
135
+ - lib/exos/version.rb
136
+ homepage: ''
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.2.2
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: Write a short summary. Required.
160
+ test_files: []