awssh 0.1.10 → 0.2.0
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 +4 -4
- data/awssh.gemspec +2 -2
- data/bin/awssh +1 -1
- data/lib/awssh/cache.rb +72 -0
- data/lib/awssh/cloud.rb +34 -0
- data/lib/awssh/command.rb +73 -96
- data/lib/awssh/config.rb +42 -0
- data/lib/awssh/search.rb +49 -0
- data/lib/awssh/version.rb +2 -2
- data/lib/awssh.rb +10 -2
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 717e709f2882548951c708971b9c520865b43a90
|
4
|
+
data.tar.gz: dc34e939651504f2d726f4c6286ec7ce8e745300
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61ea4118362d29f203c5840042202222fb39de880040b2237b4d3cd38708a7e9933c28c9874858892697b1adc7b99d7fae720b27e5074c48d1b20c95a1d8aeba
|
7
|
+
data.tar.gz: ccff97b8024852e0b3636443529bb9136a5b8b8701c709134dfe1fe527d4bd1ff9bc8a41b3f0446dea830d50cf92b8ffe290bf4cc9abb78387ba618f0bd67da3
|
data/awssh.gemspec
CHANGED
@@ -18,8 +18,8 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency 'fog', '~> 1.
|
22
|
-
spec.add_dependency 'dotenv', '~> 0.
|
21
|
+
spec.add_dependency 'fog', '~> 1.30.0'
|
22
|
+
spec.add_dependency 'dotenv', '~> 2.0.1'
|
23
23
|
spec.add_dependency 'activesupport'
|
24
24
|
|
25
25
|
spec.add_development_dependency "bundler", "~> 1.6"
|
data/bin/awssh
CHANGED
data/lib/awssh/cache.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Awssh
|
4
|
+
class Cache
|
5
|
+
class << self
|
6
|
+
def load(file)
|
7
|
+
@instance = new(file)
|
8
|
+
end
|
9
|
+
|
10
|
+
def instance
|
11
|
+
raise 'cache not loaded?' unless @instance.data
|
12
|
+
@instance
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :data
|
17
|
+
|
18
|
+
def initialize(file, expires)
|
19
|
+
if file
|
20
|
+
@file = File.expand_path(file)
|
21
|
+
else
|
22
|
+
@disabled = true
|
23
|
+
@file = nil
|
24
|
+
end
|
25
|
+
@expires = expires
|
26
|
+
@data = load
|
27
|
+
end
|
28
|
+
|
29
|
+
def write(key, value)
|
30
|
+
time = Time.now.to_i
|
31
|
+
data = {time: time, value: value}
|
32
|
+
@data[key] = data
|
33
|
+
save
|
34
|
+
end
|
35
|
+
|
36
|
+
def read(key)
|
37
|
+
@data[key][:value]
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch(key, force)
|
41
|
+
if force || @disabled
|
42
|
+
diff = Time.now.to_i
|
43
|
+
else
|
44
|
+
time = @data[key] ? @data[key][:time] : 0
|
45
|
+
diff = Time.now.to_i - time
|
46
|
+
end
|
47
|
+
if diff > @expires
|
48
|
+
value = yield
|
49
|
+
write(key, value)
|
50
|
+
return value
|
51
|
+
else
|
52
|
+
read(key)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def load
|
59
|
+
return {} if @disabled
|
60
|
+
unless File.exists?(@file)
|
61
|
+
@data = {}
|
62
|
+
save
|
63
|
+
end
|
64
|
+
YAML.load_file(@file)
|
65
|
+
end
|
66
|
+
|
67
|
+
def save
|
68
|
+
return if @disabled
|
69
|
+
File.open(@file, "w+") {|f| f.write(@data.to_yaml)}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/awssh/cloud.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Awssh
|
2
|
+
class Cloud
|
3
|
+
class << self
|
4
|
+
def connect(key, secret, region)
|
5
|
+
@instance = new(key, secret, region)
|
6
|
+
end
|
7
|
+
def instance
|
8
|
+
raise "not connected?" unless @instance
|
9
|
+
@instance
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(key, secret, region)
|
14
|
+
@key = key
|
15
|
+
@secret = secret
|
16
|
+
@region = region
|
17
|
+
@fog = Fog::Compute.new(provider: 'AWS', aws_access_key_id: @key, aws_secret_access_key: @secret, region: @region)
|
18
|
+
end
|
19
|
+
|
20
|
+
def servers
|
21
|
+
puts "requesting servers..."
|
22
|
+
list = @fog.servers.all({'instance-state-name' => 'running'})
|
23
|
+
list.inject([]) do |a, e|
|
24
|
+
a << {
|
25
|
+
id: e.id,
|
26
|
+
name: e.tags['Name'],
|
27
|
+
tags: e.tags.inject({}) {|h, e| (k,v) = e; h[k.downcase] = v.downcase; h},
|
28
|
+
private: e.private_ip_address,
|
29
|
+
public: e.public_ip_address,
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/awssh/command.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'pp'
|
1
2
|
module Awssh
|
2
3
|
class Command
|
3
4
|
def initialize(argv)
|
@@ -11,17 +12,15 @@ module Awssh
|
|
11
12
|
@config = {
|
12
13
|
multi: 'csshX',
|
13
14
|
single: 'ssh',
|
14
|
-
region: 'us-east-1',
|
15
15
|
user: nil,
|
16
|
-
|
17
|
-
secret: 'AWS SECRET ACCESS KEY',
|
18
|
-
domain: 'example.com',
|
16
|
+
use_names: false,
|
19
17
|
cache: '~/.awssh.cache',
|
20
18
|
expires: 1.day
|
21
19
|
}.stringify_keys
|
22
20
|
|
23
21
|
@config_file = File.expand_path(@options[:config])
|
24
|
-
|
22
|
+
Awssh::Config.load(@config_file)
|
23
|
+
@config = Awssh::Config.data
|
25
24
|
|
26
25
|
OptionParser.new do |opts|
|
27
26
|
opts.banner = "Usage: awssh [options] [search terms]"
|
@@ -35,14 +34,22 @@ module Awssh
|
|
35
34
|
opts.separator ' name !~ /term/'
|
36
35
|
opts.separator ''
|
37
36
|
opts.separator 'Options:'
|
37
|
+
opts.on('-c', "--config", "override config file (default: ~/.awssh)") do |c|
|
38
|
+
@options[:config] = c
|
39
|
+
end
|
38
40
|
opts.on('-V', '--version', 'print version') do |v|
|
39
41
|
puts "awssh version: #{Awssh::Version::STRING}"
|
40
42
|
exit 0
|
41
43
|
end
|
42
44
|
opts.on('-i', '--init', 'initialize config') do |i|
|
43
|
-
path = File.expand_path(
|
44
|
-
|
45
|
-
|
45
|
+
path = File.expand_path(@options[:config])
|
46
|
+
puts "creating config file: #{path}"
|
47
|
+
if File.exists?(path)
|
48
|
+
backup = "#{path}.#{Time.now.to_i}"
|
49
|
+
puts "moving previous config to #{backup}"
|
50
|
+
FileUtils.mv(path, backup)
|
51
|
+
end
|
52
|
+
File.open(path, "w+") { |f| f.write Awssh::Config::DEFAULT }
|
46
53
|
exit 0
|
47
54
|
end
|
48
55
|
opts.separator ''
|
@@ -59,134 +66,104 @@ module Awssh
|
|
59
66
|
opts.separator ''
|
60
67
|
opts.on('-U', '--update', 'just update the cache') do |u|
|
61
68
|
@options[:update] = true
|
62
|
-
get_servers
|
63
|
-
exit 0
|
64
69
|
end
|
65
70
|
opts.on('--no-cache', 'disable cache for this run') do |u|
|
66
|
-
@config
|
71
|
+
@config.cache = false
|
67
72
|
end
|
68
73
|
opts.separator ''
|
69
74
|
opts.on('-m', '--[no-]multi', 'connect to multiple servers') do |m|
|
70
75
|
@options[:multi] = m
|
71
76
|
end
|
72
|
-
opts.on('-c', "--config", "override config file (default: ~/.awssh)") do |c|
|
73
|
-
@options[:config] = c
|
74
|
-
end
|
75
77
|
opts.on('-u', '--user USER', 'override user setting') do |u|
|
76
|
-
@config
|
78
|
+
@config.user = u
|
77
79
|
end
|
78
80
|
end.parse!(argv)
|
79
81
|
|
82
|
+
@cloud = Awssh::Cloud.connect(@config.key, @config.secret, @config.region)
|
83
|
+
@cache = Awssh::Cache.new(@config.cache, @config.expires||1.day)
|
80
84
|
@search = argv
|
81
85
|
|
86
|
+
if @options[:update]
|
87
|
+
cache(:servers, true) { @cloud.servers }
|
88
|
+
exit 0
|
89
|
+
end
|
90
|
+
|
82
91
|
if @options[:verbose]
|
83
|
-
|
84
|
-
|
85
|
-
p @config
|
92
|
+
puts "options: #{@options.inspect}"
|
93
|
+
puts "config: #{@config.inspect}"
|
86
94
|
end
|
87
95
|
end
|
88
96
|
|
89
|
-
def
|
90
|
-
@servers =
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
end
|
95
|
-
return if @options[:list]
|
97
|
+
def run
|
98
|
+
@servers = cache(:servers) { @cloud.servers }
|
99
|
+
search = Awssh::Search.new(@servers, @search)
|
100
|
+
list = search.filter
|
101
|
+
hosts = hosts(list)
|
96
102
|
|
97
|
-
if
|
98
|
-
|
99
|
-
puts "more than one server found, and multi is false"
|
100
|
-
puts "set the -m flag to connect to more than one matched server"
|
103
|
+
if hosts.count == 0
|
104
|
+
puts "no servers found."
|
101
105
|
exit 1
|
102
106
|
end
|
103
107
|
|
104
|
-
|
105
|
-
|
108
|
+
multi_not_multi = (hosts.count > 1 && !@options[:multi])
|
109
|
+
|
110
|
+
if @options[:list] || @options[:verbose] || multi_not_multi
|
111
|
+
puts_hosts(hosts)
|
112
|
+
end
|
113
|
+
puts "#{hosts.count} servers found" if @options[:verbose]
|
114
|
+
exit 0 if @options[:list]
|
115
|
+
if multi_not_multi
|
116
|
+
puts "more than one server found and multi is false"
|
117
|
+
puts "use the -m flag to connect to multiple servers"
|
106
118
|
exit 1
|
107
119
|
end
|
108
120
|
|
109
|
-
|
110
|
-
|
111
|
-
puts "running: #{@command}"
|
112
|
-
exec @command unless @options[:test]
|
113
|
-
end
|
114
|
-
|
115
|
-
private
|
116
|
-
|
117
|
-
def print_list
|
118
|
-
puts "found: #{@servers.count}"
|
119
|
-
@servers.each do |s|
|
120
|
-
puts "- #{s}"
|
121
|
-
end
|
121
|
+
connect(hosts)
|
122
122
|
end
|
123
123
|
|
124
|
-
def
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
@search.each do |v|
|
129
|
-
if v =~ /^\^/
|
130
|
-
fail = true if n =~ /#{v.gsub(/^\^/, '')}/
|
131
|
-
else
|
132
|
-
fail = true unless n =~ /#{v}/
|
133
|
-
end
|
134
|
-
end
|
135
|
-
next if fail
|
136
|
-
servers << n
|
124
|
+
def connect(hosts)
|
125
|
+
cmd = command(hosts)
|
126
|
+
if @options[:test] || @options[:verbose]
|
127
|
+
puts cmd
|
137
128
|
end
|
138
|
-
|
129
|
+
exec(cmd) unless @options[:test]
|
139
130
|
end
|
140
131
|
|
141
|
-
def
|
142
|
-
|
143
|
-
|
132
|
+
def command(hosts)
|
133
|
+
if @options[:multi]
|
134
|
+
command = "#{@config.multi} #{hosts.map { |e| host(e) }.join(' ')}"
|
135
|
+
else
|
136
|
+
command = "#{@config.single} #{host(servers.first)}"
|
144
137
|
end
|
145
|
-
|
146
|
-
list.sort
|
138
|
+
command
|
147
139
|
end
|
148
140
|
|
149
|
-
def
|
150
|
-
|
151
|
-
file = File.expand_path(@config['cache'])
|
152
|
-
if File.exists?(file)
|
153
|
-
unless @options[:update]
|
154
|
-
if Time.now - File.mtime(file) < @config['expires']
|
155
|
-
return YAML.load_file(file)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
puts "updating cache ..."
|
160
|
-
list = yield
|
161
|
-
File.open(file, "w+") { |f| f.write list.to_yaml }
|
162
|
-
return list
|
163
|
-
end
|
164
|
-
list = yield
|
165
|
-
return list
|
141
|
+
def cache(key, force=!@config.cache, &block)
|
142
|
+
@cache.fetch(key, force, &block)
|
166
143
|
end
|
167
144
|
|
168
|
-
def
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
145
|
+
def hosts(list)
|
146
|
+
list.map do |l|
|
147
|
+
(id,_) = l.split('||')
|
148
|
+
@servers.to_a.detect {|e| e[:id] == id}
|
149
|
+
end.compact.sort_by {|e| e[:name]}
|
173
150
|
end
|
174
151
|
|
175
|
-
def
|
176
|
-
|
177
|
-
|
152
|
+
def host(host)
|
153
|
+
out = []
|
154
|
+
out << "#{@config.user}@" if @config.user
|
155
|
+
if @config.use_names
|
156
|
+
out << [host[:name], @config.domain].compact.join('.')
|
178
157
|
else
|
179
|
-
|
158
|
+
out << host[:private]
|
180
159
|
end
|
181
|
-
|
160
|
+
out.join('')
|
182
161
|
end
|
183
162
|
|
184
|
-
def
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
out << ".#{@config["domain"]}" if @config["domain"]
|
189
|
-
out.join('')
|
163
|
+
def puts_hosts(hosts)
|
164
|
+
hosts.each do |host|
|
165
|
+
puts "%10s %-15s %s" % [host[:id], host[:private], host[:name]]
|
166
|
+
end
|
190
167
|
end
|
191
168
|
end
|
192
169
|
end
|
data/lib/awssh/config.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Awssh
|
4
|
+
class Config
|
5
|
+
class << self
|
6
|
+
def load(file)
|
7
|
+
@instance = new(file)
|
8
|
+
end
|
9
|
+
|
10
|
+
def data
|
11
|
+
raise 'config not loaded?' unless @instance.data
|
12
|
+
@instance.data
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :data
|
17
|
+
|
18
|
+
def initialize(file)
|
19
|
+
@file = file
|
20
|
+
raise "config file does not exist: #{file}" unless File.exist?(file)
|
21
|
+
@data = OpenStruct.new(YAML.load_file(file))
|
22
|
+
end
|
23
|
+
|
24
|
+
DEFAULT = <<-EOF
|
25
|
+
---
|
26
|
+
region: us-east-1 # AWS Region
|
27
|
+
key: AWS_ACCESS_KEY_ID # AWS access key id
|
28
|
+
secret: AWS_SECRET_ACCESS_KEY # AWS secret access key
|
29
|
+
multi: csshX # command to use when connecting to multiple servers
|
30
|
+
single: ssh # command to use when connecting to single server
|
31
|
+
#user: username # set user for connection to all servers
|
32
|
+
# this can be overridden on the command line
|
33
|
+
domain: example.com # if 'use_names' is set, this will be appended
|
34
|
+
# to names, leave blank if name is fully-qualified
|
35
|
+
use_names: false # if true, rather than connecting to IP's,
|
36
|
+
# connection strings will be created using Name
|
37
|
+
# tag and domain
|
38
|
+
cache: ~/.awssh.cache # the cache file, set to false to disable caching
|
39
|
+
expires: 86400 # cache expiration time in seconds
|
40
|
+
EOF
|
41
|
+
end
|
42
|
+
end
|
data/lib/awssh/search.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Awssh
|
2
|
+
class Search
|
3
|
+
def initialize(servers, terms)
|
4
|
+
@db = db(servers)
|
5
|
+
@terms = convert(terms)
|
6
|
+
end
|
7
|
+
|
8
|
+
def filter
|
9
|
+
list = @db
|
10
|
+
@terms.each do |key, value, opts|
|
11
|
+
regex = key == 'name' ? /\sname:[^\s]*#{value}[^\s]*/ : /\s#{key}:#{value}/
|
12
|
+
puts "regex: #{regex}"
|
13
|
+
if opts[:inverse]
|
14
|
+
found = list.grep(regex)
|
15
|
+
list = list - found
|
16
|
+
else
|
17
|
+
list = list.grep(regex)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
list
|
21
|
+
end
|
22
|
+
|
23
|
+
def convert(terms)
|
24
|
+
terms.inject([]) do |a, e|
|
25
|
+
opts = {}
|
26
|
+
term = e.downcase
|
27
|
+
if term =~ /\:/
|
28
|
+
(key, value) = term.split(':')
|
29
|
+
else
|
30
|
+
key = 'name'
|
31
|
+
value = term
|
32
|
+
if term =~ /^\^/
|
33
|
+
value = term.gsub(/^\^/, '')
|
34
|
+
key = "^#{k}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
if key =~ /^\^/
|
38
|
+
opts[:inverse] = true
|
39
|
+
key.gsub!(/^\^/)
|
40
|
+
end
|
41
|
+
a << [key, value, opts]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def db(servers)
|
46
|
+
servers.inject([]) { |a, s| a << "#{s[:id]}|| #{s[:tags].inject([]) { |a, e| a << e.join(':') }.join(' ')}" }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/awssh/version.rb
CHANGED
data/lib/awssh.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: awssh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shawn Catanzarite
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-05-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fog
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.30.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
26
|
+
version: 1.30.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: dotenv
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 2.0.1
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 2.0.1
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: activesupport
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -96,7 +96,11 @@ files:
|
|
96
96
|
- awssh.gemspec
|
97
97
|
- bin/awssh
|
98
98
|
- lib/awssh.rb
|
99
|
+
- lib/awssh/cache.rb
|
100
|
+
- lib/awssh/cloud.rb
|
99
101
|
- lib/awssh/command.rb
|
102
|
+
- lib/awssh/config.rb
|
103
|
+
- lib/awssh/search.rb
|
100
104
|
- lib/awssh/version.rb
|
101
105
|
homepage: ''
|
102
106
|
licenses:
|
@@ -118,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
122
|
version: '0'
|
119
123
|
requirements: []
|
120
124
|
rubyforge_project:
|
121
|
-
rubygems_version: 2.
|
125
|
+
rubygems_version: 2.4.7
|
122
126
|
signing_key:
|
123
127
|
specification_version: 4
|
124
128
|
summary: aws ssh wrapper
|