aws-ssh-resolver 0.0.2
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/README.md +251 -0
- data/bin/aws-ssh-resolver.rb +5 -0
- data/lib/aws-ssh-resolver.rb +1 -0
- data/lib/cli/aws-ssh-resolver-cli.rb +339 -0
- data/lib/utils/logger.rb +70 -0
- data/spec/cli/aws-ssh-resolver-cli_spec.rb +183 -0
- data/spec/cli/fixtures/fixture1.json +237 -0
- data/spec/cli/spec_helper.rb +21 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f935d670a01180e51698c25ea3ca209884f43556
|
4
|
+
data.tar.gz: ee1f14a33bcbe6b9ead7babf3fd625d96f3de9c0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 02580ee44766981ab87b40e4f5f8bdac0cd1ab5e0e1ce3082f393272fac7b144bfa4f9b3c568834d6bb66b40b54f20c698cce7a2b162e737c516bd213fddd5fc
|
7
|
+
data.tar.gz: 013f95baba3d2fef334144d5995bdc373992b9e6ece6e88287ca2ee2937047c2fc70128ead909c8e50c3e2dedf34f0f2e70a819fa3e0aadad6b6c5d9c37933c9
|
data/README.md
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
# aws-ssh-resolver - Resolve AWS EC2 HostNames for OpenSSH configuration - $Release:0.0.2$
|
2
|
+
|
3
|
+
`aws-ssh-resolver` keeps AWS EC2 HostNames in OpenSSH configuration
|
4
|
+
file in sync with Amazon cloud making it easier for a user to use
|
5
|
+
OpenSSH, and related tools, on Amazon Platform.
|
6
|
+
|
7
|
+
## The Problem
|
8
|
+
|
9
|
+
Every EC2 instance on Amazon platform has a Private IP address, and a
|
10
|
+
DNS hostname resolving to this address. Private IP cannot be reached
|
11
|
+
directly from the Internet, and the Private DNS name can be resolved
|
12
|
+
only on the network that the instance is in. An instance may be
|
13
|
+
assigned a Public IP Address, and corresponding Public DNS name. The
|
14
|
+
Public IP is accessible from the Internet, and the Public DNS name is
|
15
|
+
resolvable outside the network of the instance. Public IPs come from
|
16
|
+
Amazon's pool of public IP address, and an instance may not reuse the
|
17
|
+
IP address, once it is released. For example, stopping, or
|
18
|
+
terminating, an instance releases the Public IP Address. See Amazon
|
19
|
+
[documentation](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-instance-addressing.html)
|
20
|
+
for more details.
|
21
|
+
|
22
|
+
|
23
|
+
Amazon EC2 Instance IP Addressing presents several challenges for SSH
|
24
|
+
usage, or any SSH related tool e.g.
|
25
|
+
[ansible](http://www.ansible.com/home),
|
26
|
+
[fabric](http://www.fabfile.org/),
|
27
|
+
[serverspec](http://serverspec.org/) etc.
|
28
|
+
|
29
|
+
* Public DNS Name encodes the Public IP Address. Each time an instance
|
30
|
+
is assigned a new IP address, it also gets a new Public DNS name, In
|
31
|
+
essence this means that the task of managing DNS names becomes
|
32
|
+
comparable to the task of managing IP addresses.
|
33
|
+
|
34
|
+
* Using an IP address to contact an instance is complicated, because
|
35
|
+
Public IP Address, once released, cannot be reused. Using fixed IP
|
36
|
+
addresses requires keeping track of reserved address, and comes with
|
37
|
+
extra costs.
|
38
|
+
|
39
|
+
* EC2 instances, with only a Private IP Address, cannot be reached
|
40
|
+
directly from the Internet.
|
41
|
+
|
42
|
+
* Private DNS names also encode the IP address they map to. On top of
|
43
|
+
that, Private DNS names cannot resolved outside the cloud network.
|
44
|
+
|
45
|
+
## The Solution
|
46
|
+
|
47
|
+
[aws-ssh-resolver](https://github.com/jarjuk/aws-ssh-resolver)
|
48
|
+
addresses the challenges above
|
49
|
+
|
50
|
+
* It accepts output of
|
51
|
+
[Amazon Command Line Interface](https://aws.amazon.com/cli/) to
|
52
|
+
create
|
53
|
+
[OpenSSH Configuration](http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/ssh_config.5?query=ssh_config&sec=5)
|
54
|
+
entries mapping persistent, and human-understandable, EC2 Tag names
|
55
|
+
to mutable EC2 DNS names.
|
56
|
+
|
57
|
+
* Tag-name/DNS name mapping can be updated to reflect current cloud
|
58
|
+
configuration.
|
59
|
+
|
60
|
+
* Tag-name/DNS mapping together with
|
61
|
+
[ProxyCommand with Netcat](https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Proxies_and_Jump_Hosts#ProxyCommand_with_Netcat)
|
62
|
+
configuration in OpenSSH allows users to create a transparent
|
63
|
+
multihop SSH connection to EC2 instances with Private IP Address
|
64
|
+
only
|
65
|
+
|
66
|
+
## Usage
|
67
|
+
|
68
|
+
### Installation
|
69
|
+
|
70
|
+
Add following lines to `Gemfile`
|
71
|
+
|
72
|
+
source 'https://rubygems.org'
|
73
|
+
gem 'aws-ssh-resolver'
|
74
|
+
|
75
|
+
and run
|
76
|
+
|
77
|
+
bundle install
|
78
|
+
|
79
|
+
### Configuration
|
80
|
+
|
81
|
+
Create an initial
|
82
|
+
[OpenSSH Configuration](http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man5/ssh_config.5?query=ssh_config&sec=5)
|
83
|
+
file `ssh/config.aws` with any fixed configuration. Running
|
84
|
+
**aws-ssh-resolver** updates this file, but does not interfere with
|
85
|
+
the content user has entered.
|
86
|
+
|
87
|
+
**Notice**: If `ssh/config.aws` -file does not exist, the first
|
88
|
+
**aws-ssh-resolver** run creates the initial version of
|
89
|
+
`ssh/config.aws` automatically using `ssh/config.init`. This avoids
|
90
|
+
the need to check in the mutable `ssh/config.aws` into a version
|
91
|
+
nncontrol system.
|
92
|
+
|
93
|
+
### Update OpenSSH Configuration file
|
94
|
+
|
95
|
+
To update OpenSSH configuration with EC2 Tag/DNS mappings pipe the
|
96
|
+
result of `aws ec2 describe-instances` to **aws-ssh-resolver**
|
97
|
+
command:
|
98
|
+
|
99
|
+
aws ec2 describe-instances | bundle exec aws-ssh-resolver.rb resolve
|
100
|
+
|
101
|
+
The command extract EC2 Tag/DNS information, and writes
|
102
|
+
`host`/`HostName` configuration entries in `ssh/config.aws` -file. In
|
103
|
+
this file `host` value is taken from `Name` tag on an EC2 instance,
|
104
|
+
and `HostName` value is taken from `PublicDnsName` on an EC2
|
105
|
+
instance. If `PublicDnsName` is not defined, the command uses
|
106
|
+
`PrivateDnsName` instead.
|
107
|
+
|
108
|
+
When the network topology changes, i.e. an instance gets a new IP
|
109
|
+
address, an instance is terminated, or a new instance is launched,
|
110
|
+
rerun the command again to update content in `ssh/config.aws` to
|
111
|
+
reflect the new situation.
|
112
|
+
|
113
|
+
## An Example
|
114
|
+
|
115
|
+
### Example Setup
|
116
|
+
|
117
|
+
The example uses two Ubuntu EC2 instances with `Name` -tags `myFront`
|
118
|
+
and `myBack1`. Instance `myFront` has an internal IP
|
119
|
+
`10.0.0.246`. Instances on subnet `10.0.0.0/24` can be reached over
|
120
|
+
Internet, and `myFront` has been assigned a public IP `52.19.117.227`,
|
121
|
+
and an externally resolvable DNS name
|
122
|
+
`c2-52-19-117-227.eu-west-1.compute.amazonaws.com`. Instance `myBack1`
|
123
|
+
belongs to private subnet `10.0.1.0/24`, and cannot reached directly
|
124
|
+
from Internet. It has a private IP address `10.0.1.242` with a DNS
|
125
|
+
name `ip-10-0-1-242.eu-west-1.compute.internal`. Both of these
|
126
|
+
instances have been created using
|
127
|
+
[Amazon EC2 Key Pair](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html)
|
128
|
+
`demo_key`.
|
129
|
+
|
130
|
+
|
131
|
+
+----------------------------------------------------------------+
|
132
|
+
| Tags: [ "Name": "myFront" ], Ubuntu 14.04 LTS Trusty |
|
133
|
+
| 52.19.117.227/c2-52-19-117-227.eu-west-1.compute.amazonaws.com |
|
134
|
+
| 10.0.0.246/ip-10-0-0-246.eu-west-1.compute.internal |
|
135
|
+
! 10.0.0.0/24 |
|
136
|
+
| .ssh/demo_key.pub |
|
137
|
+
+----------------------------------------------------------------+
|
138
|
+
|
139
|
+
+----------------------------------------------------------------+
|
140
|
+
| Tags: [ "Name": "myBack1" ], Ubuntu 14.04 LTS Trusty |
|
141
|
+
| |
|
142
|
+
|10.0.1.242//ip-10-0-1-242.eu-west-1.compute.internal |
|
143
|
+
|10.0.1.0/24 |
|
144
|
+
|.ssh/demo_key.pub |
|
145
|
+
+----------------------------------------------------------------+
|
146
|
+
|
147
|
+
|
148
|
+
### Initial Configuration
|
149
|
+
|
150
|
+
We start by creating `ssh/config.aws` configuration file with the
|
151
|
+
following initial content
|
152
|
+
|
153
|
+
Host *.compute.internal
|
154
|
+
ProxyCommand ssh myFront1 -F ssh/config.aws nc -q0 %h 22
|
155
|
+
|
156
|
+
Host *
|
157
|
+
user ubuntu
|
158
|
+
StrictHostKeyChecking no
|
159
|
+
UserKnownHostsFile=/dev/null
|
160
|
+
IdentityFile ~/.ssh/demo-key/demo-key
|
161
|
+
|
162
|
+
This configuration instructs OpenSSH to use user name `ubuntu` and SSH
|
163
|
+
private key in `~/.ssh/demo-key/demo-key` for all SSH connections.
|
164
|
+
|
165
|
+
Amazon assigns DNS names ending with `compute.internal` to map to
|
166
|
+
Private IP address. The configuration tells OpenSSH to use `myFront1`
|
167
|
+
as a proxy to connect to instances with Private DNS name.
|
168
|
+
|
169
|
+
|
170
|
+
### Read Network Topology, and Update OpenSSH Configuration
|
171
|
+
|
172
|
+
Running command
|
173
|
+
|
174
|
+
aws ec2 describe-instances | bundle exec aws-ssh-resolver.rb resolve
|
175
|
+
|
176
|
+
reads EC2 information from Amazon platform, extracts `Name` tags and
|
177
|
+
DNS names, and updates `ssh/config.aws` with `host` and `HostName`
|
178
|
+
information as shown below:
|
179
|
+
|
180
|
+
# +++ aws-ssh-resolver-cli update start here +++
|
181
|
+
|
182
|
+
# Content generated 2015-09-06-21:57:37
|
183
|
+
|
184
|
+
host myFront1
|
185
|
+
HostName ec2-52-19-117-227.eu-west-1.compute.amazonaws.com
|
186
|
+
|
187
|
+
|
188
|
+
host myBack1
|
189
|
+
HostName ip-10-0-1-242.eu-west-1.compute.internal
|
190
|
+
|
191
|
+
|
192
|
+
# +++ aws-ssh-resolver-cli update end here +++
|
193
|
+
Host *.compute.internal
|
194
|
+
ProxyCommand ssh myFront1 -F ssh/config.aws nc -q0 %h 22
|
195
|
+
|
196
|
+
Host *
|
197
|
+
user ubuntu
|
198
|
+
StrictHostKeyChecking no
|
199
|
+
UserKnownHostsFile=/dev/null
|
200
|
+
IdentityFile ~/.ssh/demo-key/demo-key
|
201
|
+
|
202
|
+
This configuration adds the host definition for `myFront1`, and
|
203
|
+
instructs OpenSSH to use a Public DNS name to connect the instance.
|
204
|
+
|
205
|
+
The HostName for `myBack1` ends with `compute.internal`, and the
|
206
|
+
OpenSSH uses the proxy definition to access it.
|
207
|
+
|
208
|
+
### Using OpenSSH Configuration to Access ASW Instances
|
209
|
+
|
210
|
+
The configuration in `ssh/configaws` allows us to use tag name
|
211
|
+
`myFront1` to make a SSH connection to machine with the DNS name
|
212
|
+
`c2-52-19-117-227.eu-west-1.compute.amazonaws.com` simply with command
|
213
|
+
|
214
|
+
ssh myFront1 -F ssh/config.aws
|
215
|
+
Warning: Permanently added 'ec2-52-19-117-227.eu-west-1.compute.amazonaws.com,52.19.117.227' (ECDSA) to the list of known hosts.
|
216
|
+
|
217
|
+
|
218
|
+
The instance on subnet `10.0.1.0/24` cannot reached directly, and the
|
219
|
+
configuration instructs OpenSSH to use `myFront` as a intermediary to
|
220
|
+
create a
|
221
|
+
[transparent ssh connection](https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Proxies_and_Jump_Hosts#ProxyCommand_with_Netcat)
|
222
|
+
to `myBack1`. This all takes place transparently, and the simple
|
223
|
+
command
|
224
|
+
|
225
|
+
ssh myBack1 -F ssh/config.aws
|
226
|
+
Warning: Permanently added 'ec2-52-19-117-227.eu-west-1.compute.amazonaws.com,52.19.117.227' (ECDSA) to the list of known hosts.
|
227
|
+
Warning: Permanently added 'ip-10-0-1-242.eu-west-1.compute.internal' (ECDSA) to the list of known hosts.
|
228
|
+
|
229
|
+
creates a SSH connection to `myBack1`.
|
230
|
+
|
231
|
+
Warnings shown above, are due to parameters `UserKnownHostsFile` and
|
232
|
+
`StrictHostKeyChecking`, which prevent ssh from updating the default
|
233
|
+
`.ssh/known_hosts` file with the fingerprints of the (temporary)
|
234
|
+
instances used in testing.
|
235
|
+
|
236
|
+
|
237
|
+
### Updating OpenSSH Configuration
|
238
|
+
|
239
|
+
If the network configuration changes, rerunning
|
240
|
+
|
241
|
+
aws ec2 describe-instances | bundle exec aws-ssh-resolver.rb resolve
|
242
|
+
|
243
|
+
refreshes configuration in `ssh/config.aws`.
|
244
|
+
|
245
|
+
## Changes
|
246
|
+
|
247
|
+
See [RELEASES](RELEASES.md)
|
248
|
+
|
249
|
+
## License
|
250
|
+
|
251
|
+
MIT
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "utils/logger"
|
@@ -0,0 +1,339 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'json'
|
3
|
+
require_relative "../aws-ssh-resolver"
|
4
|
+
|
5
|
+
class Cli < Thor
|
6
|
+
|
7
|
+
include Utils::MyLogger # mix logger
|
8
|
+
PROGNAME = "main" # logger progname
|
9
|
+
|
10
|
+
# ------------------------------------------------------------------
|
11
|
+
# constanst
|
12
|
+
DEFAULT_SSH_CONFIG_FILE = "ssh/config.aws"
|
13
|
+
DEFAULT_SSH_CONFIG_INIT = "ssh/config.init"
|
14
|
+
MAGIC_START = "# +++ aws-ssh-resolver-cli update start here +++"
|
15
|
+
MAGIC_END = "# +++ aws-ssh-resolver-cli update end here +++"
|
16
|
+
DEFAULT_DESCRIBE_INSTANCES = "aws ec2 describe-instances --filters 'Name=tag-key,Values=Name'"
|
17
|
+
DEFAULT_HOST_TAG = "Name"
|
18
|
+
|
19
|
+
# ------------------------------------------------------------------
|
20
|
+
# constructore
|
21
|
+
|
22
|
+
def initialize(*args)
|
23
|
+
super
|
24
|
+
@logger = getLogger( PROGNAME, options )
|
25
|
+
end
|
26
|
+
|
27
|
+
# ------------------------------------------------------------------
|
28
|
+
# make two thor tasks share options?
|
29
|
+
# http://stackoverflow.com/questions/14346285/how-to-make-two-thor-tasks-share-options
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def add_shared_option(name, options = {})
|
33
|
+
@shared_options = {} if @shared_options.nil?
|
34
|
+
@shared_options[name] = options
|
35
|
+
end
|
36
|
+
|
37
|
+
def shared_options(*option_names)
|
38
|
+
option_names.each do |option_name|
|
39
|
+
opt = @shared_options[option_name]
|
40
|
+
raise "Tried to access shared option '#{option_name}' but it was not previously defined" if opt.nil?
|
41
|
+
option option_name, opt
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# # ------------------------------------------------------------------
|
47
|
+
|
48
|
+
class_option :log, :aliases => "-l", :type =>:string, :default => nil,
|
49
|
+
:enum => [ "DEBUG", "INFO", "WARN", "ERROR" ],
|
50
|
+
:desc => "Set debug level "
|
51
|
+
|
52
|
+
# ------------------------------------------------------------------
|
53
|
+
|
54
|
+
add_shared_option :ssh_config_file, :type => :string, :default => DEFAULT_SSH_CONFIG_FILE, :aliases => "-c",
|
55
|
+
:desc => "OpenSSH config file to update/create"
|
56
|
+
|
57
|
+
add_shared_option :ssh_config_init, :type => :string, :default => DEFAULT_SSH_CONFIG_INIT, :aliases => "-i",
|
58
|
+
:desc => "Initialize :ssh-config-file files with this file"
|
59
|
+
|
60
|
+
|
61
|
+
add_shared_option :describe_instances, :type => :string, :default => DEFAULT_DESCRIBE_INSTANCES, :aliases => "-d",
|
62
|
+
:desc => "aws command to query ec2 instances"
|
63
|
+
|
64
|
+
add_shared_option :host_tag, :type => :string, :default => DEFAULT_HOST_TAG, :aliases => "-h",
|
65
|
+
:desc => "Tag defining name of host"
|
66
|
+
|
67
|
+
|
68
|
+
# ------------------------------------------------------------------
|
69
|
+
# common instruction
|
70
|
+
long_desc_notice_on_ssh_config_init = <<-EOS
|
71
|
+
|
72
|
+
NOTICE: By default ':ssh-config-file' seeded from ':ssh-config-init',
|
73
|
+
if it does not exist. Create an empty ':ssh-config-init' file, or
|
74
|
+
pass an empty string to ':ssh-config-init' to avoid an error.
|
75
|
+
|
76
|
+
EOS
|
77
|
+
|
78
|
+
|
79
|
+
# ------------------------------------------------------------------
|
80
|
+
# resolver
|
81
|
+
|
82
|
+
desc "resolve <json-file>", "Create/update OpenSSH config file with AWS HostNames from a JSON document"
|
83
|
+
|
84
|
+
|
85
|
+
long_desc <<-LONGDESC
|
86
|
+
|
87
|
+
Updates ':ssh_config_file' (creates the file if it does not exist) with host/hostname
|
88
|
+
configuration parsed from 'json_file' (defaults to $stdin).
|
89
|
+
|
90
|
+
Entries in ':ssh_config_file' start and end with special tag-lines, which allow the tool
|
91
|
+
to replace host/hostanme entries with new values for each run.
|
92
|
+
|
93
|
+
#{long_desc_notice_on_ssh_config_init}
|
94
|
+
|
95
|
+
LONGDESC
|
96
|
+
|
97
|
+
shared_options :ssh_config_file
|
98
|
+
shared_options :ssh_config_init
|
99
|
+
shared_options :host_tag
|
100
|
+
|
101
|
+
def resolve( json_file="-" )
|
102
|
+
|
103
|
+
@logger.info( "#{__method__} starting, options '#{options}'" )
|
104
|
+
|
105
|
+
host_tag = options[:host_tag]
|
106
|
+
ssh_config_init = options[:ssh_config_init]
|
107
|
+
ssh_config_file = options[:ssh_config_file]
|
108
|
+
# puts( "options=#{options}" )
|
109
|
+
|
110
|
+
# raw data from aws
|
111
|
+
ec2_instances = get_ec2_instances( json_file )
|
112
|
+
|
113
|
+
# hash with host => hostname
|
114
|
+
host_hostname_mappings = create_host_hostname_mappings( ec2_instances, host_tag )
|
115
|
+
|
116
|
+
# seed 'ssh_config_file' with 'ssh_config_init'
|
117
|
+
init_ssh_config_file( ssh_config_file, ssh_config_init )
|
118
|
+
|
119
|
+
# output to file
|
120
|
+
output_to_file( ssh_config_file, host_hostname_mappings )
|
121
|
+
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
# ------------------------------------------------------------------
|
126
|
+
# aws-cli
|
127
|
+
desc "aws", "Create/update OpenSSH config file with AWS HostNames using aws Commad Line query"
|
128
|
+
|
129
|
+
long_desc <<-LONGDESC
|
130
|
+
|
131
|
+
Uses `aws` Command Line Interface to query ec2 information and parse host/hostname
|
132
|
+
information update/create ':ssh_config_file'.
|
133
|
+
|
134
|
+
#{long_desc_notice_on_ssh_config_init}
|
135
|
+
|
136
|
+
LONGDESC
|
137
|
+
|
138
|
+
shared_options :ssh_config_file
|
139
|
+
shared_options :ssh_config_init
|
140
|
+
shared_options :describe_instances
|
141
|
+
shared_options :host_tag
|
142
|
+
|
143
|
+
def aws()
|
144
|
+
|
145
|
+
host_tag = options[:host_tag]
|
146
|
+
ssh_config_file = options[:ssh_config_file]
|
147
|
+
ssh_config_init = options[:ssh_config_init]
|
148
|
+
describe_instances = options[:describe_instances]
|
149
|
+
|
150
|
+
# run aws-cli query
|
151
|
+
ec2_instances = aws_cli_ec2_instances( describe_instances )
|
152
|
+
|
153
|
+
# hash with host => hostname
|
154
|
+
host_hostname_mappings = create_host_hostname_mappings( ec2_instances, host_tag )
|
155
|
+
|
156
|
+
# seed 'ssh_config_file' with 'ssh_config_init'
|
157
|
+
init_ssh_config_file( ssh_config_file, ssh_config_init )
|
158
|
+
|
159
|
+
# output to file
|
160
|
+
output_to_file( ssh_config_file, host_hostname_mappings )
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
# ------------------------------------------------------------------
|
165
|
+
# reset
|
166
|
+
|
167
|
+
desc "reset", "Removes automatic entries in OpenSSH config file"
|
168
|
+
|
169
|
+
|
170
|
+
long_desc <<-LONGDESC
|
171
|
+
|
172
|
+
Removes automatic entries starting with with special tag-lines from ':ssh_config_file'.
|
173
|
+
|
174
|
+
Delete the file if it becomes empty
|
175
|
+
|
176
|
+
LONGDESC
|
177
|
+
|
178
|
+
|
179
|
+
shared_options :ssh_config_file
|
180
|
+
|
181
|
+
def reset( )
|
182
|
+
|
183
|
+
ssh_config_file = options[:ssh_config_file]
|
184
|
+
# Read content of (without magic content) ssh_config_file into memory
|
185
|
+
ssh_config_file_content = read_ssh_config_file_content_minus_magic( ssh_config_file )
|
186
|
+
if ssh_config_file_content.empty?
|
187
|
+
File.delete( ssh_config_file )
|
188
|
+
else
|
189
|
+
File.open( ssh_config_file, 'w') do |f2|
|
190
|
+
ssh_config_file_content.each do |line|
|
191
|
+
f2.puts line
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
# ------------------------------------------------------------------
|
201
|
+
# subrus
|
202
|
+
|
203
|
+
no_commands do
|
204
|
+
|
205
|
+
# return raw ec2 describe-status JSON
|
206
|
+
def aws_cli_ec2_instances( describe_instances )
|
207
|
+
|
208
|
+
@logger.info( "#{__method__} describe_instances '#{describe_instances}'" )
|
209
|
+
|
210
|
+
json_string = %x{ #{describe_instances} }
|
211
|
+
ec2_instances = parse_json( json_string )
|
212
|
+
|
213
|
+
@logger.debug( "#{__method__} describe_instances '#{describe_instances}' --> #{ec2_instances}" )
|
214
|
+
|
215
|
+
return ec2_instances
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
# return raw ec2 describe-status JSON
|
221
|
+
def get_ec2_instances( file )
|
222
|
+
|
223
|
+
@logger.info( "#{__method__} read file '#{file}'" )
|
224
|
+
|
225
|
+
json_string = ( file == "-" ? $stdin.readlines.join : File.read(file) )
|
226
|
+
ec2_instances = parse_json( json_string )
|
227
|
+
|
228
|
+
@logger.debug( "#{__method__} file '#{file}' --> #{ec2_instances}" )
|
229
|
+
return ec2_instances
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
def parse_json( json_string )
|
234
|
+
|
235
|
+
@logger.debug( "#{__method__} json_string '#{json_string}'" )
|
236
|
+
|
237
|
+
ec2_instances = JSON.parse( json_string )
|
238
|
+
|
239
|
+
return ec2_instances
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
# map raw aws ec2-describe-status json to hash with Host/PublicDnsName props
|
245
|
+
def create_host_hostname_mappings( ec2_instances, host_tag )
|
246
|
+
|
247
|
+
@logger.info( "#{__method__} host_tag '#{host_tag}'" )
|
248
|
+
|
249
|
+
host_hostname_mappings = ec2_instances['Reservations']
|
250
|
+
.map{ |i| i['Instances'].first }
|
251
|
+
.select{ |i| i['Tags'].select{ |t| t['Key'] == host_tag }.any? }
|
252
|
+
.map{ |i| {
|
253
|
+
:Host => i['Tags'].select{ |t| t['Key'] == host_tag }.first['Value'],
|
254
|
+
:HostName => i['PublicDnsName'] && !i['PublicDnsName'].empty? ? i['PublicDnsName'] : i['PrivateDnsName']
|
255
|
+
} }
|
256
|
+
|
257
|
+
@logger.info( "#{__method__} host_hostname_mappings '#{host_hostname_mappings}'" )
|
258
|
+
return host_hostname_mappings
|
259
|
+
end
|
260
|
+
|
261
|
+
# add 'host_hostname_mappings' to 'ssh_config_file'
|
262
|
+
def output_to_file( ssh_config_file, host_hostname_mappings )
|
263
|
+
|
264
|
+
# Read content of (without magic content) ssh_config_file into memory
|
265
|
+
ssh_config_file_content = read_ssh_config_file_content_minus_magic( ssh_config_file )
|
266
|
+
|
267
|
+
# write new magic with host entries
|
268
|
+
File.open( ssh_config_file, 'w') do |f2|
|
269
|
+
|
270
|
+
f2.puts MAGIC_START
|
271
|
+
f2.puts <<-EOS
|
272
|
+
|
273
|
+
# Content generated #{Time.now.strftime("%Y-%m-%d-%H:%M:%S")}
|
274
|
+
|
275
|
+
EOS
|
276
|
+
|
277
|
+
host_hostname_mappings.each do |h|
|
278
|
+
host_entry = <<EOS
|
279
|
+
host #{h[:Host]}
|
280
|
+
HostName #{h[:HostName]}
|
281
|
+
|
282
|
+
|
283
|
+
EOS
|
284
|
+
f2.puts host_entry
|
285
|
+
end
|
286
|
+
|
287
|
+
f2.puts MAGIC_END
|
288
|
+
|
289
|
+
ssh_config_file_content.each do |line|
|
290
|
+
f2.puts line
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
# copy 'ssh_config_init' to 'ssh_config_file' - if it does not
|
298
|
+
# exist && 'ssh_config_init' define
|
299
|
+
def init_ssh_config_file( ssh_config_file, ssh_config_init )
|
300
|
+
File.open( ssh_config_file, 'w') { |f| f.write(File.read(ssh_config_init )) } if !File.exist?( ssh_config_file ) &&
|
301
|
+
ssh_config_init && !ssh_config_init.empty?
|
302
|
+
end
|
303
|
+
|
304
|
+
# read ssh_config from file/$stdin, remove old magic
|
305
|
+
def read_ssh_config_file_content_minus_magic( ssh_config_file )
|
306
|
+
|
307
|
+
ssh_config_file_content = File.exist?( ssh_config_file ) ? File.readlines( ssh_config_file ) : []
|
308
|
+
|
309
|
+
# remove old magic
|
310
|
+
within_magic = false
|
311
|
+
ssh_config_file_content = ssh_config_file_content.select do |line|
|
312
|
+
ret = case within_magic
|
313
|
+
when true
|
314
|
+
if line.chomp == MAGIC_END then
|
315
|
+
within_magic = false
|
316
|
+
end
|
317
|
+
false
|
318
|
+
when false
|
319
|
+
if line.chomp == MAGIC_START then
|
320
|
+
within_magic = true
|
321
|
+
end
|
322
|
+
(line.chomp == MAGIC_START ? false : true)
|
323
|
+
end
|
324
|
+
ret
|
325
|
+
end
|
326
|
+
|
327
|
+
return ssh_config_file_content
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
|
332
|
+
|
333
|
+
|
334
|
+
end # no_task
|
335
|
+
|
336
|
+
|
337
|
+
|
338
|
+
|
339
|
+
end # class
|
data/lib/utils/logger.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
# see http://hawkins.io/2013/08/using-the-ruby-logger/
|
4
|
+
|
5
|
+
module Utils
|
6
|
+
|
7
|
+
module MyLogger
|
8
|
+
|
9
|
+
# no logging done
|
10
|
+
|
11
|
+
class NullLoger < Logger
|
12
|
+
def initialize(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(*args, &block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
LOGFILE="aws-ssh-resolver.log"
|
20
|
+
|
21
|
+
def getLogger( progname, options={} )
|
22
|
+
|
23
|
+
level = get_level( options )
|
24
|
+
|
25
|
+
if level.nil?
|
26
|
+
|
27
|
+
return NullLoger.new
|
28
|
+
|
29
|
+
else
|
30
|
+
|
31
|
+
logger = Logger.new( LOGFILE )
|
32
|
+
logger.level=level
|
33
|
+
logger.progname = progname
|
34
|
+
return logger
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end # getLogger
|
39
|
+
|
40
|
+
# ------------------------------------------------------------------
|
41
|
+
private
|
42
|
+
|
43
|
+
def get_level( options )
|
44
|
+
|
45
|
+
# puts "#{__method__}: options=#{options}"
|
46
|
+
|
47
|
+
level_name = options && options[:log] ? options[:log] : ENV['LOG_LEVEL']
|
48
|
+
|
49
|
+
level = case level_name
|
50
|
+
when 'warn', 'WARN'
|
51
|
+
Logger::WARN
|
52
|
+
when 'info', 'INFO'
|
53
|
+
Logger::INFO
|
54
|
+
when 'debug', 'DEBUG'
|
55
|
+
Logger::DEBUG
|
56
|
+
when 'error', 'ERROR'
|
57
|
+
Logger::ERROR
|
58
|
+
else
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
return level
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
describe Cli do
|
6
|
+
|
7
|
+
# ------------------------------------------------------------------
|
8
|
+
# constants
|
9
|
+
cmd = ""
|
10
|
+
|
11
|
+
# ------------------------------------------------------------------
|
12
|
+
# framework
|
13
|
+
describe "rspec framework" do
|
14
|
+
it "#works" do
|
15
|
+
expect( 1 ).to eql( 1 )
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# ------------------------------------------------------------------
|
20
|
+
# interface
|
21
|
+
describe "interface" do
|
22
|
+
|
23
|
+
before :each do
|
24
|
+
@sut = Cli.new
|
25
|
+
end
|
26
|
+
|
27
|
+
it "#defines resolve" do
|
28
|
+
expect( @sut ).to respond_to( :resolve )
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# ------------------------------------------------------------------
|
34
|
+
# help && options
|
35
|
+
interfaces = [
|
36
|
+
{
|
37
|
+
:command => "resolve",
|
38
|
+
:options => [
|
39
|
+
{ :long=>"--log", :short => "-l"},
|
40
|
+
{ :long=>"--ssh-config-file", :short => "-c"},
|
41
|
+
{ :long=>"--ssh-config-init", :short => "-i"},
|
42
|
+
{ :long=>"--host-tag", :short => "-h"},
|
43
|
+
]
|
44
|
+
},
|
45
|
+
{
|
46
|
+
:command => "aws",
|
47
|
+
:options => [
|
48
|
+
{ :long=>"--log", :short => "-l"},
|
49
|
+
{ :long=>"--ssh-config-file", :short => "-c"},
|
50
|
+
{ :long=>"--describe-instances", :short => "-d"},
|
51
|
+
{ :long=>"--ssh-config-init", :short => "-i"},
|
52
|
+
{ :long=>"--host-tag", :short => "-h"},
|
53
|
+
]
|
54
|
+
},
|
55
|
+
{
|
56
|
+
:command => "reset",
|
57
|
+
:options => [
|
58
|
+
{ :long=>"--log", :short => "-l"},
|
59
|
+
{ :long=>"--ssh-config-file", :short => "-c"},
|
60
|
+
]
|
61
|
+
},
|
62
|
+
]
|
63
|
+
|
64
|
+
interfaces.each do |i|
|
65
|
+
|
66
|
+
describe "help #{i[:command]}" do
|
67
|
+
|
68
|
+
before :all do
|
69
|
+
@output = with_captured_stdout { Cli.start(["help", i[:command]]) }
|
70
|
+
end
|
71
|
+
|
72
|
+
i[:options].each do |o|
|
73
|
+
|
74
|
+
it "#option short #{o[:short]}" do
|
75
|
+
expect( @output ).to match( /#{o[:short]}/ )
|
76
|
+
end
|
77
|
+
|
78
|
+
it "#option short #{o[:long]}" do
|
79
|
+
expect( @output ).to match( /#{o[:long]}/ )
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end # options
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end # interfaces each
|
88
|
+
|
89
|
+
|
90
|
+
# ------------------------------------------------------------------
|
91
|
+
# resolve
|
92
|
+
command = "resolve"
|
93
|
+
|
94
|
+
describe "command '#{command}'" do
|
95
|
+
|
96
|
+
context "file" do
|
97
|
+
|
98
|
+
json_file = "spec/cli/fixtures/fixture1.json"
|
99
|
+
|
100
|
+
ssh_config_filename = Cli::DEFAULT_SSH_CONFIG_FILE
|
101
|
+
|
102
|
+
before :each do
|
103
|
+
@dbl_ssh_config_file = double( "ssh-config-file" )
|
104
|
+
expect( File ).to receive( :open ).with( ssh_config_filename, "w").and_yield( @dbl_ssh_config_file )
|
105
|
+
allow( @dbl_ssh_config_file ).to receive( :puts ).with( kind_of( String ))
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
context "when ':ssh-config-file' does not exist" do
|
110
|
+
|
111
|
+
before :each do
|
112
|
+
expect( File ).to receive( :exist? ).with( ssh_config_filename).and_return( false )
|
113
|
+
expect( File ).no_to receive( :readlines? ).with( ssh_config_filename )
|
114
|
+
end
|
115
|
+
|
116
|
+
end # context "when ':ssh-config-file' does not exist" do
|
117
|
+
|
118
|
+
context "when ':ssh-config-file' does exists" do
|
119
|
+
|
120
|
+
before :each do
|
121
|
+
@ssh_config_file_lines= [ "line 1", "line2" ]
|
122
|
+
expect( File ).to receive( :exist? ).twice.with( ssh_config_filename).and_return( true )
|
123
|
+
end
|
124
|
+
|
125
|
+
context "when NO previous resolves" do
|
126
|
+
|
127
|
+
before :each do
|
128
|
+
expect( File ).to receive( :readlines ).with( ssh_config_filename).and_return( @ssh_config_file_lines )
|
129
|
+
end
|
130
|
+
|
131
|
+
it "writes existing lines to ssh-config file" do
|
132
|
+
expect( @dbl_ssh_config_file ).to receive( :puts ).once.with( Cli::MAGIC_START ).ordered
|
133
|
+
expect( @dbl_ssh_config_file ).to receive( :puts ).twice.with( /^host\s+\w+\s*\n\s*HostName\s+\w?/ ).ordered
|
134
|
+
expect( @dbl_ssh_config_file ).to receive( :puts ).once.with( Cli::MAGIC_END ).ordered
|
135
|
+
|
136
|
+
@ssh_config_file_lines.each do |line|
|
137
|
+
expect( @dbl_ssh_config_file ).to receive( :puts ).with( line ).ordered
|
138
|
+
end
|
139
|
+
|
140
|
+
Cli.start( [command, json_file] )
|
141
|
+
end
|
142
|
+
|
143
|
+
end # context "when NO previous resolves"
|
144
|
+
|
145
|
+
context "when previous resolves" do
|
146
|
+
|
147
|
+
before :each do
|
148
|
+
content = [ Cli::MAGIC_START, "old magic", Cli::MAGIC_END ] + @ssh_config_file_lines
|
149
|
+
expect( File ).to receive( :readlines ).with( ssh_config_filename).and_return( content )
|
150
|
+
end
|
151
|
+
|
152
|
+
it "removes previous lines between MAGIC_START - MAGIC_END " do
|
153
|
+
|
154
|
+
expect( @dbl_ssh_config_file ).to receive( :puts ).once.with( Cli::MAGIC_START ).ordered
|
155
|
+
expect( @dbl_ssh_config_file ).to receive( :puts ).with( /^host\s+\w+\s*\n\s*HostName\s+\w?/ ).twice.ordered
|
156
|
+
expect( @dbl_ssh_config_file ).to receive( :puts ).once.with( Cli::MAGIC_END ).ordered
|
157
|
+
|
158
|
+
@ssh_config_file_lines.each do |line|
|
159
|
+
expect( @dbl_ssh_config_file ).to receive( :puts ).with( line ).ordered
|
160
|
+
end
|
161
|
+
|
162
|
+
Cli.start( [command, json_file] )
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
end # context "when ':ssh-config-file' does exists" do
|
169
|
+
|
170
|
+
|
171
|
+
# it "#writes to file" do
|
172
|
+
#
|
173
|
+
# expect( File ).to receive( :read ).with( file ).and_call_original
|
174
|
+
# expect( 1 ).to eql( 1 )
|
175
|
+
# end
|
176
|
+
|
177
|
+
end # context "file" do
|
178
|
+
|
179
|
+
end # describe "resolve" do
|
180
|
+
|
181
|
+
|
182
|
+
|
183
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
{
|
2
|
+
"Reservations": [
|
3
|
+
{
|
4
|
+
"OwnerId": "9999999999999",
|
5
|
+
"ReservationId": "r-0144b6f8",
|
6
|
+
"Groups": [],
|
7
|
+
"Instances": [
|
8
|
+
{
|
9
|
+
"Monitoring": {
|
10
|
+
"State": "disabled"
|
11
|
+
},
|
12
|
+
"PublicDnsName": "ec2-52-19-100-250.eu-west-1.compute.amazonaws.com",
|
13
|
+
"State": {
|
14
|
+
"Code": 16,
|
15
|
+
"Name": "running"
|
16
|
+
},
|
17
|
+
"EbsOptimized": false,
|
18
|
+
"LaunchTime": "2015-09-05T23:43:42.000Z",
|
19
|
+
"PublicIpAddress": "52.19.100.250",
|
20
|
+
"PrivateIpAddress": "10.0.0.16",
|
21
|
+
"ProductCodes": [],
|
22
|
+
"VpcId": "vpc-1025be75",
|
23
|
+
"StateTransitionReason": "",
|
24
|
+
"InstanceId": "i-9d460030",
|
25
|
+
"ImageId": "ami-d74437a0",
|
26
|
+
"PrivateDnsName": "ip-10-0-0-16.eu-west-1.compute.internal",
|
27
|
+
"KeyName": "demo-key",
|
28
|
+
"SecurityGroups": [
|
29
|
+
{
|
30
|
+
"GroupName": "suite2-MyDefaultSecurityGroup-JQ3LNYV0UMSP",
|
31
|
+
"GroupId": "sg-2bd02f4f"
|
32
|
+
}
|
33
|
+
],
|
34
|
+
"ClientToken": "suite-myFro-1PI0COCRIL0BN",
|
35
|
+
"SubnetId": "subnet-678e193e",
|
36
|
+
"InstanceType": "t2.micro",
|
37
|
+
"NetworkInterfaces": [
|
38
|
+
{
|
39
|
+
"Status": "in-use",
|
40
|
+
"MacAddress": "0a:cf:49:ff:1e:49",
|
41
|
+
"SourceDestCheck": true,
|
42
|
+
"VpcId": "vpc-1025be75",
|
43
|
+
"Description": "",
|
44
|
+
"Association": {
|
45
|
+
"PublicIp": "52.19.100.250",
|
46
|
+
"PublicDnsName": "ec2-52-19-100-250.eu-west-1.compute.amazonaws.com",
|
47
|
+
"IpOwnerId": "amazon"
|
48
|
+
},
|
49
|
+
"NetworkInterfaceId": "eni-c0b4629b",
|
50
|
+
"PrivateIpAddresses": [
|
51
|
+
{
|
52
|
+
"PrivateDnsName": "ip-10-0-0-16.eu-west-1.compute.internal",
|
53
|
+
"Association": {
|
54
|
+
"PublicIp": "52.19.100.250",
|
55
|
+
"PublicDnsName": "ec2-52-19-100-250.eu-west-1.compute.amazonaws.com",
|
56
|
+
"IpOwnerId": "amazon"
|
57
|
+
},
|
58
|
+
"Primary": true,
|
59
|
+
"PrivateIpAddress": "10.0.0.16"
|
60
|
+
}
|
61
|
+
],
|
62
|
+
"PrivateDnsName": "ip-10-0-0-16.eu-west-1.compute.internal",
|
63
|
+
"Attachment": {
|
64
|
+
"Status": "attached",
|
65
|
+
"DeviceIndex": 0,
|
66
|
+
"DeleteOnTermination": true,
|
67
|
+
"AttachmentId": "eni-attach-9364c5d6",
|
68
|
+
"AttachTime": "2015-09-05T23:43:42.000Z"
|
69
|
+
},
|
70
|
+
"Groups": [
|
71
|
+
{
|
72
|
+
"GroupName": "suite2-MyDefaultSecurityGroup-JQ3LNYV0UMSP",
|
73
|
+
"GroupId": "sg-2bd02f4f"
|
74
|
+
}
|
75
|
+
],
|
76
|
+
"SubnetId": "subnet-678e193e",
|
77
|
+
"OwnerId": "9999999999999",
|
78
|
+
"PrivateIpAddress": "10.0.0.16"
|
79
|
+
}
|
80
|
+
],
|
81
|
+
"SourceDestCheck": true,
|
82
|
+
"Placement": {
|
83
|
+
"Tenancy": "default",
|
84
|
+
"GroupName": "",
|
85
|
+
"AvailabilityZone": "eu-west-1c"
|
86
|
+
},
|
87
|
+
"Hypervisor": "xen",
|
88
|
+
"BlockDeviceMappings": [
|
89
|
+
{
|
90
|
+
"DeviceName": "/dev/sda1",
|
91
|
+
"Ebs": {
|
92
|
+
"Status": "attached",
|
93
|
+
"DeleteOnTermination": true,
|
94
|
+
"VolumeId": "vol-29df06e6",
|
95
|
+
"AttachTime": "2015-09-05T23:43:44.000Z"
|
96
|
+
}
|
97
|
+
}
|
98
|
+
],
|
99
|
+
"Architecture": "x86_64",
|
100
|
+
"RootDeviceType": "ebs",
|
101
|
+
"RootDeviceName": "/dev/sda1",
|
102
|
+
"VirtualizationType": "hvm",
|
103
|
+
"Tags": [
|
104
|
+
{
|
105
|
+
"Value": "myFront1",
|
106
|
+
"Key": "Name"
|
107
|
+
},
|
108
|
+
{
|
109
|
+
"Value": "myFront1",
|
110
|
+
"Key": "aws:cloudformation:logical-id"
|
111
|
+
},
|
112
|
+
{
|
113
|
+
"Value": "arn:aws:cloudformation:eu-west-1:9999999999999:stack/suite2/cbabcd80-5427-11e5-a493-50d500fa4218",
|
114
|
+
"Key": "aws:cloudformation:stack-id"
|
115
|
+
},
|
116
|
+
{
|
117
|
+
"Value": "suite2",
|
118
|
+
"Key": "aws:cloudformation:stack-name"
|
119
|
+
}
|
120
|
+
],
|
121
|
+
"AmiLaunchIndex": 0
|
122
|
+
}
|
123
|
+
]
|
124
|
+
},
|
125
|
+
{
|
126
|
+
"OwnerId": "9999999999999",
|
127
|
+
"ReservationId": "r-9145b768",
|
128
|
+
"Groups": [],
|
129
|
+
"Instances": [
|
130
|
+
{
|
131
|
+
"Monitoring": {
|
132
|
+
"State": "disabled"
|
133
|
+
},
|
134
|
+
"PublicDnsName": "",
|
135
|
+
"State": {
|
136
|
+
"Code": 16,
|
137
|
+
"Name": "running"
|
138
|
+
},
|
139
|
+
"EbsOptimized": false,
|
140
|
+
"LaunchTime": "2015-09-05T23:43:42.000Z",
|
141
|
+
"PrivateIpAddress": "10.0.1.76",
|
142
|
+
"ProductCodes": [],
|
143
|
+
"VpcId": "vpc-1025be75",
|
144
|
+
"StateTransitionReason": "",
|
145
|
+
"InstanceId": "i-52387eff",
|
146
|
+
"ImageId": "ami-d74437a0",
|
147
|
+
"PrivateDnsName": "ip-10-0-1-76.eu-west-1.compute.internal",
|
148
|
+
"KeyName": "demo-key",
|
149
|
+
"SecurityGroups": [
|
150
|
+
{
|
151
|
+
"GroupName": "suite2-MyDefaultSecurityGroup-JQ3LNYV0UMSP",
|
152
|
+
"GroupId": "sg-2bd02f4f"
|
153
|
+
}
|
154
|
+
],
|
155
|
+
"ClientToken": "suite-myBac-Q9KBSFHUQADW",
|
156
|
+
"SubnetId": "subnet-668e193f",
|
157
|
+
"InstanceType": "t2.micro",
|
158
|
+
"NetworkInterfaces": [
|
159
|
+
{
|
160
|
+
"Status": "in-use",
|
161
|
+
"MacAddress": "0a:a1:53:0b:f5:f5",
|
162
|
+
"SourceDestCheck": true,
|
163
|
+
"VpcId": "vpc-1025be75",
|
164
|
+
"Description": "",
|
165
|
+
"NetworkInterfaceId": "eni-c5b4629e",
|
166
|
+
"PrivateIpAddresses": [
|
167
|
+
{
|
168
|
+
"PrivateDnsName": "ip-10-0-1-76.eu-west-1.compute.internal",
|
169
|
+
"Primary": true,
|
170
|
+
"PrivateIpAddress": "10.0.1.76"
|
171
|
+
}
|
172
|
+
],
|
173
|
+
"PrivateDnsName": "ip-10-0-1-76.eu-west-1.compute.internal",
|
174
|
+
"Attachment": {
|
175
|
+
"Status": "attached",
|
176
|
+
"DeviceIndex": 0,
|
177
|
+
"DeleteOnTermination": true,
|
178
|
+
"AttachmentId": "eni-attach-3e64c57b",
|
179
|
+
"AttachTime": "2015-09-05T23:43:42.000Z"
|
180
|
+
},
|
181
|
+
"Groups": [
|
182
|
+
{
|
183
|
+
"GroupName": "suite2-MyDefaultSecurityGroup-JQ3LNYV0UMSP",
|
184
|
+
"GroupId": "sg-2bd02f4f"
|
185
|
+
}
|
186
|
+
],
|
187
|
+
"SubnetId": "subnet-668e193f",
|
188
|
+
"OwnerId": "9999999999999",
|
189
|
+
"PrivateIpAddress": "10.0.1.76"
|
190
|
+
}
|
191
|
+
],
|
192
|
+
"SourceDestCheck": true,
|
193
|
+
"Placement": {
|
194
|
+
"Tenancy": "default",
|
195
|
+
"GroupName": "",
|
196
|
+
"AvailabilityZone": "eu-west-1c"
|
197
|
+
},
|
198
|
+
"Hypervisor": "xen",
|
199
|
+
"BlockDeviceMappings": [
|
200
|
+
{
|
201
|
+
"DeviceName": "/dev/sda1",
|
202
|
+
"Ebs": {
|
203
|
+
"Status": "attached",
|
204
|
+
"DeleteOnTermination": true,
|
205
|
+
"VolumeId": "vol-40de078f",
|
206
|
+
"AttachTime": "2015-09-05T23:43:45.000Z"
|
207
|
+
}
|
208
|
+
}
|
209
|
+
],
|
210
|
+
"Architecture": "x86_64",
|
211
|
+
"RootDeviceType": "ebs",
|
212
|
+
"RootDeviceName": "/dev/sda1",
|
213
|
+
"VirtualizationType": "hvm",
|
214
|
+
"Tags": [
|
215
|
+
{
|
216
|
+
"Value": "arn:aws:cloudformation:eu-west-1:9999999999999:stack/suite2/cbabcd80-5427-11e5-a493-50d500fa4218",
|
217
|
+
"Key": "aws:cloudformation:stack-id"
|
218
|
+
},
|
219
|
+
{
|
220
|
+
"Value": "suite2",
|
221
|
+
"Key": "aws:cloudformation:stack-name"
|
222
|
+
},
|
223
|
+
{
|
224
|
+
"Value": "myBack1",
|
225
|
+
"Key": "aws:cloudformation:logical-id"
|
226
|
+
},
|
227
|
+
{
|
228
|
+
"Value": "myBack1",
|
229
|
+
"Key": "Name"
|
230
|
+
}
|
231
|
+
],
|
232
|
+
"AmiLaunchIndex": 0
|
233
|
+
}
|
234
|
+
]
|
235
|
+
}
|
236
|
+
]
|
237
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative "../../lib/cli/aws-ssh-resolver-cli"
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
|
5
|
+
|
6
|
+
# http://stackoverflow.com/questions/14987362/how-can-i-capture-stdout-to-a-string
|
7
|
+
def with_captured_stdout
|
8
|
+
begin
|
9
|
+
old_stdout = $stdout
|
10
|
+
$stdout = StringIO.new('','w')
|
11
|
+
$stdout.sync = true
|
12
|
+
yield
|
13
|
+
$stdout.string
|
14
|
+
ensure
|
15
|
+
$stdout = old_stdout
|
16
|
+
$stdout.sync = true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end # RSpec.configure do |config|
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aws-ssh-resolver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- jarjuk
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.18'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.18'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: |2
|
42
|
+
Update OpenSSH config file to map EC2 instance name in CloudFormation
|
43
|
+
to DNS-name on Amazon platform.
|
44
|
+
email:
|
45
|
+
executables:
|
46
|
+
- aws-ssh-resolver.rb
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- README.md
|
51
|
+
- bin/aws-ssh-resolver.rb
|
52
|
+
- lib/aws-ssh-resolver.rb
|
53
|
+
- lib/cli/aws-ssh-resolver-cli.rb
|
54
|
+
- lib/utils/logger.rb
|
55
|
+
- spec/cli/aws-ssh-resolver-cli_spec.rb
|
56
|
+
- spec/cli/fixtures/fixture1.json
|
57
|
+
- spec/cli/spec_helper.rb
|
58
|
+
homepage: https://github.com/jarjuk/aws-ssh-resolver
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.2.2
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: Update OpenSSH config with CloudFormation EC2 instance DNS names'
|
82
|
+
test_files: []
|
83
|
+
has_rdoc:
|