files: # Licensed to the Apache Software Foundation (ASF) under one or more
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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.
  
  """
  Provides generic deployment steps for machines post boot.
  """
  
  from __future__ import with_statement
  
  from typing import Union
  from typing import Optional
  from typing import List
  from typing import IO
  from typing import cast
  
  import os
  import binascii
  
  from libcloud.utils.py3 import basestring, PY3
  from libcloud.compute.base import Node
  from libcloud.compute.ssh import BaseSSHClient
  
  
  class Deployment(object):
      """
      Base class for deployment tasks.
      """
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Runs this deployment task on node using the client provided.
  
          :type node: :class:`Node`
          :keyword node: Node to operate one
  
          :type client: :class:`BaseSSHClient`
          :keyword client: Connected SSH client to use.
  
          :return: :class:`Node`
          """
          raise NotImplementedError(
              'run not implemented for this deployment')
  
      def _get_string_value(self, argument_name, argument_value):
          if not isinstance(argument_value, basestring) and \
             not hasattr(argument_value, 'read'):
              raise TypeError('%s argument must be a string or a file-like '
                              'object' % (argument_name))
  
          if hasattr(argument_value, 'read'):
              argument_value = argument_value.read()
  
          return argument_value
  
  
  class SSHKeyDeployment(Deployment):
      """
      Installs a public SSH Key onto a server.
      """
  
      def __init__(self, key):
          # type: (Union[str, IO]) -> None
          """
          :type key: ``str`` or :class:`File` object
          :keyword key: Contents of the public key write or a file object which
                        can be read.
          """
          self.key = self._get_string_value(argument_name='key',
                                            argument_value=key)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Installs SSH key into ``.ssh/authorized_keys``
  
          See also :class:`Deployment.run`
          """
          client.put(".ssh/authorized_keys", contents=self.key, mode='a')
          return node
  
  
  class FileDeployment(Deployment):
      """
      Installs a file on the server.
      """
  
      def __init__(self, source, target):
          # type: (str, str) -> None
          """
          :type source: ``str``
          :keyword source: Local path of file to be installed
  
          :type target: ``str``
          :keyword target: Path to install file on node
          """
          self.source = source
          self.target = target
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Upload the file, retaining permissions.
  
          See also :class:`Deployment.run`
          """
          perms = int(oct(os.stat(self.source).st_mode)[4:], 8)
  
          with open(self.source, 'rb') as fp:
              content = fp.read()
  
          client.put(path=self.target, chmod=perms,
                     contents=content)
          return node
  
  
  class ScriptDeployment(Deployment):
      """
      Runs an arbitrary shell script on the server.
  
      This step works by first writing the content of the shell script (script
      argument) in a *.sh file on a remote server and then running that file.
  
      If you are running a non-shell script, make sure to put the appropriate
      shebang to the top of the script. You are also advised to do that even if
      you are running a plan shell script.
      """
  
      def __init__(self,
                   script,  # type: str
                   args=None,  # type: Optional[List[str]]
                   name=None,  # type: Optional[str]
                   delete=False,  # type bool
                   timeout=None  # type: Optional[float]
                   ):
          # type: (...) -> None
          """
          :type script: ``str``
          :keyword script: Contents of the script to run.
  
          :type args: ``list``
          :keyword args: Optional command line arguments which get passed to the
                         deployment script file.
  
          :type name: ``str``
          :keyword name: Name of the script to upload it as, if not specified,
                         a random name will be chosen.
  
          :type delete: ``bool``
          :keyword delete: Whether to delete the script on completion.
  
          :param timeout: Optional run timeout for this command.
          :type timeout: ``float``
          """
          script = self._get_string_value(argument_name='script',
                                          argument_value=script)
  
          self.script = script
          self.args = args or []
          self.stdout = None  # type: Optional[str]
          self.stderr = None  # type: Optional[str]
          self.exit_status = None  # type: Optional[int]
          self.delete = delete
          self.timeout = timeout
          self.name = name  # type: Optional[str]
  
          if self.name is None:
              # File is put under user's home directory
              # (~/libcloud_deployment_<random_string>.sh)
              random_string = ''  # type: Union[str, bytes]
              random_string = binascii.hexlify(os.urandom(4))
              random_string = cast(bytes, random_string)
              random_string = random_string.decode('ascii')
              self.name = 'libcloud_deployment_%s.sh' % (random_string)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Uploads the shell script and then executes it.
  
          See also :class:`Deployment.run`
          """
          self.name = cast(str, self.name)
          file_path = client.put(path=self.name, chmod=int('755', 8),
                                 contents=self.script)
  
          # Pre-pend cwd if user specified a relative path
          if self.name and self.name[0] != '/':
              base_path = os.path.dirname(file_path)
              name = os.path.join(base_path, self.name)
          else:
              self.name = cast(str, self.name)
              name = self.name
  
          cmd = name
  
          if self.args:
              # Append arguments to the command
              cmd = '%s %s' % (name, ' '.join(self.args))
          else:
              cmd = name
  
          self.stdout, self.stderr, self.exit_status = \
              client.run(cmd, timeout=self.timeout)
  
          if self.delete:
              client.delete(self.name)
  
          return node
  
      def __str__(self):
          return self.__repr__()
  
      def __repr__(self):
          script = self.script[:15] + '...'
          exit_status = self.exit_status
  
          if exit_status is not None:
              stdout = self.stdout[:30] + '...'
              stderr = self.stderr[:30] + '...'
          else:
              exit_status = 'script didn\'t run yet'
              stdout = None
              stderr = None
          return ("<ScriptDeployment script=%s, exit_status=%s, stdout=%s, "
                  "stderr=%s>" % (script, exit_status, stdout, stderr))
  
  
  class ScriptFileDeployment(ScriptDeployment):
      """
      Runs an arbitrary shell script from a local file on the server. Same as
      ScriptDeployment, except that you can pass in a path to the file instead of
      the script content.
      """
  
      def __init__(self,
                   script_file,  # type: str
                   args=None,  # type: Optional[List[str]]
                   name=None,  # type: Optional[str]
                   delete=False,  # type bool
                   timeout=None  # type: Optional[float]
                   ):
          # type: (...) -> None
          """
          :type script_file: ``str``
          :keyword script_file: Path to a file containing the script to run.
  
          :type args: ``list``
          :keyword args: Optional command line arguments which get passed to the
                         deployment script file.
  
  
          :type name: ``str``
          :keyword name: Name of the script to upload it as, if not specified,
                         a random name will be chosen.
  
          :type delete: ``bool``
          :keyword delete: Whether to delete the script on completion.
  
          :param timeout: Optional run timeout for this command.
          :type timeout: ``float``
          """
          with open(script_file, 'rb') as fp:
              content = fp.read()  # type: Union[bytes, str]
  
          if PY3:
              content = cast(bytes, content)
              content = content.decode('utf-8')
  
          super(ScriptFileDeployment, self).__init__(script=content,
                                                     args=args,
                                                     name=name,
                                                     delete=delete,
                                                     timeout=timeout)
  
  
  class MultiStepDeployment(Deployment):
      """
      Runs a chain of Deployment steps.
      """
      def __init__(self, add=None):
          # type: (Optional[Union[Deployment, List[Deployment]]]) -> None
          """
          :type add: ``list``
          :keyword add: Deployment steps to add.
          """
          self.steps = []  # type: list
  
          if add:
              self.add(add)
  
      def add(self, add):
          # type: (Union[Deployment, List[Deployment]]) -> None
          """
          Add a deployment to this chain.
  
          :type add: Single :class:`Deployment` or a ``list`` of
                     :class:`Deployment`
          :keyword add: Adds this deployment to the others already in this
                        object.
          """
          if add is not None:
              add = add if isinstance(add, (list, tuple)) else [add]
              self.steps.extend(add)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Run each deployment that has been added.
  
          See also :class:`Deployment.run`
          """
          for s in self.steps:
              node = s.run(node, client)
          return node
  
  
  [... the rest is omitted ...]
files: libcloud/compute/deployment.py
files: # Licensed to the Apache Software Foundation (ASF) under one or more
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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.
  
  """
  Provides generic deployment steps for machines post boot.
  """
  
  from __future__ import with_statement
  
  from typing import Union
  from typing import Optional
  from typing import List
  from typing import IO
  from typing import cast
  
  import os
  import binascii
  
  from libcloud.utils.py3 import basestring, PY3
  from libcloud.compute.base import Node
  from libcloud.compute.ssh import BaseSSHClient
  
  
  class Deployment(object):
      """
      Base class for deployment tasks.
      """
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Runs this deployment task on node using the client provided.
  
          :type node: :class:`Node`
          :keyword node: Node to operate one
  
          :type client: :class:`BaseSSHClient`
          :keyword client: Connected SSH client to use.
  
          :return: :class:`Node`
          """
          raise NotImplementedError(
              'run not implemented for this deployment')
  
      def _get_string_value(self, argument_name, argument_value):
          if not isinstance(argument_value, basestring) and \
             not hasattr(argument_value, 'read'):
              raise TypeError('%s argument must be a string or a file-like '
                              'object' % (argument_name))
  
          if hasattr(argument_value, 'read'):
              argument_value = argument_value.read()
  
          return argument_value
  
  
  class SSHKeyDeployment(Deployment):
      """
      Installs a public SSH Key onto a server.
      """
  
      def __init__(self, key):
          # type: (Union[str, IO]) -> None
          """
          :type key: ``str`` or :class:`File` object
          :keyword key: Contents of the public key write or a file object which
                        can be read.
          """
          self.key = self._get_string_value(argument_name='key',
                                            argument_value=key)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Installs SSH key into ``.ssh/authorized_keys``
  
          See also :class:`Deployment.run`
          """
          client.put(".ssh/authorized_keys", contents=self.key, mode='a')
          return node
  
  
  class FileDeployment(Deployment):
      """
      Installs a file on the server.
      """
  
      def __init__(self, source, target):
          # type: (str, str) -> None
          """
          :type source: ``str``
          :keyword source: Local path of file to be installed
  
          :type target: ``str``
          :keyword target: Path to install file on node
          """
          self.source = source
          self.target = target
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Upload the file, retaining permissions.
  
          See also :class:`Deployment.run`
          """
          perms = int(oct(os.stat(self.source).st_mode)[4:], 8)
  
          with open(self.source, 'rb') as fp:
              content = fp.read()
  
          client.put(path=self.target, chmod=perms,
                     contents=content)
          return node
  
  
  class ScriptDeployment(Deployment):
      """
      Runs an arbitrary shell script on the server.
  
      This step works by first writing the content of the shell script (script
      argument) in a *.sh file on a remote server and then running that file.
  
      If you are running a non-shell script, make sure to put the appropriate
      shebang to the top of the script. You are also advised to do that even if
      you are running a plan shell script.
      """
  
      def __init__(self, script, args=None, name=None, delete=False):
          # type: (str, Optional[List[str]], Optional[str], bool) -> None
          """
          :type script: ``str``
          :keyword script: Contents of the script to run.
  
          :type args: ``list``
          :keyword args: Optional command line arguments which get passed to the
                         deployment script file.
  
          :type name: ``str``
          :keyword name: Name of the script to upload it as, if not specified,
                         a random name will be chosen.
  
          :type delete: ``bool``
          :keyword delete: Whether to delete the script on completion.
          """
          script = self._get_string_value(argument_name='script',
                                          argument_value=script)
  
          self.script = script
          self.args = args or []
          self.stdout = None  # type: Optional[str]
          self.stderr = None  # type: Optional[str]
          self.exit_status = None  # type: Optional[int]
          self.delete = delete
          self.name = name  # type: Optional[str]
  
          if self.name is None:
              # File is put under user's home directory
              # (~/libcloud_deployment_<random_string>.sh)
              random_string = ''  # type: Union[str, bytes]
              random_string = binascii.hexlify(os.urandom(4))
              random_string = cast(bytes, random_string)
              random_string = random_string.decode('ascii')
              self.name = 'libcloud_deployment_%s.sh' % (random_string)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Uploads the shell script and then executes it.
  
          See also :class:`Deployment.run`
          """
          self.name = cast(str, self.name)
          file_path = client.put(path=self.name, chmod=int('755', 8),
                                 contents=self.script)
  
          # Pre-pend cwd if user specified a relative path
          if self.name and self.name[0] != '/':
              base_path = os.path.dirname(file_path)
              name = os.path.join(base_path, self.name)
          else:
              self.name = cast(str, self.name)
              name = self.name
  
          cmd = name
  
          if self.args:
              # Append arguments to the command
              cmd = '%s %s' % (name, ' '.join(self.args))
          else:
              cmd = name
  
          self.stdout, self.stderr, self.exit_status = client.run(cmd)
  
          if self.delete:
              client.delete(self.name)
  
          return node
  
      def __str__(self):
          return self.__repr__()
  
      def __repr__(self):
          script = self.script[:15] + '...'
          exit_status = self.exit_status
  
          if exit_status is not None:
              stdout = self.stdout[:30] + '...'
              stderr = self.stderr[:30] + '...'
          else:
              exit_status = 'script didn\'t run yet'
              stdout = None
              stderr = None
          return ("<ScriptDeployment script=%s, exit_status=%s, stdout=%s, "
                  "stderr=%s>" % (script, exit_status, stdout, stderr))
  
  
  class ScriptFileDeployment(ScriptDeployment):
      """
      Runs an arbitrary shell script from a local file on the server. Same as
      ScriptDeployment, except that you can pass in a path to the file instead of
      the script content.
      """
  
      def __init__(self, script_file, args=None, name=None, delete=False):
          # type: (str, Optional[List[str]], Optional[str], bool) -> None
          """
          :type script_file: ``str``
          :keyword script_file: Path to a file containing the script to run.
  
          :type args: ``list``
          :keyword args: Optional command line arguments which get passed to the
                         deployment script file.
  
  
          :type name: ``str``
          :keyword name: Name of the script to upload it as, if not specified,
                         a random name will be chosen.
  
          :type delete: ``bool``
          :keyword delete: Whether to delete the script on completion.
          """
          with open(script_file, 'rb') as fp:
              content = fp.read()  # type: Union[bytes, str]
  
          if PY3:
              content = cast(bytes, content)
              content = content.decode('utf-8')
  
          super(ScriptFileDeployment, self).__init__(script=content,
                                                     args=args,
                                                     name=name,
                                                     delete=delete)
  
  
  class MultiStepDeployment(Deployment):
      """
      Runs a chain of Deployment steps.
      """
      def __init__(self, add=None):
          # type: (Optional[Union[Deployment, List[Deployment]]]) -> None
          """
          :type add: ``list``
          :keyword add: Deployment steps to add.
          """
          self.steps = []  # type: list
  
          if add:
              self.add(add)
  
      def add(self, add):
          # type: (Union[Deployment, List[Deployment]]) -> None
          """
          Add a deployment to this chain.
  
          :type add: Single :class:`Deployment` or a ``list`` of
                     :class:`Deployment`
          :keyword add: Adds this deployment to the others already in this
                        object.
          """
          if add is not None:
              add = add if isinstance(add, (list, tuple)) else [add]
              self.steps.extend(add)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Run each deployment that has been added.
  
          See also :class:`Deployment.run`
          """
          for s in self.steps:
              node = s.run(node, client)
          return node
  
  
  [... the rest is omitted ...]
files: libcloud/compute/deployment.py
files: # Licensed to the Apache Software Foundation (ASF) under one or more
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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.
  
  """
  Wraps multiple ways to communicate over SSH.
  """
  
  from typing import Type
  from typing import Optional
  from typing import Tuple
  from typing import List
  from typing import Union
  from typing import cast
  
  have_paramiko = False
  
  try:
      import paramiko
      have_paramiko = True
  except ImportError:
      pass
  
  # Depending on your version of Paramiko, it may cause a deprecation
  # warning on Python 2.6.
  # Ref: https://bugs.launchpad.net/paramiko/+bug/392973
  
  import os
  import time
  import subprocess
  import logging
  import warnings
  
  from os.path import split as psplit
  from os.path import join as pjoin
  
  from libcloud.utils.logging import ExtraLogFormatter
  from libcloud.utils.py3 import StringIO
  from libcloud.utils.py3 import b
  
  __all__ = [
      'BaseSSHClient',
      'ParamikoSSHClient',
      'ShellOutSSHClient',
  
      'SSHCommandTimeoutError'
  ]
  
  SUPPORTED_KEY_TYPES_URL = 'https://libcloud.readthedocs.io/en/latest/compute/deployment.html#supported-private-ssh-key-types' # NOQA
  
  
  class SSHCommandTimeoutError(Exception):
      """
      Exception which is raised when an SSH command times out.
      """
      def __init__(self, cmd, timeout):
          # type: (str, float) -> None
          self.cmd = cmd
          self.timeout = timeout
          self.message = 'Command didn\'t finish in %s seconds' % (timeout)
          super(SSHCommandTimeoutError, self).__init__(self.message)
  
      def __repr__(self):
          return ('<SSHCommandTimeoutError: cmd="%s",timeout=%s)>' %
                  (self.cmd, self.timeout))
  
      def __str__(self):
          return self.__repr__()
  
  
  class BaseSSHClient(object):
      """
      Base class representing a connection over SSH/SCP to a remote node.
      """
  
      def __init__(self,
                   hostname,  # type: str
                   port=22,  # type: int
                   username='root',  # type: str
                   password=None,  # type: Optional[str]
                   key=None,  # type: Optional[str]
                   key_files=None,  # type: Optional[Union[str, List[str]]]
                   timeout=None  # type: Optional[float]
                   ):
          """
          :type hostname: ``str``
          :keyword hostname: Hostname or IP address to connect to.
  
          :type port: ``int``
          :keyword port: TCP port to communicate on, defaults to 22.
  
          :type username: ``str``
          :keyword username: Username to use, defaults to root.
  
          :type password: ``str``
          :keyword password: Password to authenticate with or a password used
                             to unlock a private key if a password protected key
                             is used.
  
          :param key: Deprecated in favor of ``key_files`` argument.
  
          :type key_files: ``str`` or ``list``
          :keyword key_files: A list of paths to the private key files to use.
          """
          if key is not None:
              message = ('You are using deprecated "key" argument which has '
                         'been replaced with "key_files" argument')
              warnings.warn(message, DeprecationWarning)
  
              # key_files has precedent
              key_files = key if not key_files else key_files
  
          self.hostname = hostname
          self.port = port
          self.username = username
          self.password = password
          self.key_files = key_files
          self.timeout = timeout
  
      def connect(self):
          # type: () -> bool
          """
          Connect to the remote node over SSH.
  
          :return: True if the connection has been successfully established,
                   False otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'connect not implemented for this ssh client')
  
      def put(self, path, contents=None, chmod=None, mode='w'):
          # type: (str, Optional[Union[str, bytes]], Optional[int], str) -> str
          """
          Upload a file to the remote node.
  
          :type path: ``str``
          :keyword path: File path on the remote node.
  
          :type contents: ``str``
          :keyword contents: File Contents.
  
          :type chmod: ``int``
          :keyword chmod: chmod file to this after creation.
  
          :type mode: ``str``
          :keyword mode: Mode in which the file is opened.
  
          :return: Full path to the location where a file has been saved.
          :rtype: ``str``
          """
          raise NotImplementedError(
              'put not implemented for this ssh client')
  
      def delete(self, path):
          # type: (str) -> bool
          """
          Delete/Unlink a file on the remote node.
  
          :type path: ``str``
          :keyword path: File path on the remote node.
  
          :return: True if the file has been successfully deleted, False
                   otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'delete not implemented for this ssh client')
  
      def run(self, cmd, timeout=None):
          # type: (str, Optional[float]) -> Tuple[str, str, int]
          """
          Run a command on a remote node.
  
          :type cmd: ``str``
          :keyword cmd: Command to run.
  
          :return ``list`` of [stdout, stderr, exit_status]
          """
          raise NotImplementedError(
              'run not implemented for this ssh client')
  
      def close(self):
          # type: () -> bool
          """
          Shutdown connection to the remote node.
  
          :return: True if the connection has been successfully closed, False
                   otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'close not implemented for this ssh client')
  
      def _get_and_setup_logger(self):
          # type: () -> logging.Logger
          logger = logging.getLogger('libcloud.compute.ssh')
          path = os.getenv('LIBCLOUD_DEBUG')
  
          if path:
              handler = logging.FileHandler(path)
              handler.setFormatter(ExtraLogFormatter())
              logger.addHandler(handler)
              logger.setLevel(logging.DEBUG)
  
          return logger
  
  
  class ParamikoSSHClient(BaseSSHClient):
      """
      A SSH Client powered by Paramiko.
      """
  
      # Maximum number of bytes to read at once from a socket
      CHUNK_SIZE = 4096
  
      # How long to sleep while waiting for command to finish (to prevent busy
      # waiting)
      SLEEP_DELAY = 0.2
  
      def __init__(self,
                   hostname,  # type: str
                   port=22,  # type: int
                   username='root',  # type: str
                   password=None,  # type: Optional[str]
                   key=None,  # type: Optional[str]
                   key_files=None,  # type: Optional[Union[str, List[str]]]
                   key_material=None,  # type: Optional[str]
                   timeout=None  # type: Optional[float]
                   ):
          """
          Authentication is always attempted in the following order:
  
          - The key passed in (if key is provided)
          - Any key we can find through an SSH agent (only if no password and
            key is provided)
          - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (only if no
            password and key is provided)
          - Plain username/password auth, if a password was given (if password is
            provided)
          """
          if key_files and key_material:
              raise ValueError(('key_files and key_material arguments are '
                                'mutually exclusive'))
  
          super(ParamikoSSHClient, self).__init__(hostname=hostname, port=port,
                                                  username=username,
                                                  password=password,
                                                  key=key,
                                                  key_files=key_files,
                                                  timeout=timeout)
  
          self.key_material = key_material
  
          self.client = paramiko.SSHClient()
          self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
          self.logger = self._get_and_setup_logger()
  
      def connect(self):
          conninfo = {'hostname': self.hostname,
                      'port': self.port,
                      'username': self.username,
                      'allow_agent': False,
                      'look_for_keys': False}
  
          if self.password:
              conninfo['password'] = self.password
  
          if self.key_files:
              conninfo['key_filename'] = self.key_files
  
          if self.key_material:
              conninfo['pkey'] = self._get_pkey_object(
                  key=self.key_material,
                  passpharse=self.password)
  
          if not self.password and not (self.key_files or self.key_material):
              conninfo['allow_agent'] = True
              conninfo['look_for_keys'] = True
  
          if self.timeout:
              conninfo['timeout'] = self.timeout
  
          # This is a workaround for paramiko only supporting key files in
          # format staring with "BEGIN RSA PRIVATE KEY".
          # If key_files are provided and a key looks like a PEM formatted key
          # we try to convert it into a format supported by paramiko
          if (self.key_files and not isinstance(self.key_files, (list, tuple))
                  and os.path.isfile(self.key_files)):
              with open(self.key_files, 'r') as fp:
                  key_material = fp.read()
  
              try:
                  pkey = self._get_pkey_object(key=key_material,
                                               passpharse=self.password)
              except paramiko.ssh_exception.PasswordRequiredException as e:
                  raise e
              except Exception:
                  pass
              else:
                  # It appears key is valid, but it was passed in in an invalid
                  # format. Try to use the converted key directly
                  del conninfo['key_filename']
                  conninfo['pkey'] = pkey
  
          extra = {'_hostname': self.hostname, '_port': self.port,
                   '_username': self.username, '_timeout': self.timeout}
  
          if self.password:
              extra['_auth_method'] = 'password'
          else:
              extra['_auth_method'] = 'key_file'
  
              if self.key_files:
                  extra['_key_file'] = self.key_files
  
          self.logger.debug('Connecting to server', extra=extra)
  
          self.client.connect(**conninfo)
          return True
  
      def put(self, path, contents=None, chmod=None, mode='w'):
          extra = {'_path': path, '_mode': mode, '_chmod': chmod}
          self.logger.debug('Uploading file', extra=extra)
  
          sftp = self.client.open_sftp()
          # less than ideal, but we need to mkdir stuff otherwise file() fails
          head, tail = psplit(path)
  
          if path[0] == "/":
              sftp.chdir("/")
          else:
              # Relative path - start from a home directory (~)
              sftp.chdir('.')
  
          for part in head.split("/"):
              if part != "":
                  try:
                      sftp.mkdir(part)
                  except IOError:
                      # so, there doesn't seem to be a way to
                      # catch EEXIST consistently *sigh*
                      pass
                  sftp.chdir(part)
  
  
  
  [... the rest is omitted ...]
files: libcloud/compute/ssh.py
files: # Licensed to the Apache Software Foundation (ASF) under one or more
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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.
  
  """
  Wraps multiple ways to communicate over SSH.
  """
  
  from typing import Type
  from typing import Optional
  from typing import Tuple
  from typing import List
  from typing import Union
  from typing import cast
  
  have_paramiko = False
  
  try:
      import paramiko
      have_paramiko = True
  except ImportError:
      pass
  
  # Depending on your version of Paramiko, it may cause a deprecation
  # warning on Python 2.6.
  # Ref: https://bugs.launchpad.net/paramiko/+bug/392973
  
  import os
  import time
  import subprocess
  import logging
  import warnings
  
  from os.path import split as psplit
  from os.path import join as pjoin
  
  from libcloud.utils.logging import ExtraLogFormatter
  from libcloud.utils.py3 import StringIO
  from libcloud.utils.py3 import b
  
  __all__ = [
      'BaseSSHClient',
      'ParamikoSSHClient',
      'ShellOutSSHClient',
  
      'SSHCommandTimeoutError'
  ]
  
  SUPPORTED_KEY_TYPES_URL = 'https://libcloud.readthedocs.io/en/latest/compute/deployment.html#supported-private-ssh-key-types' # NOQA
  
  
  class SSHCommandTimeoutError(Exception):
      """
      Exception which is raised when an SSH command times out.
      """
      def __init__(self, cmd, timeout):
          # type: (str, float) -> None
          self.cmd = cmd
          self.timeout = timeout
          self.message = 'Command didn\'t finish in %s seconds' % (timeout)
          super(SSHCommandTimeoutError, self).__init__(self.message)
  
      def __repr__(self):
          return ('<SSHCommandTimeoutError: cmd="%s",timeout=%s)>' %
                  (self.cmd, self.timeout))
  
      def __str__(self):
          return self.__repr__()
  
  
  class BaseSSHClient(object):
      """
      Base class representing a connection over SSH/SCP to a remote node.
      """
  
      def __init__(self,
                   hostname,  # type: str
                   port=22,  # type: int
                   username='root',  # type: str
                   password=None,  # type: Optional[str]
                   key=None,  # type: Optional[str]
                   key_files=None,  # type: Optional[Union[str, List[str]]]
                   timeout=None  # type: Optional[float]
                   ):
          """
          :type hostname: ``str``
          :keyword hostname: Hostname or IP address to connect to.
  
          :type port: ``int``
          :keyword port: TCP port to communicate on, defaults to 22.
  
          :type username: ``str``
          :keyword username: Username to use, defaults to root.
  
          :type password: ``str``
          :keyword password: Password to authenticate with or a password used
                             to unlock a private key if a password protected key
                             is used.
  
          :param key: Deprecated in favor of ``key_files`` argument.
  
          :type key_files: ``str`` or ``list``
          :keyword key_files: A list of paths to the private key files to use.
          """
          if key is not None:
              message = ('You are using deprecated "key" argument which has '
                         'been replaced with "key_files" argument')
              warnings.warn(message, DeprecationWarning)
  
              # key_files has precedent
              key_files = key if not key_files else key_files
  
          self.hostname = hostname
          self.port = port
          self.username = username
          self.password = password
          self.key_files = key_files
          self.timeout = timeout
  
      def connect(self):
          # type: () -> bool
          """
          Connect to the remote node over SSH.
  
          :return: True if the connection has been successfully established,
                   False otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'connect not implemented for this ssh client')
  
      def put(self, path, contents=None, chmod=None, mode='w'):
          # type: (str, Optional[Union[str, bytes]], Optional[int], str) -> str
          """
          Upload a file to the remote node.
  
          :type path: ``str``
          :keyword path: File path on the remote node.
  
          :type contents: ``str``
          :keyword contents: File Contents.
  
          :type chmod: ``int``
          :keyword chmod: chmod file to this after creation.
  
          :type mode: ``str``
          :keyword mode: Mode in which the file is opened.
  
          :return: Full path to the location where a file has been saved.
          :rtype: ``str``
          """
          raise NotImplementedError(
              'put not implemented for this ssh client')
  
      def delete(self, path):
          # type: (str) -> bool
          """
          Delete/Unlink a file on the remote node.
  
          :type path: ``str``
          :keyword path: File path on the remote node.
  
          :return: True if the file has been successfully deleted, False
                   otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'delete not implemented for this ssh client')
  
      def run(self, cmd):
          # type: (str) -> Tuple[str, str, int]
          """
          Run a command on a remote node.
  
          :type cmd: ``str``
          :keyword cmd: Command to run.
  
          :return ``list`` of [stdout, stderr, exit_status]
          """
          raise NotImplementedError(
              'run not implemented for this ssh client')
  
      def close(self):
          # type: () -> bool
          """
          Shutdown connection to the remote node.
  
          :return: True if the connection has been successfully closed, False
                   otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'close not implemented for this ssh client')
  
      def _get_and_setup_logger(self):
          # type: () -> logging.Logger
          logger = logging.getLogger('libcloud.compute.ssh')
          path = os.getenv('LIBCLOUD_DEBUG')
  
          if path:
              handler = logging.FileHandler(path)
              handler.setFormatter(ExtraLogFormatter())
              logger.addHandler(handler)
              logger.setLevel(logging.DEBUG)
  
          return logger
  
  
  class ParamikoSSHClient(BaseSSHClient):
      """
      A SSH Client powered by Paramiko.
      """
  
      # Maximum number of bytes to read at once from a socket
      CHUNK_SIZE = 4096
  
      # How long to sleep while waiting for command to finish (to prevent busy
      # waiting)
      SLEEP_DELAY = 0.2
  
      def __init__(self,
                   hostname,  # type: str
                   port=22,  # type: int
                   username='root',  # type: str
                   password=None,  # type: Optional[str]
                   key=None,  # type: Optional[str]
                   key_files=None,  # type: Optional[Union[str, List[str]]]
                   key_material=None,  # type: Optional[str]
                   timeout=None  # type: Optional[float]
                   ):
          """
          Authentication is always attempted in the following order:
  
          - The key passed in (if key is provided)
          - Any key we can find through an SSH agent (only if no password and
            key is provided)
          - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (only if no
            password and key is provided)
          - Plain username/password auth, if a password was given (if password is
            provided)
          """
          if key_files and key_material:
              raise ValueError(('key_files and key_material arguments are '
                                'mutually exclusive'))
  
          super(ParamikoSSHClient, self).__init__(hostname=hostname, port=port,
                                                  username=username,
                                                  password=password,
                                                  key=key,
                                                  key_files=key_files,
                                                  timeout=timeout)
  
          self.key_material = key_material
  
          self.client = paramiko.SSHClient()
          self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
          self.logger = self._get_and_setup_logger()
  
      def connect(self):
          conninfo = {'hostname': self.hostname,
                      'port': self.port,
                      'username': self.username,
                      'allow_agent': False,
                      'look_for_keys': False}
  
          if self.password:
              conninfo['password'] = self.password
  
          if self.key_files:
              conninfo['key_filename'] = self.key_files
  
          if self.key_material:
              conninfo['pkey'] = self._get_pkey_object(
                  key=self.key_material,
                  passpharse=self.password)
  
          if not self.password and not (self.key_files or self.key_material):
              conninfo['allow_agent'] = True
              conninfo['look_for_keys'] = True
  
          if self.timeout:
              conninfo['timeout'] = self.timeout
  
          # This is a workaround for paramiko only supporting key files in
          # format staring with "BEGIN RSA PRIVATE KEY".
          # If key_files are provided and a key looks like a PEM formatted key
          # we try to convert it into a format supported by paramiko
          if (self.key_files and not isinstance(self.key_files, (list, tuple))
                  and os.path.isfile(self.key_files)):
              with open(self.key_files, 'r') as fp:
                  key_material = fp.read()
  
              try:
                  pkey = self._get_pkey_object(key=key_material,
                                               passpharse=self.password)
              except paramiko.ssh_exception.PasswordRequiredException as e:
                  raise e
              except Exception:
                  pass
              else:
                  # It appears key is valid, but it was passed in in an invalid
                  # format. Try to use the converted key directly
                  del conninfo['key_filename']
                  conninfo['pkey'] = pkey
  
          extra = {'_hostname': self.hostname, '_port': self.port,
                   '_username': self.username, '_timeout': self.timeout}
  
          if self.password:
              extra['_auth_method'] = 'password'
          else:
              extra['_auth_method'] = 'key_file'
  
              if self.key_files:
                  extra['_key_file'] = self.key_files
  
          self.logger.debug('Connecting to server', extra=extra)
  
          self.client.connect(**conninfo)
          return True
  
      def put(self, path, contents=None, chmod=None, mode='w'):
          extra = {'_path': path, '_mode': mode, '_chmod': chmod}
          self.logger.debug('Uploading file', extra=extra)
  
          sftp = self.client.open_sftp()
          # less than ideal, but we need to mkdir stuff otherwise file() fails
          head, tail = psplit(path)
  
          if path[0] == "/":
              sftp.chdir("/")
          else:
              # Relative path - start from a home directory (~)
              sftp.chdir('.')
  
          for part in head.split("/"):
              if part != "":
                  try:
                      sftp.mkdir(part)
                  except IOError:
                      # so, there doesn't seem to be a way to
                      # catch EEXIST consistently *sigh*
                      pass
                  sftp.chdir(part)
  
          cwd = sftp.get
  
  [... the rest is omitted ...]
files: libcloud/compute/ssh.py
files: # -*- coding: utf-8 -*-
  # Licensed to the Apache Software Foundation (ASF) under one or moreÂ§
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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 __future__ import with_statement
  
  import os
  import sys
  import time
  import unittest
  
  from libcloud.utils.py3 import httplib
  from libcloud.utils.py3 import u
  from libcloud.utils.py3 import PY3
  from libcloud.utils.py3 import assertRaisesRegex
  
  from libcloud.compute.deployment import MultiStepDeployment, Deployment
  from libcloud.compute.deployment import SSHKeyDeployment, ScriptDeployment
  from libcloud.compute.deployment import ScriptFileDeployment, FileDeployment
  from libcloud.compute.base import Node
  from libcloud.compute.base import NodeAuthPassword
  from libcloud.compute.types import NodeState, DeploymentError, LibcloudError
  from libcloud.compute.ssh import BaseSSHClient
  from libcloud.compute.ssh import have_paramiko
  from libcloud.compute.ssh import SSHCommandTimeoutError
  from libcloud.compute.drivers.rackspace import RackspaceFirstGenNodeDriver as Rackspace
  
  from libcloud.test import MockHttp, XML_HEADERS
  from libcloud.test.file_fixtures import ComputeFileFixtures
  from mock import Mock, patch
  
  from libcloud.test.secrets import RACKSPACE_PARAMS
  
  # Keyword arguments which are specific to deploy_node() method, but not
  # create_node()
  DEPLOY_NODE_KWARGS = ['deploy', 'ssh_username', 'ssh_alternate_usernames',
                        'ssh_port', 'ssh_timeout', 'ssh_key', 'timeout',
                        'max_tries', 'ssh_interface']
  
  FILE_PATH = '{0}home{0}ubuntu{0}relative.sh'.format(os.path.sep)
  
  
  class MockDeployment(Deployment):
  
      def run(self, node, client):
          return node
  
  
  class MockClient(BaseSSHClient):
  
      def __init__(self, throw_on_timeout=False, *args, **kwargs):
          self.stdout = ''
          self.stderr = ''
          self.exit_status = 0
          self.throw_on_timeout = throw_on_timeout
  
      def put(self, path, contents, chmod=755, mode='w'):
          return contents
  
      def run(self, cmd, timeout=None):
          if self.throw_on_timeout and timeout is not None:
              raise ValueError("timeout")
  
          return self.stdout, self.stderr, self.exit_status
  
      def delete(self, name):
          return True
  
  
  class DeploymentTests(unittest.TestCase):
  
      def setUp(self):
          Rackspace.connectionCls.conn_class = RackspaceMockHttp
          RackspaceMockHttp.type = None
          self.driver = Rackspace(*RACKSPACE_PARAMS)
          # normally authentication happens lazily, but we force it here
          self.driver.connection._populate_hosts_and_request_paths()
          self.driver.features = {'create_node': ['generates_password']}
          self.node = Node(id=12345, name='test', state=NodeState.RUNNING,
                           public_ips=['1.2.3.4'], private_ips=['1.2.3.5'],
                           driver=Rackspace)
          self.node2 = Node(id=123456, name='test', state=NodeState.RUNNING,
                            public_ips=['1.2.3.4'], private_ips=['1.2.3.5'],
                            driver=Rackspace)
  
      def test_multi_step_deployment(self):
          msd = MultiStepDeployment()
          self.assertEqual(len(msd.steps), 0)
  
          msd.add(MockDeployment())
          self.assertEqual(len(msd.steps), 1)
  
          self.assertEqual(self.node, msd.run(node=self.node, client=None))
  
      def test_ssh_key_deployment(self):
          sshd = SSHKeyDeployment(key='1234')
  
          self.assertEqual(self.node, sshd.run(node=self.node,
                                               client=MockClient(hostname='localhost')))
  
      def test_file_deployment(self):
          # use this file (__file__) for obtaining permissions
          target = os.path.join('/tmp', os.path.basename(__file__))
          fd = FileDeployment(__file__, target)
          self.assertEqual(target, fd.target)
          self.assertEqual(__file__, fd.source)
          self.assertEqual(self.node, fd.run(
              node=self.node, client=MockClient(hostname='localhost')))
  
      def test_script_deployment(self):
          sd1 = ScriptDeployment(script='foobar', delete=True)
          sd2 = ScriptDeployment(script='foobar', delete=False)
          sd3 = ScriptDeployment(
              script='foobar', delete=False, name='foobarname')
          sd4 = ScriptDeployment(
              script='foobar', delete=False, name='foobarname', timeout=10)
  
          self.assertTrue(sd1.name.find('deployment') != '1')
          self.assertEqual(sd3.name, 'foobarname')
          self.assertEqual(sd3.timeout, None)
          self.assertEqual(sd4.timeout, 10)
  
          self.assertEqual(self.node, sd1.run(node=self.node,
                                              client=MockClient(hostname='localhost')))
          self.assertEqual(self.node, sd2.run(node=self.node,
                                              client=MockClient(hostname='localhost')))
          self.assertEqual(self.node, sd3.run(node=self.node,
                                              client=MockClient(hostname='localhost')))
  
          assertRaisesRegex(self, ValueError, 'timeout', sd4.run,
                            node=self.node,
                            client=MockClient(hostname='localhost',
                                              throw_on_timeout=True))
  
      def test_script_file_deployment(self):
          file_path = os.path.abspath(__file__)
          with open(file_path, 'rb') as fp:
              content = fp.read()
  
          if PY3:
              content = content.decode('utf-8')
  
          sfd1 = ScriptFileDeployment(script_file=file_path)
          self.assertEqual(sfd1.script, content)
          self.assertEqual(sfd1.timeout, None)
  
          sfd2 = ScriptFileDeployment(script_file=file_path, timeout=20)
          self.assertEqual(sfd2.timeout, 20)
  
      def test_script_deployment_relative_path(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          sd = ScriptDeployment(script='echo "foo"', name='relative.sh')
          sd.run(self.node, client)
  
          client.run.assert_called_once_with(FILE_PATH, timeout=None)
  
      def test_script_deployment_absolute_path(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          file_path = '{0}root{0}relative.sh'.format(os.path.sep)
  
          sd = ScriptDeployment(script='echo "foo"', name=file_path)
          sd.run(self.node, client)
  
          client.run.assert_called_once_with(file_path, timeout=None)
  
      def test_script_deployment_with_arguments(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          file_path = '{0}root{0}relative.sh'.format(os.path.sep)
  
          args = ['arg1', 'arg2', '--option1=test']
          sd = ScriptDeployment(script='echo "foo"', args=args,
                                name=file_path)
          sd.run(self.node, client)
  
          expected = '%s arg1 arg2 --option1=test' % (file_path)
          client.run.assert_called_once_with(expected, timeout=None)
  
          client.reset_mock()
  
          args = []
          sd = ScriptDeployment(script='echo "foo"', args=args,
                                name=file_path)
          sd.run(self.node, client)
  
          expected = file_path
          client.run.assert_called_once_with(expected, timeout=None)
  
      def test_script_file_deployment_with_arguments(self):
          file_path = os.path.abspath(__file__)
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          args = ['arg1', 'arg2', '--option1=test', 'option2']
          sfd = ScriptFileDeployment(script_file=file_path, args=args,
                                     name=file_path)
  
          sfd.run(self.node, client)
  
          expected = '%s arg1 arg2 --option1=test option2' % (file_path)
          client.run.assert_called_once_with(expected, timeout=None)
  
      def test_script_deployment_and_sshkey_deployment_argument_types(self):
          class FileObject(object):
  
              def __init__(self, name):
                  self.name = name
  
              def read(self):
                  return 'bar'
  
          ScriptDeployment(script='foobar')
          ScriptDeployment(script=u('foobar'))
          ScriptDeployment(script=FileObject('test'))
  
          SSHKeyDeployment(key='foobar')
          SSHKeyDeployment(key=u('foobar'))
          SSHKeyDeployment(key=FileObject('test'))
  
          try:
              ScriptDeployment(script=[])
          except TypeError:
              pass
          else:
              self.fail('TypeError was not thrown')
  
          try:
              SSHKeyDeployment(key={})
          except TypeError:
              pass
          else:
              self.fail('TypeError was not thrown')
  
      def test_wait_until_running_running_instantly(self):
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=0.1,
              timeout=0.5)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['67.23.21.33'], ips)
  
      def test_wait_until_running_without_ip(self):
          RackspaceMockHttp.type = 'NO_IP'
  
          try:
              node2, ips = self.driver.wait_until_running(
                  nodes=[self.node], wait_period=0.1,
                  timeout=0.2)[0]
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_with_only_ipv6(self):
          RackspaceMockHttp.type = 'IPV6'
  
          try:
              node2, ips = self.driver.wait_until_running(
                  nodes=[self.node], wait_period=0.1,
                  timeout=0.2)[0]
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_with_ipv6_ok(self):
          RackspaceMockHttp.type = 'IPV6'
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=1, force_ipv4=False,
              timeout=0.5)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['2001:DB8::1'], ips)
  
      def test_wait_until_running_running_after_0_2_second(self):
          RackspaceMockHttp.type = '05_SECOND_DELAY'
          node2, ips = self.driver.wait_until
  
  [... the rest is omitted ...]
files: libcloud/test/compute/test_deployment.py
files: # -*- coding: utf-8 -*-
  # Licensed to the Apache Software Foundation (ASF) under one or moreÂ§
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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 __future__ import with_statement
  
  import os
  import sys
  import time
  import unittest
  
  from libcloud.utils.py3 import httplib
  from libcloud.utils.py3 import u
  from libcloud.utils.py3 import PY3
  from libcloud.utils.py3 import assertRaisesRegex
  
  from libcloud.compute.deployment import MultiStepDeployment, Deployment
  from libcloud.compute.deployment import SSHKeyDeployment, ScriptDeployment
  from libcloud.compute.deployment import ScriptFileDeployment, FileDeployment
  from libcloud.compute.base import Node
  from libcloud.compute.base import NodeAuthPassword
  from libcloud.compute.types import NodeState, DeploymentError, LibcloudError
  from libcloud.compute.ssh import BaseSSHClient
  from libcloud.compute.ssh import have_paramiko
  from libcloud.compute.ssh import SSHCommandTimeoutError
  from libcloud.compute.drivers.rackspace import RackspaceFirstGenNodeDriver as Rackspace
  
  from libcloud.test import MockHttp, XML_HEADERS
  from libcloud.test.file_fixtures import ComputeFileFixtures
  from mock import Mock, patch
  
  from libcloud.test.secrets import RACKSPACE_PARAMS
  
  # Keyword arguments which are specific to deploy_node() method, but not
  # create_node()
  DEPLOY_NODE_KWARGS = ['deploy', 'ssh_username', 'ssh_alternate_usernames',
                        'ssh_port', 'ssh_timeout', 'ssh_key', 'timeout',
                        'max_tries', 'ssh_interface']
  
  FILE_PATH = '{0}home{0}ubuntu{0}relative.sh'.format(os.path.sep)
  
  
  class MockDeployment(Deployment):
  
      def run(self, node, client):
          return node
  
  
  class MockClient(BaseSSHClient):
  
      def __init__(self, *args, **kwargs):
          self.stdout = ''
          self.stderr = ''
          self.exit_status = 0
  
      def put(self, path, contents, chmod=755, mode='w'):
          return contents
  
      def run(self, name):
          return self.stdout, self.stderr, self.exit_status
  
      def delete(self, name):
          return True
  
  
  class DeploymentTests(unittest.TestCase):
  
      def setUp(self):
          Rackspace.connectionCls.conn_class = RackspaceMockHttp
          RackspaceMockHttp.type = None
          self.driver = Rackspace(*RACKSPACE_PARAMS)
          # normally authentication happens lazily, but we force it here
          self.driver.connection._populate_hosts_and_request_paths()
          self.driver.features = {'create_node': ['generates_password']}
          self.node = Node(id=12345, name='test', state=NodeState.RUNNING,
                           public_ips=['1.2.3.4'], private_ips=['1.2.3.5'],
                           driver=Rackspace)
          self.node2 = Node(id=123456, name='test', state=NodeState.RUNNING,
                            public_ips=['1.2.3.4'], private_ips=['1.2.3.5'],
                            driver=Rackspace)
  
      def test_multi_step_deployment(self):
          msd = MultiStepDeployment()
          self.assertEqual(len(msd.steps), 0)
  
          msd.add(MockDeployment())
          self.assertEqual(len(msd.steps), 1)
  
          self.assertEqual(self.node, msd.run(node=self.node, client=None))
  
      def test_ssh_key_deployment(self):
          sshd = SSHKeyDeployment(key='1234')
  
          self.assertEqual(self.node, sshd.run(node=self.node,
                                               client=MockClient(hostname='localhost')))
  
      def test_file_deployment(self):
          # use this file (__file__) for obtaining permissions
          target = os.path.join('/tmp', os.path.basename(__file__))
          fd = FileDeployment(__file__, target)
          self.assertEqual(target, fd.target)
          self.assertEqual(__file__, fd.source)
          self.assertEqual(self.node, fd.run(
              node=self.node, client=MockClient(hostname='localhost')))
  
      def test_script_deployment(self):
          sd1 = ScriptDeployment(script='foobar', delete=True)
          sd2 = ScriptDeployment(script='foobar', delete=False)
          sd3 = ScriptDeployment(
              script='foobar', delete=False, name='foobarname')
  
          self.assertTrue(sd1.name.find('deployment') != '1')
          self.assertEqual(sd3.name, 'foobarname')
  
          self.assertEqual(self.node, sd1.run(node=self.node,
                                              client=MockClient(hostname='localhost')))
          self.assertEqual(self.node, sd2.run(node=self.node,
                                              client=MockClient(hostname='localhost')))
  
      def test_script_file_deployment(self):
          file_path = os.path.abspath(__file__)
          with open(file_path, 'rb') as fp:
              content = fp.read()
  
          if PY3:
              content = content.decode('utf-8')
  
          sfd1 = ScriptFileDeployment(script_file=file_path)
          self.assertEqual(sfd1.script, content)
  
      def test_script_deployment_relative_path(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          sd = ScriptDeployment(script='echo "foo"', name='relative.sh')
          sd.run(self.node, client)
  
          client.run.assert_called_once_with(FILE_PATH)
  
      def test_script_deployment_absolute_path(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          file_path = '{0}root{0}relative.sh'.format(os.path.sep)
  
          sd = ScriptDeployment(script='echo "foo"', name=file_path)
          sd.run(self.node, client)
  
          client.run.assert_called_once_with(file_path)
  
      def test_script_deployment_with_arguments(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          file_path = '{0}root{0}relative.sh'.format(os.path.sep)
  
          args = ['arg1', 'arg2', '--option1=test']
          sd = ScriptDeployment(script='echo "foo"', args=args,
                                name=file_path)
          sd.run(self.node, client)
  
          expected = '%s arg1 arg2 --option1=test' % (file_path)
          client.run.assert_called_once_with(expected)
  
          client.reset_mock()
  
          args = []
          sd = ScriptDeployment(script='echo "foo"', args=args,
                                name=file_path)
          sd.run(self.node, client)
  
          expected = file_path
          client.run.assert_called_once_with(expected)
  
      def test_script_file_deployment_with_arguments(self):
          file_path = os.path.abspath(__file__)
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          args = ['arg1', 'arg2', '--option1=test', 'option2']
          sfd = ScriptFileDeployment(script_file=file_path, args=args,
                                     name=file_path)
  
          sfd.run(self.node, client)
  
          expected = '%s arg1 arg2 --option1=test option2' % (file_path)
          client.run.assert_called_once_with(expected)
  
      def test_script_deployment_and_sshkey_deployment_argument_types(self):
          class FileObject(object):
  
              def __init__(self, name):
                  self.name = name
  
              def read(self):
                  return 'bar'
  
          ScriptDeployment(script='foobar')
          ScriptDeployment(script=u('foobar'))
          ScriptDeployment(script=FileObject('test'))
  
          SSHKeyDeployment(key='foobar')
          SSHKeyDeployment(key=u('foobar'))
          SSHKeyDeployment(key=FileObject('test'))
  
          try:
              ScriptDeployment(script=[])
          except TypeError:
              pass
          else:
              self.fail('TypeError was not thrown')
  
          try:
              SSHKeyDeployment(key={})
          except TypeError:
              pass
          else:
              self.fail('TypeError was not thrown')
  
      def test_wait_until_running_running_instantly(self):
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=0.1,
              timeout=0.5)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['67.23.21.33'], ips)
  
      def test_wait_until_running_without_ip(self):
          RackspaceMockHttp.type = 'NO_IP'
  
          try:
              node2, ips = self.driver.wait_until_running(
                  nodes=[self.node], wait_period=0.1,
                  timeout=0.2)[0]
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_with_only_ipv6(self):
          RackspaceMockHttp.type = 'IPV6'
  
          try:
              node2, ips = self.driver.wait_until_running(
                  nodes=[self.node], wait_period=0.1,
                  timeout=0.2)[0]
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_with_ipv6_ok(self):
          RackspaceMockHttp.type = 'IPV6'
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=1, force_ipv4=False,
              timeout=0.5)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['2001:DB8::1'], ips)
  
      def test_wait_until_running_running_after_0_2_second(self):
          RackspaceMockHttp.type = '05_SECOND_DELAY'
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=0.2,
              timeout=0.1)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['67.23.21.33'], ips)
  
      def test_wait_until_running_running_after_0_2_second_private_ips(self):
          RackspaceMockHttp.type = '05_SECOND_DELAY'
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=0.2,
              timeout=0.1, ssh_interface='private_ips')[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['10.176.168.218'], ips)
  
      def test_wait_until_running_invalid_ssh_interface_argument(self):
          try:
              self.driver.wait_until_running(nodes=[self.node], wait_period=1,
                                             ssh_interface='invalid')
         
  
  [... the rest is omitted ...]
files: libcloud/test/compute/test_deployment.py