1#!/usr/bin/env python3
  2
  3"""Helper functions for ssh connections.
  4Should be rewritten to use a persistent connection object.
  5Clients should cache the retrieved zip file.
  6Not authentication is not implemented. Only hosts from .ssh/config
  7are supported and keys need to be set up.
  8Also doesn't work because everything comes in as a zero length file for some reason.
  9"""
 10
 11import logging
 12
 13# import paramiko
 14
 15from chart.common.path import Path
 16
 17logger = logging.getLogger()
 18
 19SSH_CONFIG_FILE = Path('~/.ssh/config').expand()
 20SSH_ID_FILE = Path('~/.ssh/id_rsa').expand()
 21
 22# Warning: this doesn't work. Files retrieved always have length 0. Use the weird function below
 23# instead
 24# def ssh_open(connection, path):
 25#     """Retrieve `path` from `connection`.
 26#     Connection may be either:
 27
 28#     * a shortcut from ~/.ssh/config
 29#     * a hostname, with same user as currently logged on (?)
 30#     * a string like "user@host"
 31<<<hidden due to potential security issue>>>
 32
 33#     Keys will be used if available, otherwise if connected to a live terminal
 34<<<hidden due to potential security issue>>>
 35
 36#     logger.debug('ssh_open conn {c} path {p}'.format(c=connection, p=path))
 37
 38#     client = paramiko.SSHClient()
 39#     client._policy = paramiko.WarningPolicy()
 40#     client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 41
 42#     ssh_config = paramiko.SSHConfig()
 43#     user_config_file = os.path.expanduser("~/.ssh/config")
 44#     if os.path.exists(user_config_file):
 45#         with open(user_config_file) as f:
 46#             ssh_config.parse(f)
 47
 48#     # cfg = {'hostname': options['hostname'], 'username': options['username]}
 49#     config = {'hostname': connection}
 50
 51#     user_config = ssh_config.lookup(config['hostname'])
 52#     for k in ('hostname', 'user', 'port'):
 53#         if k in user_config:
 54#             config[k] = user_config[k]
 55
 56#     if 'proxycommand' in user_config:
 57#         config['sock'] = paramiko.ProxyCommand(user_config['proxycommand'])
 58
 59#     if 'user' in config:
 60#         config['username'] = config['user']
 61#         del config['user']
 62
 63#     logger.debug('connection {c}'.format(c=config))
 64#     client.connect(**config)
 65#     # sftp = paramiko.SFTPClient(client)
 66#     sftp = client.open_sftp()
 67#     # print(sftp.listdir(os.path.dirname(path)))
 68#     return sftp.open(path, 'r', 102400)
 69
 70
 71class SSHClient:
 72    """Wrap up some paramiko ugliness."""
 73
 74    def __init__(self, connection):
 75        """`connection` must be an entry from .ssh/config.
 76<<<hidden due to potential security issue>>>
 77        Could be improved greatly.
 78        Parsing constructs like ssh://chart@fviprs01 would be nice (?)
 79        for now strip the leading ssh://
 80        """
 81        # we import here because on the TCE it takes 1-2s and this file gets pulled
 82        # in by basically everything
 83        import paramiko
 84        ssh_config = paramiko.SSHConfig()
 85        if SSH_CONFIG_FILE.exists():
 86            with SSH_CONFIG_FILE.open() as f:
 87                ssh_config.parse(f)
 88
 89        host_config = ssh_config.lookup(connection)
 90        hostname = host_config['hostname']
 91        user = host_config['user']
 92        port = host_config.get('port', 22)
 93        transport = paramiko.Transport((hostname, port))
 94        key = paramiko.RSAKey.from_private_key_file(str(SSH_ID_FILE))
 95        transport.start_client()
 96        transport.auth_publickey(user, key)
 97        self.sftp = paramiko.SFTPClient.from_transport(transport)
 98
 99
100def ssh_open(connection, path):
101    """Retrieve `path` through `connection`.
102    For speed we could cache connections.
103    If speed is an issue don't use this and create a single SSHClient and reuse the connection.
104    """
105    logger.debug('sftp open {p}'.format(p=path))
106    return SSHClient(connection).sftp.open(path)