aws-ssh-resolver 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|