ec2ssh 2.0.8 → 3.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|