ec2ssh 2.0.8 → 3.0.0.beta1
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/.gitignore +15 -5
- data/.travis.yml +1 -1
- data/ChangeLog.md +0 -5
- data/Gemfile +4 -2
- data/Guardfile +1 -5
- data/README.md +26 -90
- data/Rakefile +1 -0
- data/example/example.ec2ssh +17 -0
- data/lib/ec2ssh/builder.rb +29 -0
- data/lib/ec2ssh/cli.rb +66 -91
- data/lib/ec2ssh/command.rb +25 -0
- data/lib/ec2ssh/command/init.rb +72 -0
- data/lib/ec2ssh/command/migrate.rb +34 -0
- data/lib/ec2ssh/command/remove.rb +20 -0
- data/lib/ec2ssh/command/update.rb +39 -0
- data/lib/ec2ssh/dsl.rb +55 -0
- data/lib/ec2ssh/ec2_instances.rb +38 -0
- data/lib/ec2ssh/exceptions.rb +9 -0
- data/lib/ec2ssh/migrator.rb +74 -0
- data/lib/ec2ssh/ssh_config.rb +5 -6
- data/lib/ec2ssh/version.rb +1 -1
- data/spec/lib/ec2ssh/builder_spec.rb +67 -0
- data/spec/lib/ec2ssh/command/init_spec.rb +41 -0
- data/spec/lib/ec2ssh/command/migrate_spec.rb +108 -0
- data/spec/lib/ec2ssh/command/remove_spec.rb +68 -0
- data/spec/lib/ec2ssh/command/update_spec.rb +97 -0
- data/spec/lib/ec2ssh/dsl_spec.rb +33 -0
- data/spec/lib/ec2ssh/migrator_spec.rb +59 -0
- data/spec/lib/ec2ssh/ssh_config_spec.rb +70 -54
- data/spec/spec_helper.rb +10 -8
- metadata +30 -8
- data/lib/ec2ssh/dotfile.rb +0 -49
- data/lib/ec2ssh/hosts.rb +0 -42
- data/spec/lib/ec2ssh/cli_spec.rb +0 -171
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'ec2ssh/dsl'
|
2
|
+
|
3
|
+
module Ec2ssh
|
4
|
+
module Command
|
5
|
+
class Base
|
6
|
+
attr_reader :cli
|
7
|
+
|
8
|
+
def initialize(cli)
|
9
|
+
@cli = cli
|
10
|
+
end
|
11
|
+
|
12
|
+
def dotfile
|
13
|
+
@dotfile ||= Ec2ssh::Dsl::Parser.parse_file(dotfile_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def ssh_config_path
|
17
|
+
cli.options.path || dotfile.path || "#{$ENV['HOME']}/.ssh/config"
|
18
|
+
end
|
19
|
+
|
20
|
+
def dotfile_path
|
21
|
+
cli.options.dotfile
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'ec2ssh/command'
|
2
|
+
require 'ec2ssh/ssh_config'
|
3
|
+
require 'ec2ssh/exceptions'
|
4
|
+
|
5
|
+
module Ec2ssh
|
6
|
+
module Command
|
7
|
+
class Init < Base
|
8
|
+
def initialize(cli)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
begin
|
14
|
+
init_ssh_config
|
15
|
+
rescue DotfileNotFound
|
16
|
+
init_dotfile
|
17
|
+
retry
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def init_dotfile
|
22
|
+
if File.exist?(dotfile_path)
|
23
|
+
cli.yellow "Warning: #{dotfile_path} already exists."
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
write_dotfile_example
|
28
|
+
|
29
|
+
cli.green "Generated #{dotfile_path}"
|
30
|
+
cli.yellow "Please check and edit #{dotfile_path} before run `ec2ssh update`"
|
31
|
+
end
|
32
|
+
|
33
|
+
def write_dotfile_example
|
34
|
+
example = <<-DOTFILE
|
35
|
+
path '#{ENV['HOME']}/.ssh/config'
|
36
|
+
aws_keys(
|
37
|
+
default: {
|
38
|
+
access_key_id: ENV['AWS_ACCESS_KEY_ID'],
|
39
|
+
secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
|
40
|
+
},
|
41
|
+
# my_key1: { access_key_id: '...', secret_access_key: '...' }, ...
|
42
|
+
)
|
43
|
+
regions ENV['AWS_REGION'] || ENV['AMAZON_REGION'] || ENV['AWS_DEFAULT_REGION'] || 'us-east-1'
|
44
|
+
# Enable regions as you like
|
45
|
+
# regions *%w(ap-northeast-1 ap-southeast-1 ap-southeast-2 eu-west-1 sa-east-1 us-east-1 us-west-1 us-west-2)
|
46
|
+
|
47
|
+
# You can use methods of AWS::EC2::Instance.
|
48
|
+
# See http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/Instance.html
|
49
|
+
host_line <<END
|
50
|
+
Host <%= tags['Name'] %>.<%= availability_zone %>
|
51
|
+
HostName <%= dns_name || private_ip_address %>
|
52
|
+
END
|
53
|
+
DOTFILE
|
54
|
+
|
55
|
+
File.open(dotfile_path, 'w') {|f| f.write example }
|
56
|
+
end
|
57
|
+
|
58
|
+
def init_ssh_config
|
59
|
+
if ssh_config.mark_exist?
|
60
|
+
raise MarkAlreadyExists
|
61
|
+
else
|
62
|
+
ssh_config.append_mark!
|
63
|
+
cli.green "Added mark to #{ssh_config_path}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def ssh_config
|
68
|
+
@ssh_config ||= SshConfig.new(ssh_config_path)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'ec2ssh/exceptions'
|
2
|
+
require 'ec2ssh/command'
|
3
|
+
require 'ec2ssh/migrator'
|
4
|
+
|
5
|
+
module Ec2ssh
|
6
|
+
module Command
|
7
|
+
class Migrate < Base
|
8
|
+
def initialize(cli)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
migrator = Migrator.new dotfile_path
|
14
|
+
version = migrator.check_version
|
15
|
+
case version
|
16
|
+
when '2'
|
17
|
+
cli.red "Current dotfile version is #{version} (#{dotfile_path})"
|
18
|
+
new_dotfile_str = migrator.migrate_from_2
|
19
|
+
cli.red "Ec2ssh is migrating your dotfile to version 3 style as follows:"
|
20
|
+
cli.yellow new_dotfile_str
|
21
|
+
if cli.yes? "Are you sure to migrate #{dotfile_path} to version 3 style? (y/n)"
|
22
|
+
backup_path = migrator.backup!
|
23
|
+
puts "Creating backup as #{backup_path}"
|
24
|
+
migrator.replace! new_dotfile_str
|
25
|
+
cli.green 'Migrated successfully.'
|
26
|
+
end
|
27
|
+
when '3'
|
28
|
+
cli.green "Current dotfile version is #{version} (#{dotfile_path})"
|
29
|
+
cli.green 'Your dotfile is up-to-date.'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'ec2ssh/exceptions'
|
2
|
+
require 'ec2ssh/command'
|
3
|
+
require 'ec2ssh/ssh_config'
|
4
|
+
|
5
|
+
module Ec2ssh
|
6
|
+
module Command
|
7
|
+
class Remove < Base
|
8
|
+
def initialize(cli)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
ssh_config = SshConfig.new(ssh_config_path)
|
14
|
+
raise MarkNotFound unless ssh_config.mark_exist?
|
15
|
+
|
16
|
+
ssh_config.replace! ""
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'ec2ssh/exceptions'
|
2
|
+
require 'ec2ssh/command'
|
3
|
+
require 'ec2ssh/ssh_config'
|
4
|
+
require 'ec2ssh/builder'
|
5
|
+
require 'ec2ssh/dsl'
|
6
|
+
require 'ec2ssh/migrator'
|
7
|
+
|
8
|
+
module Ec2ssh
|
9
|
+
module Command
|
10
|
+
class Update < Base
|
11
|
+
def initialize(cli)
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
ssh_config = SshConfig.new(ssh_config_path)
|
17
|
+
raise MarkNotFound unless ssh_config.mark_exist?
|
18
|
+
|
19
|
+
ssh_config.parse!
|
20
|
+
lines = builder.build_host_lines
|
21
|
+
ssh_config_str = ssh_config.wrap lines
|
22
|
+
ssh_config.replace! ssh_config_str
|
23
|
+
cli.yellow ssh_config_str
|
24
|
+
end
|
25
|
+
|
26
|
+
def builder
|
27
|
+
@builder ||= Builder.new dsl
|
28
|
+
end
|
29
|
+
|
30
|
+
def dsl
|
31
|
+
@dsl ||= Ec2ssh::Dsl::Parser.parse File.read(dotfile_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def migrator
|
35
|
+
@migrator ||= Migrator.new dotfile_path
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/ec2ssh/dsl.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'ec2ssh/exceptions'
|
2
|
+
|
3
|
+
module Ec2ssh
|
4
|
+
class Dsl
|
5
|
+
attr_reader :_result
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@_result = Container.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def aws_keys(keys)
|
12
|
+
@_result.aws_keys = keys
|
13
|
+
end
|
14
|
+
|
15
|
+
def regions(*regions)
|
16
|
+
@_result.regions = regions
|
17
|
+
end
|
18
|
+
|
19
|
+
def host_line(erb)
|
20
|
+
@_result.host_line = erb
|
21
|
+
end
|
22
|
+
|
23
|
+
def reject(&block)
|
24
|
+
@_result.reject = block
|
25
|
+
end
|
26
|
+
|
27
|
+
def path(str)
|
28
|
+
@_result.path = str
|
29
|
+
end
|
30
|
+
|
31
|
+
class Container < Struct.new(*%i[
|
32
|
+
aws_keys
|
33
|
+
regions
|
34
|
+
host_line
|
35
|
+
reject
|
36
|
+
path
|
37
|
+
])
|
38
|
+
end
|
39
|
+
|
40
|
+
module Parser
|
41
|
+
def self.parse(dsl_str)
|
42
|
+
dsl = Dsl.new
|
43
|
+
dsl.instance_eval dsl_str
|
44
|
+
dsl._result
|
45
|
+
rescue SyntaxError => e
|
46
|
+
raise DotfileSyntaxError, e.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.parse_file(path)
|
50
|
+
raise DotfileNotFound, path.to_s unless File.exist?(path)
|
51
|
+
parse File.read(path)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module Ec2ssh
|
4
|
+
class Ec2Instances
|
5
|
+
attr_reader :ec2s, :aws_keys
|
6
|
+
|
7
|
+
def initialize(aws_keys, regions)
|
8
|
+
@aws_keys = aws_keys
|
9
|
+
@regions = regions
|
10
|
+
end
|
11
|
+
|
12
|
+
def make_ec2s
|
13
|
+
AWS.start_memoizing
|
14
|
+
_ec2s = {}
|
15
|
+
aws_keys.each do |name, key|
|
16
|
+
_ec2s[name] = {}
|
17
|
+
@regions.each do |region|
|
18
|
+
options = key.merge ec2_region: region
|
19
|
+
_ec2s[name][region] = AWS::EC2.new options
|
20
|
+
end
|
21
|
+
end
|
22
|
+
_ec2s
|
23
|
+
end
|
24
|
+
|
25
|
+
def ec2s
|
26
|
+
@ec2s ||= make_ec2s
|
27
|
+
end
|
28
|
+
|
29
|
+
def instances(key_name)
|
30
|
+
@regions.map {|region|
|
31
|
+
ec2s[key_name][region].instances.
|
32
|
+
filter('instance-state-name', 'running').
|
33
|
+
to_a.
|
34
|
+
sort_by {|ins| ins.tags['Name'] }
|
35
|
+
}.flatten
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Ec2ssh
|
2
|
+
class DotfileNotFound < StandardError; end
|
3
|
+
class DotfileSyntaxError < StandardError; end
|
4
|
+
class ObsoleteDotfile < StandardError; end
|
5
|
+
class InvalidDotfile < StandardError; end
|
6
|
+
class MarkNotFound < StandardError; end
|
7
|
+
class MarkAlreadyExists < StandardError; end
|
8
|
+
class AwsKeyNotFound < StandardError; end
|
9
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'stringio'
|
3
|
+
require 'ec2ssh/dsl'
|
4
|
+
|
5
|
+
module Ec2ssh
|
6
|
+
class Migrator
|
7
|
+
def initialize(dotfile_path)
|
8
|
+
@dotfile_path = dotfile_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def dotfile_str
|
12
|
+
@dotfile_str ||= File.read(@dotfile_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def check_version
|
16
|
+
begin
|
17
|
+
hash = YAML.load dotfile_str
|
18
|
+
return '2' if hash.is_a?(Hash) && hash.keys.include?('aws_keys')
|
19
|
+
rescue Psych::SyntaxError
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
Dsl::Parser.parse dotfile_str
|
24
|
+
return '3'
|
25
|
+
rescue DotfileSyntaxError
|
26
|
+
end
|
27
|
+
|
28
|
+
raise InvalidDotfile
|
29
|
+
end
|
30
|
+
|
31
|
+
def migrate_from_2
|
32
|
+
hash = YAML.load dotfile_str
|
33
|
+
out = StringIO.new
|
34
|
+
|
35
|
+
out.puts "path '#{hash['path']}'" if hash['path']
|
36
|
+
|
37
|
+
out.puts 'aws_keys('
|
38
|
+
out.puts hash['aws_keys'].map {|name, key|
|
39
|
+
" #{name}: { access_key_id: '#{key['access_key_id']}', secret_access_key: '#{key['secret_access_key']}' }"
|
40
|
+
}.join(",\n")
|
41
|
+
out.puts ')'
|
42
|
+
|
43
|
+
if hash['regions']
|
44
|
+
regions = hash['regions'].map{|r| "'#{r}'" }.join(', ')
|
45
|
+
out.puts "regions #{regions}"
|
46
|
+
end
|
47
|
+
|
48
|
+
out.puts <<-END
|
49
|
+
|
50
|
+
# You can use methods of AWS::EC2::Instance.
|
51
|
+
# See http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/Instance.html
|
52
|
+
host_line <<EOS
|
53
|
+
Host <%= tags['Name'] %>.<%= availability_zone %>
|
54
|
+
HostName <%= dns_name || private_ip_address %>
|
55
|
+
EOS
|
56
|
+
END
|
57
|
+
|
58
|
+
out.puts
|
59
|
+
out.puts dotfile_str.gsub(/^/m, '# ')
|
60
|
+
|
61
|
+
out.string
|
62
|
+
end
|
63
|
+
|
64
|
+
def replace!(new_dotfile_str)
|
65
|
+
File.open(@dotfile_path, 'w') {|f| f.write new_dotfile_str }
|
66
|
+
end
|
67
|
+
|
68
|
+
def backup!
|
69
|
+
backup_path = "#{@dotfile_path}.#{Time.now.strftime("%Y%m%d%H%M%S")}"
|
70
|
+
File.open(backup_path, 'w') {|f| f.write dotfile_str }
|
71
|
+
backup_path
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/ec2ssh/ssh_config.rb
CHANGED
@@ -8,9 +8,8 @@ module Ec2ssh
|
|
8
8
|
|
9
9
|
attr_reader :path, :sections
|
10
10
|
|
11
|
-
def initialize(path=nil
|
12
|
-
@path =
|
13
|
-
@aws_key = aws_key
|
11
|
+
def initialize(path=nil)
|
12
|
+
@path = path || "#{ENV['HOME']}/.ssh/config"
|
14
13
|
@sections = {}
|
15
14
|
end
|
16
15
|
|
@@ -35,7 +34,7 @@ module Ec2ssh
|
|
35
34
|
|
36
35
|
def append_mark!
|
37
36
|
replace! ""
|
38
|
-
open(@path, "a") do |f|
|
37
|
+
File.open(@path, "a") do |f|
|
39
38
|
f.puts wrap("")
|
40
39
|
end
|
41
40
|
end
|
@@ -49,13 +48,13 @@ module Ec2ssh
|
|
49
48
|
end
|
50
49
|
|
51
50
|
def config_src
|
52
|
-
@config_src ||= open(@path, "r") do |f|
|
51
|
+
@config_src ||= File.open(@path, "r") do |f|
|
53
52
|
f.read
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
57
56
|
def save!(str)
|
58
|
-
open(@path, "w") do |f|
|
57
|
+
File.open(@path, "w") do |f|
|
59
58
|
f.puts str
|
60
59
|
end
|
61
60
|
end
|
data/lib/ec2ssh/version.rb
CHANGED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ec2ssh/dsl'
|
3
|
+
require 'ec2ssh/builder'
|
4
|
+
|
5
|
+
describe Ec2ssh::Builder do
|
6
|
+
describe '#build_host_lines' do
|
7
|
+
let(:container) do
|
8
|
+
Ec2ssh::Dsl::Container.new.tap do |c|
|
9
|
+
c.aws_keys = {
|
10
|
+
key1: { access_key_id: 'KEY1', secret_access_key: 'SEC1' },
|
11
|
+
key2: { access_key_id: 'KEY2', secret_access_key: 'SEC2' }
|
12
|
+
}
|
13
|
+
c.host_line = "Host <%= tags['Name'] %>"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:builder) do
|
18
|
+
Ec2ssh::Builder.new(container).tap do |bldr|
|
19
|
+
allow(bldr).to receive(:ec2s) { ec2s }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:ec2s) do
|
24
|
+
double('ec2s', aws_keys: container.aws_keys).tap do |dbl|
|
25
|
+
allow(dbl).to receive(:instances) {|name| instances[name] }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:instances) do
|
30
|
+
{
|
31
|
+
key1: [
|
32
|
+
double('instance', tags: {'Name' => 'srv1'}),
|
33
|
+
double('instance', tags: {'Name' => 'srv2'})],
|
34
|
+
key2: [
|
35
|
+
double('instance', tags: {'Name' => 'srv3'}),
|
36
|
+
double('instance', tags: {'Name' => 'srv4'})]
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
it do
|
41
|
+
expect(builder.build_host_lines).to eq <<-END
|
42
|
+
# section: key1
|
43
|
+
Host srv1
|
44
|
+
Host srv2
|
45
|
+
# section: key2
|
46
|
+
Host srv3
|
47
|
+
Host srv4
|
48
|
+
END
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'with #reject' do
|
52
|
+
before do
|
53
|
+
container.reject = lambda {|ins| ins.tags['Name'] == 'srv1' }
|
54
|
+
end
|
55
|
+
|
56
|
+
it do
|
57
|
+
expect(builder.build_host_lines).to eq <<-END
|
58
|
+
# section: key1
|
59
|
+
Host srv2
|
60
|
+
# section: key2
|
61
|
+
Host srv3
|
62
|
+
Host srv4
|
63
|
+
END
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|