ec2ssh 3.1.0.rc1 → 5.0.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.
@@ -2,16 +2,15 @@ require 'thor'
2
2
  require 'highline'
3
3
  require 'ec2ssh/ssh_config'
4
4
  require 'ec2ssh/exceptions'
5
- require 'ec2ssh/migrator'
6
5
 
7
6
  module Ec2ssh
8
7
  class CLI < Thor
8
+ class_option :path, banner: "/path/to/ssh_config"
9
9
  class_option :dotfile, banner: '$HOME/.ec2ssh', default: "#{ENV['HOME']}/.ec2ssh"
10
- class_option :verbose, banner: 'enable debug log', default: false
10
+ class_option :verbose, banner: 'enable debug log', type: 'boolean'
11
11
 
12
12
  desc 'init', 'Add ec2ssh mark to ssh_config'
13
13
  def init
14
- check_dotfile_version
15
14
  command = make_command :init
16
15
  command.run
17
16
  rescue MarkAlreadyExists
@@ -21,7 +20,6 @@ module Ec2ssh
21
20
  desc 'update', 'Update ec2 hosts list in ssh_config'
22
21
  def update
23
22
  check_dotfile_existence
24
- check_dotfile_version
25
23
  set_aws_logging
26
24
  command = make_command :update
27
25
  command.run
@@ -36,7 +34,6 @@ module Ec2ssh
36
34
  desc 'remove', 'Remove ec2ssh mark from ssh_config'
37
35
  def remove
38
36
  check_dotfile_existence
39
- check_dotfile_version
40
37
  command = make_command :remove
41
38
  command.run
42
39
  green "Removed mark from #{command.ssh_config_path}"
@@ -44,12 +41,6 @@ module Ec2ssh
44
41
  red "Marker not found in #{command.ssh_config_path}"
45
42
  end
46
43
 
47
- desc 'migrate', 'Migrate dotfile from old versions'
48
- def migrate
49
- command = make_command :migrate
50
- command.run
51
- end
52
-
53
44
  desc 'shellcomp [-]', 'Initialize shell completion for bash/zsh'
54
45
  def shellcomp(_ = false)
55
46
  if args.include?("-")
@@ -89,16 +80,6 @@ EOS
89
80
  end
90
81
 
91
82
  no_tasks do
92
- def check_dotfile_version
93
- return unless File.exist?(options.dotfile)
94
- migrator = Migrator.new options.dotfile
95
- if migrator.check_version < '3'
96
- red "#{options.dotfile} is old style."
97
- red "Try '#{$0} migrate' to migrate to version 3"
98
- abort
99
- end
100
- end
101
-
102
83
  def check_dotfile_existence
103
84
  unless File.exist?(options.dotfile)
104
85
  red "#{options.dotfile} doesn't exist."
@@ -116,10 +97,10 @@ EOS
116
97
  def set_aws_logging
117
98
  if options.verbose
118
99
  require 'logger'
119
- require 'aws-sdk'
100
+ require 'aws-sdk-core'
120
101
  logger = ::Logger.new($stdout)
121
102
  logger.level = ::Logger::DEBUG
122
- ::AWS.config logger: logger
103
+ ::Aws.config.update logger: logger
123
104
  end
124
105
  end
125
106
 
@@ -14,7 +14,7 @@ module Ec2ssh
14
14
  end
15
15
 
16
16
  def ssh_config_path
17
- cli.options.path || dotfile.path || "#{$ENV['HOME']}/.ssh/config"
17
+ cli.options.path || dotfile.path || "#{ENV['HOME']}/.ssh/config"
18
18
  end
19
19
 
20
20
  def dotfile_path
@@ -33,13 +33,7 @@ module Ec2ssh
33
33
  def write_dotfile_example
34
34
  example = <<-DOTFILE
35
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
- )
36
+ profiles 'default', 'myprofile'
43
37
  regions ENV['AWS_REGION'] || ENV['AMAZON_REGION'] || ENV['AWS_DEFAULT_REGION'] || 'us-east-1'
44
38
  # Enable regions as you like
45
39
  # 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)
@@ -47,8 +41,8 @@ regions ENV['AWS_REGION'] || ENV['AMAZON_REGION'] || ENV['AWS_DEFAULT_REGION'] |
47
41
  # You can use methods of AWS::EC2::Instance.
48
42
  # See http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/Instance.html
49
43
  host_line <<END
50
- Host <%= tags['Name'] %>.<%= availability_zone %>
51
- HostName <%= dns_name || private_ip_address %>
44
+ Host <%= tag('Name') %>.<%= placement.availability_zone %>
45
+ HostName <%= public_dns_name || private_ip_address %>
52
46
  END
53
47
  DOTFILE
54
48
 
@@ -3,7 +3,6 @@ require 'ec2ssh/command'
3
3
  require 'ec2ssh/ssh_config'
4
4
  require 'ec2ssh/builder'
5
5
  require 'ec2ssh/dsl'
6
- require 'ec2ssh/migrator'
7
6
 
8
7
  module Ec2ssh
9
8
  module Command
@@ -30,10 +29,6 @@ module Ec2ssh
30
29
  def dsl
31
30
  @dsl ||= Ec2ssh::Dsl::Parser.parse File.read(dotfile_path)
32
31
  end
33
-
34
- def migrator
35
- @migrator ||= Migrator.new dotfile_path
36
- end
37
32
  end
38
33
  end
39
34
  end
@@ -1,14 +1,24 @@
1
1
  require 'ec2ssh/exceptions'
2
+ require 'aws-sdk-core'
2
3
 
3
4
  module Ec2ssh
4
5
  class Dsl
5
6
  attr_reader :_result
6
7
 
8
+ CREDENTIAL_CLASSES = [Aws::Credentials, Aws::SharedCredentials, Aws::InstanceProfileCredentials, Aws::AssumeRoleCredentials].freeze
9
+ private_constant :CREDENTIAL_CLASSES
10
+
7
11
  def initialize
8
12
  @_result = Container.new
9
13
  end
10
14
 
11
15
  def aws_keys(keys)
16
+ unless keys.all? {|_, v| v.is_a?(Hash) && v.each_value.all? {|c| CREDENTIAL_CLASSES.any?(&c.method(:is_a?)) } }
17
+ raise DotfileValidationError, <<-MSG
18
+ Since v4.0, `aws_keys` in the dotfile must be specified regions as a hash key.
19
+ See: https://github.com/mirakui/ec2ssh#how-to-upgrade-from-3x
20
+ MSG
21
+ end
12
22
  @_result.aws_keys = keys
13
23
  end
14
24
 
@@ -28,6 +38,10 @@ module Ec2ssh
28
38
  @_result.reject = block
29
39
  end
30
40
 
41
+ def filters(filters)
42
+ @_result.filters = filters
43
+ end
44
+
31
45
  def path(str)
32
46
  @_result.path = str
33
47
  end
@@ -38,6 +52,7 @@ module Ec2ssh
38
52
  regions
39
53
  host_line
40
54
  reject
55
+ filters
41
56
  path
42
57
  ])
43
58
  end
@@ -1,22 +1,75 @@
1
- require 'aws-sdk-v1'
1
+ require 'aws-sdk-core'
2
+ require 'aws-sdk-ec2'
3
+ require 'ec2ssh/exceptions'
2
4
 
3
5
  module Ec2ssh
4
6
  class Ec2Instances
5
7
  attr_reader :ec2s, :aws_keys
6
8
 
7
- def initialize(aws_keys, regions)
9
+ class InstanceWrapper
10
+ class TagsWrapper
11
+ def initialize(tags)
12
+ @tags = tags
13
+ end
14
+
15
+ # simulate
16
+ def [](key)
17
+ if key.is_a? ::String
18
+ raise DotfileValidationError, <<-MSG
19
+ `tags[String]` syntax in the dotfile has been deleted since v4.0. Use `tag(String)` instead.
20
+ See: https://github.com/mirakui/ec2ssh#how-to-upgrade-from-3x
21
+ MSG
22
+ end
23
+ super
24
+ end
25
+
26
+ private
27
+
28
+ def method_missing(name, *args, &block)
29
+ @tags.public_send(name, *args, &block)
30
+ end
31
+
32
+ def respond_to_missing?(symbol, include_private)
33
+ @tags.respond_to?(symbol, include_private)
34
+ end
35
+ end
36
+
37
+ def initialize(ec2_instance)
38
+ @ec2_instance = ec2_instance
39
+ @_tags ||= @ec2_instance.tags.each_with_object({}) {|t, h| h[t.key] = t.value }
40
+ end
41
+
42
+ def tag(key)
43
+ @_tags[key]
44
+ end
45
+
46
+ def tags
47
+ TagsWrapper.new(super)
48
+ end
49
+
50
+ private
51
+
52
+ def method_missing(name, *args, &block)
53
+ @ec2_instance.public_send(name, *args, &block)
54
+ end
55
+
56
+ def respond_to_missing?(symbol, include_private)
57
+ @ec2_instance.respond_to?(symbol, include_private)
58
+ end
59
+ end
60
+
61
+ def initialize(aws_keys, filters)
8
62
  @aws_keys = aws_keys
9
- @regions = regions
63
+ @filters = filters
10
64
  end
11
65
 
12
66
  def make_ec2s
13
- AWS.start_memoizing
14
67
  _ec2s = {}
15
- aws_keys.each do |name, key|
68
+ aws_keys.each_pair do |name, keys|
16
69
  _ec2s[name] = {}
17
- @regions.each do |region|
18
- options = key.merge ec2_region: region
19
- _ec2s[name][region] = AWS::EC2.new options
70
+ keys.each_pair do |region, key|
71
+ client = Aws::EC2::Client.new region: region, credentials: key
72
+ _ec2s[name][region] = Aws::EC2::Resource.new client: client
20
73
  end
21
74
  end
22
75
  _ec2s
@@ -27,17 +80,18 @@ module Ec2ssh
27
80
  end
28
81
 
29
82
  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'].to_s }
83
+ aws_keys[key_name].each_key.map {|region|
84
+ ec2s[key_name][region].instances(
85
+ filters: @filters
86
+ ).
87
+ map {|ins| InstanceWrapper.new(ins) }.
88
+ sort_by {|ins| ins.tag('Name').to_s }
35
89
  }.flatten
36
90
  end
37
91
 
38
- def self.expand_profile_name_to_credential(profile_name)
39
- provider = AWS::Core::CredentialProviders::SharedCredentialFileProvider.new(profile_name: profile_name)
40
- provider.credentials
92
+ def self.expand_profile_name_to_credential(profile_name, region)
93
+ client = Aws::STS::Client.new(profile: profile_name, region: region)
94
+ client.config.credentials
41
95
  end
42
96
  end
43
97
  end
@@ -1,3 +1,3 @@
1
1
  module Ec2ssh
2
- VERSION = '3.1.0.rc1'
2
+ VERSION = '5.0.0'
3
3
  end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+ require 'ec2ssh/ec2_instances'
3
+
4
+ describe 'aws-sdk compatibility' do
5
+ let(:region) { 'us-west-1' }
6
+ let(:root_device) { '/dev/xvda' }
7
+
8
+ let!(:ec2_instances) do
9
+ VCR.use_cassette('ec2-instances') do
10
+ Ec2ssh::Ec2Instances.new(
11
+ {'foo' => {'us-west-1' => Aws::Credentials.new('access_key_id', 'secret_access_key')}},
12
+ [{ name: 'instance-state-name', values: ['running'] }]
13
+ ).instances('foo')
14
+ end
15
+ end
16
+
17
+ let(:ins) { ec2_instances.first }
18
+
19
+ it { expect(ec2_instances.count).to be == 1 }
20
+
21
+ it { expect(ins.tag('Name')).to match /.+/ }
22
+ it { expect(ins.tag('Role')).to match /.+/ }
23
+ it { expect(ins.tags).to match_array([have_attributes(key: 'Name', value: /.+/), have_attributes(key: 'Role', value: /.+/)]) }
24
+ it { expect(ins.ami_launch_index).to be == 0 }
25
+ it { expect(ins.architecture).to be == 'x86_64' }
26
+ it do
27
+ expect(ins.block_device_mappings).to match [
28
+ have_attributes(
29
+ device_name: root_device,
30
+ ebs: have_attributes(
31
+ volume_id: /\Avol-\w+\z/,
32
+ status: 'attached',
33
+ attach_time: an_instance_of(Time),
34
+ delete_on_termination: true
35
+ )
36
+ )]
37
+ end
38
+ # it { expect(ins.capacity_reservation_id).to be_nil}
39
+ # it { expect(ins.capacity_reservation_specification).to be_nil }
40
+ it { expect(ins.classic_address).to be_a(Aws::EC2::ClassicAddress) }
41
+ it { expect(ins.client).to be_a(Aws::EC2::Client) }
42
+ it { expect(ins.client_token).to match /\A\w{18}\z/ }
43
+ # it { expect(ins.cpu_options).to be_nil }
44
+ it { expect(ins.ebs_optimized).to be_falsy }
45
+ it { expect(ins.elastic_gpu_associations).to be_nil }
46
+ # it { expect(ins.elastic_inference_accelerator_associations).to be_nil }
47
+ it { expect(ins.ena_support).to be_falsy }
48
+ # it { expect(ins.hibernation_options).to be_nil}
49
+ it { expect(ins.hypervisor).to be == 'xen' }
50
+ it { expect(ins.iam_instance_profile).to have_attributes(arn: /\Aarn:aws:iam::\d+:instance-profile\/[\w\-]+\z/, id: /\A\w{21}\z/) }
51
+ it { expect(ins.id).to match /\Ai-\w+\z/ }
52
+ it { expect(ins.image).to be_a(Aws::EC2::Image) }
53
+ it { expect(ins.image_id).to match /\Aami-\w+\z/ }
54
+ it { expect(ins.instance_id).to match /\Ai-\w+\z/ }
55
+ it { expect(ins.instance_lifecycle).to be_nil }
56
+ it { expect(ins.instance_type).to match /\A[trmci][1248]\.\w+\z/ }
57
+ it { expect(ins.kernel_id).to be_nil }
58
+ it { expect(ins.key_name).to match /\A.+\.pem\z/ }
59
+ it { expect(ins.key_pair).to be_a(Aws::EC2::KeyPairInfo) }
60
+ it { expect(ins.launch_time).to be_a(Time) }
61
+ # it { expect(ins.licenses).to all have_attributes(license_configuration_arn: '') }
62
+ it { expect(ins.monitoring).to have_attributes(state: 'disabled') }
63
+ it { expect(ins.network_interfaces).to all match(an_instance_of(Aws::EC2::NetworkInterface)) }
64
+ it { expect(ins.placement).to have_attributes(availability_zone: /\A#{region}[a-c]\z/, group_name: '', tenancy: 'default') }
65
+ it { expect(ins.placement_group).to be_a(Aws::EC2::PlacementGroup) }
66
+ it { expect(ins.platform).to be_nil }
67
+ it { expect(ins.private_dns_name).to match /\Aip-[\w\-]+\.#{region}\.compute\.internal\z/ }
68
+ it { expect(ins.private_ip_address).to match /\A[\d\.]+\z/ }
69
+ it { expect(ins.product_codes).to be == [] }
70
+ it { expect(ins.public_dns_name).to match /\Aec2-[\w\-]+\.#{region}\.compute\.amazonaws\.com\z/ }
71
+ it { expect(ins.public_ip_address).to match /\A[\d\.]+\z/ }
72
+ it { expect(ins.ramdisk_id).to be_nil }
73
+ it { expect(ins.root_device_name).to eq root_device }
74
+ it { expect(ins.root_device_type).to be == 'ebs' }
75
+ it do
76
+ expect(ins.security_groups).to all have_attributes(
77
+ group_id: /\Asg-\w+\z/,
78
+ group_name: /\A.+\z/
79
+ )
80
+ end
81
+ it { expect(ins.source_dest_check).to be true }
82
+ it { expect(ins.spot_instance_request_id).to be_nil }
83
+ it { expect(ins.sriov_net_support).to be_nil }
84
+ it { expect(ins.state).to have_attributes(code: 16, name: 'running') }
85
+ it { expect(ins.state_reason).to be_nil }
86
+ it { expect(ins.state_transition_reason).to be == '' }
87
+ it { expect(ins.subnet).to be_a(Aws::EC2::Subnet) }
88
+ it { expect(ins.subnet_id).to match /\Asubnet-\w+\z/ }
89
+ it { expect(ins.virtualization_type).to be == 'hvm' }
90
+ it { expect(ins.vpc).to be_a(Aws::EC2::Vpc) }
91
+ it { expect(ins.vpc_id).to match /\Avpc-\w+\z/ }
92
+ end
@@ -7,10 +7,10 @@ describe Ec2ssh::Builder do
7
7
  let(:container) do
8
8
  Ec2ssh::Dsl::Container.new.tap do |c|
9
9
  c.aws_keys = {
10
- key1: { access_key_id: 'KEY1', secret_access_key: 'SEC1' },
11
- key2: { access_key_id: 'KEY2', secret_access_key: 'SEC2' }
10
+ 'key1' => { 'us-west-1' => Aws::Credentials.new('KEY1', 'SEC1') },
11
+ 'key2' => { 'us-west-1' => Aws::Credentials.new('KEY2', 'SEC2') }
12
12
  }
13
- c.host_line = "Host <%= tags['Name'] %>"
13
+ c.host_line = "Host <%= tag('Name') %>"
14
14
  end
15
15
  end
16
16
 
@@ -28,12 +28,14 @@ describe Ec2ssh::Builder do
28
28
 
29
29
  let(:instances) do
30
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'})]
31
+ 'key1' => [
32
+ double('instance').tap {|m| allow(m).to receive(:tag).with('Name').and_return('srv1') },
33
+ double('instance').tap {|m| allow(m).to receive(:tag).with('Name').and_return('srv2') }
34
+ ],
35
+ 'key2' => [
36
+ double('instance').tap {|m| allow(m).to receive(:tag).with('Name').and_return('srv3') },
37
+ double('instance').tap {|m| allow(m).to receive(:tag).with('Name').and_return('srv4') }
38
+ ]
37
39
  }
38
40
  end
39
41
 
@@ -50,7 +52,7 @@ Host srv4
50
52
 
51
53
  context 'with #reject' do
52
54
  before do
53
- container.reject = lambda {|ins| ins.tags['Name'] == 'srv1' }
55
+ container.reject = lambda {|ins| ins.tag('Name') == 'srv1' }
54
56
  end
55
57
 
56
58
  it do
@@ -67,10 +69,10 @@ Host srv4
67
69
  context 'checking erb trim_mode' do
68
70
  before do
69
71
  container.host_line = <<-END
70
- % if tags['Name']
71
- <%- if tags['Name'] == 'srv3' -%>
72
- Host <%= tags['Name'] %>
73
- HostName <%= tags['Name'] %>
72
+ % if tag('Name')
73
+ <%- if tag('Name') == 'srv3' -%>
74
+ Host <%= tag('Name') %>
75
+ HostName <%= tag('Name') %>
74
76
  <%- end -%>
75
77
  % end
76
78
  END
@@ -2,6 +2,8 @@ require 'spec_helper'
2
2
  require 'ec2ssh/command/remove'
3
3
 
4
4
  describe Ec2ssh::Command::Remove do
5
+ include FakeFS::SpecHelpers
6
+
5
7
  describe '#run' do
6
8
  let(:command) do
7
9
  described_class.new(cli).tap do |cmd|
@@ -17,11 +19,10 @@ describe Ec2ssh::Command::Remove do
17
19
 
18
20
  let(:dotfile_str) { <<-END }
19
21
  path '/dotfile'
20
- aws_keys(
21
- default: { access_key_id: 'ACCESS_KEY1', secret_access_key: 'SECRET1' }
22
- )
22
+ profiles 'default'
23
+ regions 'us-west-1'
23
24
  host_line <<EOS
24
- Host <%= tags['Name'] %>
25
+ Host <%= tag('Name') %>
25
26
  HostName <%= private_ip_address %>
26
27
  EOS
27
28
  END