Skip to content

Commit 8efd2fb

Browse files
committed
support templating and ansible_ssh_common_args in ansible-ssh
1 parent ef9ca2a commit 8efd2fb

File tree

1 file changed

+41
-43
lines changed

1 file changed

+41
-43
lines changed

dev/ansible-ssh

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
#!/usr/bin/env python3
2+
"""
3+
SSH to a cluster host using connection properties from Ansible inventory.
24
3-
# This tool allows you to ssh into a host using the ansible inventory.
4-
# Example: ansible-ssh compute[0] -o GlobalKnownHostsFile=/dev/null -o
5-
# UserKnownHostsFile=/dev/null
5+
Usage:
6+
ansible-ssh [-n] PATTERN
7+
8+
where PATTERN can be any of:
9+
- host e.g. mycluster-login-0
10+
- group e.g. login
11+
- group[index] e.g. compute[0]
12+
options:
13+
-n Disable strict host key checking and do not store accepted keys
14+
"""
615

716
import json
817
import os
@@ -11,55 +20,44 @@ import subprocess
1120
import sys
1221
from collections import defaultdict
1322

14-
15-
def _optional_arg(prototype, *values):
16-
# returns empty string if any of the values are falsey
17-
filtered = [value for value in values if value]
18-
return prototype.format(*values) if len(values) == len(filtered) else ""
19-
23+
NO_HOSTKEY_CHECK_OPTS = [
24+
'-o', 'StrictHostKeyChecking=no',
25+
'-o', 'GlobalKnownHostsFile=/dev/null',
26+
'-o', 'UserKnownHostsFile=/dev/null'
27+
]
2028

2129
if __name__ == "__main__":
22-
if len(sys.argv) < 2:
23-
msg = (
24-
f"Usage: {sys.argv[0]} <inventory_hostname> [args to pass to ssh]")
25-
print(msg, file=sys.stderr)
30+
no_known_hosts = '-n' in sys.argv
31+
if sys.argv[-1] in (sys.argv[0], '-n'):
32+
print(__doc__, file=sys.stderr)
2633
sys.exit(-1)
2734

28-
# Quote to prevent shell injection
29-
host = shlex.quote(sys.argv[1])
35+
pattern = sys.argv[-1]
3036

37+
template_str = ("ssh "
38+
"{% if ansible_ssh_port | default(false) %}-p {{ ansible_ssh_port }} {% endif %}"
39+
"{% if ansible_ssh_private_key_file | default(false) %} -i {{ ansible_ssh_private_key_file }} {% endif %}"
40+
"{{ ansible_ssh_common_args | default('') }} "
41+
"{{ ansible_user }}@{{ ansible_host }}")
42+
module_args = json.dumps({'msg':template_str})
43+
ansible_cmd = ['ansible', pattern, '-o', '-m', 'debug', '-a', module_args]
3144
try:
32-
output = subprocess.check_output(
33-
f'ansible-inventory --host {host}', shell=True)
45+
output = subprocess.check_output(ansible_cmd, text=True)
3446
except (subprocess.CalledProcessError) as e:
35-
msg = (f"[ERROR]: Is {host} missing from the inventory?")
36-
print(msg, file=sys.stderr)
37-
sys.exit(-1)
38-
39-
meta = defaultdict(str, json.loads(output))
40-
41-
ansible_ssh_host = meta['ansible_ssh_host'] or meta['ansible_host']
42-
ansible_ssh_user = meta['ansible_ssh_user'] or meta['ansible_user']
43-
ansible_ssh_port = meta['ansible_ssh_port']
44-
ansible_ssh_private_key_file = meta['ansible_ssh_private_key_file']
45-
46-
port = _optional_arg("-p {}", ansible_ssh_port)
47-
identity = _optional_arg("-i {}", ansible_ssh_private_key_file)
48-
host = _optional_arg("{}@{}", ansible_ssh_user, ansible_ssh_host)
49-
opts = meta['ansible_ssh_common_args']
50-
51-
# Handle case where user is not set
52-
if not host:
53-
host = ansible_ssh_host
54-
55-
if not host:
56-
# if we get here, "ansible_ssh_host" is not set.
57-
msg = f"Could not determine the host"
47+
msg = (f"[ERROR]: Is {pattern} missing from the inventory?")
5848
print(msg, file=sys.stderr)
5949
sys.exit(-1)
50+
# output looks like e.g.
51+
# stg-login-0 | SUCCESS => { "changed": false, ...
52+
# one line per host
53+
result = output.splitlines()[0].split('>', 1)[-1]
54+
expanded = json.loads(result)['msg']
55+
# can assume ansible_host exists b/c defined by terraform
56+
# can assume ansible_user exists b/c defined in common inventory
57+
58+
cmd = shlex.split(expanded)
59+
if no_known_hosts:
60+
cmd = cmd[0:1] + NO_HOSTKEY_CHECK_OPTS + cmd[1:]
6061

61-
base = shlex.split(f'ssh {port} {identity} {opts}')
62-
extras = sys.argv[2:]
63-
cmd = base + extras + [host]
6462
print(f"[INFO]: Running: {subprocess.list2cmdline(cmd)}")
6563
os.execvp(cmd[0], cmd)

0 commit comments

Comments
 (0)