exos 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 +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/Makefile +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/VERSION +1 -0
- data/bin/exos +5 -0
- data/exos.gemspec +29 -0
- data/lib/exos.rb +25 -0
- data/lib/exos/commands.rb +31 -0
- data/lib/exos/commands/env.rb +0 -0
- data/lib/exos/commands/keys.rb +288 -0
- data/lib/exos/commands/logs.rb +21 -0
- data/lib/exos/commands/ssh.rb +64 -0
- data/lib/exos/commands/status.rb +99 -0
- data/lib/exos/version.rb +3 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
@@ -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
data/LICENSE.txt
ADDED
@@ -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.
|
data/Makefile
ADDED
@@ -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
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bin/exos
ADDED
data/exos.gemspec
ADDED
@@ -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
|
data/lib/exos.rb
ADDED
@@ -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
|
data/lib/exos/version.rb
ADDED
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: []
|