+++ /dev/null
-##
-# Copyright 2016 Canonical Ltd.
-# All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-##
-
-from charmhelpers.core.hookenv import (
- config,
-)
-import io
-import paramiko
-
-from subprocess import (
- Popen,
- CalledProcessError,
- PIPE,
-)
-
-
-def _run(cmd, env=None):
- """ Run a command, either on the local machine or remotely via SSH. """
- if isinstance(cmd, str):
- cmd = cmd.split(' ') if ' ' in cmd else [cmd]
-
- cfg = config()
- if all(k in cfg for k in ['ssh-hostname', 'ssh-username',
- 'ssh-password', 'ssh-private-key']):
- host = cfg['ssh-hostname']
- user = cfg['ssh-username']
- passwd = cfg['ssh-password']
- key = cfg['ssh-private-key']
-
- if host and user and (passwd or key):
- return ssh(cmd, host, user, passwd, key)
-
- p = Popen(cmd,
- env=env,
- shell=True,
- stdout=PIPE,
- stderr=PIPE)
- stdout, stderr = p.communicate()
- retcode = p.poll()
- if retcode > 0:
- raise CalledProcessError(returncode=retcode,
- cmd=cmd,
- output=stderr.decode("utf-8").strip())
- return (stdout.decode('utf-8').strip(), stderr.decode('utf-8').strip())
-
-
-def get_ssh_client(host, user, password=None, key=None):
- """Return a connected Paramiko ssh object"""
-
- client = paramiko.SSHClient()
- client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-
- pkey = None
- if key:
- f = io.StringIO(key)
- pkey = paramiko.RSAKey.from_private_key(f)
-
- ###########################################################################
- # There is a bug in some versions of OpenSSH 4.3 (CentOS/RHEL 5) where #
- # the server may not send the SSH_MSG_USERAUTH_BANNER message except when #
- # responding to an auth_none request. For example, paramiko will attempt #
- # to use password authentication when a password is set, but the server #
- # could deny that, instead requesting keyboard-interactive. The hack to #
- # workaround this is to attempt a reconnect, which will receive the right #
- # banner, and authentication can proceed. See the following for more info #
- # https://github.com/paramiko/paramiko/issues/432 #
- # https://github.com/paramiko/paramiko/pull/438 #
- ###########################################################################
-
- try:
- client.connect(host, port=22, username=user,
- password=password, pkey=pkey)
- except paramiko.ssh_exception.SSHException as e:
- if 'Error reading SSH protocol banner' == str(e):
- # Once more, with feeling
- client.connect(host, port=22, username=user,
- password=password, pkey=pkey)
- pass
- else:
- raise paramiko.ssh_exception.SSHException(e)
-
- return client
-
-
-def sftp(local_file, remote_file, host, user, password=None, key=None):
- """Copy a local file to a remote host"""
- client = get_ssh_client(host, user, password, key)
-
- # Create an sftp connection from the underlying transport
- sftp = paramiko.SFTPClient.from_transport(client.get_transport())
- sftp.put(local_file, remote_file)
- client.close()
-
-
-def ssh(cmd, host, user, password=None, key=None):
- """ Run an arbitrary command over SSH. """
- client = get_ssh_client(host, user, password, key)
-
- cmds = ' '.join(cmd)
- stdin, stdout, stderr = client.exec_command(cmds, get_pty=True)
- retcode = stdout.channel.recv_exit_status()
- client.close() # @TODO re-use connections
- if retcode > 0:
- output = stderr.read().strip()
- raise CalledProcessError(returncode=retcode, cmd=cmd,
- output=output)
- return (
- stdout.read().decode('utf-8').strip(),
- stderr.read().decode('utf-8').strip()
- )