Hello community,

here is the log from the commit of package python3-ipa for openSUSE:Factory 
checked in at 2018-06-02 12:14:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python3-ipa (Old)
 and      /work/SRC/openSUSE:Factory/.python3-ipa.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python3-ipa"

Sat Jun  2 12:14:36 2018 rev:4 rq:613452 version:1.1.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python3-ipa/python3-ipa.changes  2018-04-04 
11:04:14.494599198 +0200
+++ /work/SRC/openSUSE:Factory/.python3-ipa.new/python3-ipa.changes     
2018-06-02 12:14:52.957972322 +0200
@@ -1,0 +2,12 @@
+Wed May 16 21:03:53 UTC 2018 - [email protected]
+
+- Update to v1.1.1 (2018-05-16)
+  + Cleanup typo in docs.
+  + Explicitly close SSH connections.
+- Update to v1.1.0 (2018-05-15)
+  + Added the --inject option.
+  + Adds the option to inject packages, archives and files.
+  + Also provides the ability to execute commands and install
+    packages from an existing repository. 
+
+-------------------------------------------------------------------

Old:
----
  python3-ipa-1.0.0.tar.gz

New:
----
  python3-ipa-1.1.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python3-ipa.spec ++++++
--- /var/tmp/diff_new_pack.ROOzkw/_old  2018-06-02 12:14:53.833940192 +0200
+++ /var/tmp/diff_new_pack.ROOzkw/_new  2018-06-02 12:14:53.837940046 +0200
@@ -18,7 +18,7 @@
 
 %bcond_without test
 Name:           python3-ipa
-Version:        1.0.0
+Version:        1.1.1
 Release:        0
 Summary:        Command line and API for testing custom images
 License:        GPL-3.0-or-later

++++++ python3-ipa-1.0.0.tar.gz -> python3-ipa-1.1.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/CHANGES.asciidoc 
new/python3-ipa-1.1.1/CHANGES.asciidoc
--- old/python3-ipa-1.0.0/CHANGES.asciidoc      2018-03-30 17:48:40.000000000 
+0200
+++ new/python3-ipa-1.1.1/CHANGES.asciidoc      2018-05-16 22:43:59.000000000 
+0200
@@ -1,5 +1,19 @@
 = Changelog
 
+== v1.1.1 (2018-05-16)
+
+* Cleanup typo in docs.
+* Explicitly close SSH connections.
+  link:https://github.com/SUSE/ipa/pull/79[#79]
+
+== v1.1.0 (2018-05-15)
+
+* Add requirements and external test injection with the `--inject` option.
+  link:https://github.com/SUSE/ipa/pull/78[#78]
+** Adds the option to inject packages, archives and
+   files. Also provides the ability to execute commands
+   and install packages from an existing repository.
+
 == v1.0.0 (2018-03-30)
 
 * Tests argument is now optional.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/PKG-INFO 
new/python3-ipa-1.1.1/PKG-INFO
--- old/python3-ipa-1.0.0/PKG-INFO      2018-03-30 18:23:36.000000000 +0200
+++ new/python3-ipa-1.1.1/PKG-INFO      2018-05-16 22:55:56.000000000 +0200
@@ -1,12 +1,11 @@
-Metadata-Version: 1.2
+Metadata-Version: 2.1
 Name: python3-ipa
-Version: 1.0.0
+Version: 1.1.1
 Summary: Package for automated testing of cloud images.
 Home-page: https://github.com/SUSE/pubcloud/ipa
 Author: SUSE
 Author-email: [email protected]
 License: GPLv3+
-Description-Content-Type: UNKNOWN
 Description: = ipa
         
         image:https://travis-ci.org/SUSE/ipa.svg?branch=master["Build Status", 
link="https://travis-ci.org/SUSE/ipa";]
@@ -78,8 +77,11 @@
         
         === Azure Configuration
         
-        There is no additional configuration file used for Azure. All options
-        should be placed in the *ipa* config file.
+        Azure uses service principals for authentication. See
+        
link:https://docs.microsoft.com/en-us/python/azure/python-sdk-azure-authenticate?view=azure-python#mgmt-auth-file[Azure
 docs]
+        for more info on creating a service principal json file. Additional
+        configuration options can be placed in the `azure` section of the
+        `ipa` configuration file.
         
         === EC2 Configuration
         
@@ -166,3 +168,6 @@
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Requires-Python: >=3.4
+Provides-Extra: tox
+Provides-Extra: test
+Provides-Extra: dev
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/README.asciidoc 
new/python3-ipa-1.1.1/README.asciidoc
--- old/python3-ipa-1.0.0/README.asciidoc       2018-03-30 18:05:40.000000000 
+0200
+++ new/python3-ipa-1.1.1/README.asciidoc       2018-04-25 23:05:09.000000000 
+0200
@@ -69,8 +69,11 @@
 
 === Azure Configuration
 
-There is no additional configuration file used for Azure. All options
-should be placed in the *ipa* config file.
+Azure uses service principals for authentication. See
+link:https://docs.microsoft.com/en-us/python/azure/python-sdk-azure-authenticate?view=azure-python#mgmt-auth-file[Azure
 docs]
+for more info on creating a service principal json file. Additional
+configuration options can be placed in the `azure` section of the
+`ipa` configuration file.
 
 === EC2 Configuration
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/docs/start.asciidoc 
new/python3-ipa-1.1.1/docs/start.asciidoc
--- old/python3-ipa-1.0.0/docs/start.asciidoc   2018-03-30 18:02:18.000000000 
+0200
+++ new/python3-ipa-1.1.1/docs/start.asciidoc   2018-05-16 17:54:33.000000000 
+0200
@@ -63,8 +63,12 @@
 
 === Azure
 
-There is no additional configuration file used for Azure. All options
-should be placed in the *ipa* config file.
+Azure uses service principals for authentication. See
+link:https://docs.microsoft.com/en-us/python/azure/python-sdk-azure-authenticate?view=azure-python#mgmt-auth-file[Azure
 docs]
+for more info on creating a service principal json file. Additional
+configuration options can be placed in the `azure` section of the
+`ipa` configuration file.
+
 
 === EC2
 
@@ -181,6 +185,61 @@
 
link:https://docs.pytest.org/en/latest/usage.html#stopping-after-the-first-or-n-failures[Pytest
 docs]
 for more info.
 
+==== Requirements and external test injection
+
+Using the `--inject` option; packages, archives and files can be injected
+on the test instance. This also provides the ability to install packages
+in an existing repository and run commands on the test instance. The
+following sections may be provided in a YAML style config file. Each
+section can be a single item or a list of items. All files are copied
+and extracted to the default SSH location for the test instance. This
+is generally the user's home directory.
+
+*_inject_packages_*::
+an rpm path or list of rpm paths which will be copied and installed on
+the test instance.
+
+*_inject_archives_*::
+an archive or list of archives which will be copied and extracted on the
+test instance.
+
+*_inject_files_*::
+a file path or list of file paths which will be copied to the test instance.
+
+*_execute_*::
+a command or list of commands to run on the test instance.
+
+*_install_*::
+a package name or list of package names to install from an existing repo on
+the test instance.
+
+The order of processing for the sections is as follows:
+
+1. inject_packages
+1. inject_archives
+1. inject_files
+1. execute
+1. install
+
+===== Example
+
+[source]
+.testing_injection.yaml
+----
+inject_packages: /home/user/test.noarch.rpm
+inject_archives: /home/user/test.tar.xz
+inject_files: /home/user/test.py
+install:
+  - python3
+  - python3-Django
+execute: python test.py
+----
+
+[source]
+----
+> ipa test ... --inject testing_injection.yaml
+----
+
 === Code
 
 *ipa* primarily provides a CLI tool for testing images. However, the endpoints
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/ipa/__init__.py 
new/python3-ipa-1.1.1/ipa/__init__.py
--- old/python3-ipa-1.0.0/ipa/__init__.py       2018-03-30 18:11:43.000000000 
+0200
+++ new/python3-ipa-1.1.1/ipa/__init__.py       2018-05-16 22:44:47.000000000 
+0200
@@ -22,4 +22,4 @@
 
 __author__ = """SUSE"""
 __email__ = '[email protected]'
-__version__ = '1.0.0'
+__version__ = '1.1.1'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/ipa/ipa_azure.py 
new/python3-ipa-1.1.1/ipa/ipa_azure.py
--- old/python3-ipa-1.0.0/ipa/ipa_azure.py      2018-03-28 15:36:07.000000000 
+0200
+++ new/python3-ipa-1.1.1/ipa/ipa_azure.py      2018-05-15 21:01:19.000000000 
+0200
@@ -46,6 +46,7 @@
                  early_exit=None,
                  history_log=None,
                  image_id=None,
+                 inject=None,
                  instance_type=None,
                  log_level=30,
                  no_default_test_dirs=False,
@@ -54,7 +55,7 @@
                  results_dir=None,
                  running_instance_id=None,
                  secret_access_key=None,  # Not used in Azure
-                 service_account_file=None,  # Not used in Azure
+                 service_account_file=None,
                  ssh_key_name=None,  # Not used in Azure
                  ssh_private_key=None,
                  ssh_user=None,
@@ -72,6 +73,7 @@
                                             early_exit,
                                             history_log,
                                             image_id,
+                                            inject,
                                             instance_type,
                                             log_level,
                                             no_default_test_dirs,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/ipa/ipa_controller.py 
new/python3-ipa-1.1.1/ipa/ipa_controller.py
--- old/python3-ipa-1.0.0/ipa/ipa_controller.py 2018-03-28 15:36:07.000000000 
+0200
+++ new/python3-ipa-1.1.1/ipa/ipa_controller.py 2018-05-15 21:01:19.000000000 
+0200
@@ -43,6 +43,7 @@
                early_exit=None,
                history_log=None,
                image_id=None,
+               inject=None,
                instance_type=None,
                log_level=None,
                no_default_test_dirs=None,
@@ -83,6 +84,7 @@
         early_exit=early_exit,
         history_log=history_log,
         image_id=image_id,
+        inject=inject,
         instance_type=instance_type,
         log_level=log_level,
         no_default_test_dirs=no_default_test_dirs,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/ipa/ipa_distro.py 
new/python3-ipa-1.1.1/ipa/ipa_distro.py
--- old/python3-ipa-1.0.0/ipa/ipa_distro.py     2017-08-15 16:50:28.000000000 
+0200
+++ new/python3-ipa-1.1.1/ipa/ipa_distro.py     2018-05-10 18:21:09.000000000 
+0200
@@ -37,6 +37,10 @@
         """Determine the init system of distribution."""
         raise NotImplementedError(NOT_IMPLEMENTED)
 
+    def get_install_cmd(self):
+        """Return install package command for distribution."""
+        raise NotImplementedError(NOT_IMPLEMENTED)
+
     def get_reboot_cmd(self):
         """Return reboot command for given distribution."""
         return 'shutdown -r now'
@@ -57,6 +61,30 @@
         """Return command to update instance."""
         raise NotImplementedError(NOT_IMPLEMENTED)
 
+    def install_package(self, client, package):
+        """Install package on instance."""
+        install_cmd = "{sudo} '{install} {package}'".format(
+            sudo=self.get_sudo_exec_wrapper(),
+            install=self.get_install_cmd(),
+            package=package
+        )
+
+        try:
+            out = ipa_utils.execute_ssh_command(
+                client,
+                install_cmd
+            )
+        except Exception as error:
+            raise IpaDistroException(
+                'An error occurred installing package {package} '
+                'on instance: {error}'.format(
+                    package=package,
+                    error=error
+                )
+            )
+        else:
+            return out
+
     def reboot(self, client):
         """Execute reboot command on instance."""
         if not self.init_system:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/ipa/ipa_ec2.py 
new/python3-ipa-1.1.1/ipa/ipa_ec2.py
--- old/python3-ipa-1.0.0/ipa/ipa_ec2.py        2018-03-28 15:42:08.000000000 
+0200
+++ new/python3-ipa-1.1.1/ipa/ipa_ec2.py        2018-05-15 21:01:19.000000000 
+0200
@@ -48,6 +48,7 @@
                  early_exit=None,
                  history_log=None,
                  image_id=None,
+                 inject=None,
                  instance_type=None,
                  log_level=30,
                  no_default_test_dirs=False,
@@ -75,6 +76,7 @@
                                           early_exit,
                                           history_log,
                                           image_id,
+                                          inject,
                                           instance_type,
                                           log_level,
                                           no_default_test_dirs,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/ipa/ipa_gce.py 
new/python3-ipa-1.1.1/ipa/ipa_gce.py
--- old/python3-ipa-1.0.0/ipa/ipa_gce.py        2018-03-28 15:36:07.000000000 
+0200
+++ new/python3-ipa-1.1.1/ipa/ipa_gce.py        2018-05-15 21:01:19.000000000 
+0200
@@ -49,6 +49,7 @@
                  early_exit=None,
                  history_log=None,
                  image_id=None,
+                 inject=None,
                  instance_type=None,
                  log_level=30,
                  no_default_test_dirs=False,
@@ -75,6 +76,7 @@
                                           early_exit,
                                           history_log,
                                           image_id,
+                                          inject,
                                           instance_type,
                                           log_level,
                                           no_default_test_dirs,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/ipa/ipa_provider.py 
new/python3-ipa-1.1.1/ipa/ipa_provider.py
--- old/python3-ipa-1.0.0/ipa/ipa_provider.py   2018-03-30 16:39:26.000000000 
+0200
+++ new/python3-ipa-1.1.1/ipa/ipa_provider.py   2018-05-15 21:01:19.000000000 
+0200
@@ -67,6 +67,7 @@
                  early_exit=None,
                  history_log=None,
                  image_id=None,
+                 inject=None,
                  instance_type=None,
                  log_level=None,
                  no_default_test_dirs=False,
@@ -102,6 +103,7 @@
         self.distro_name = self._get_value(distro_name)
         self.early_exit = self._get_value(early_exit)
         self.image_id = self._get_value(image_id)
+        self.inject = self._get_value(inject)
         self.instance_type = self._get_value(instance_type)
         self.running_instance_id = self._get_value(running_instance_id)
         self.test_files = list(self._get_value(test_files, default=[]))
@@ -391,6 +393,37 @@
             )
         )
 
+    def execute_ssh_command(self, client, command):
+        """Execute the provided command and log output."""
+        try:
+            out = ipa_utils.execute_ssh_command(client, command)
+        except Exception as error:
+            raise IpaProviderException(
+                'Command: "{0}", failed execution: {1}.'.format(
+                    command, error
+                )
+            )
+        else:
+            with open(self.log_file, 'a') as log_file:
+                log_file.write('\n')
+                log_file.write(out)
+
+    def extract_archive(self, client, archive_path, extract_path=None):
+        """Extract the archive files using the client in the current path."""
+        try:
+            out = ipa_utils.extract_archive(client, archive_path, extract_path)
+        except Exception as error:
+            raise IpaProviderException(
+                'Failed to extract archive, "{0}": {1}.'.format(
+                    archive_path, error
+                )
+            )
+
+        else:
+            with open(self.log_file, 'a') as log_file:
+                log_file.write('\n')
+                log_file.write(out)
+
     def hard_reboot_instance(self):
         """Stop then start the instance."""
         self._stop_instance()
@@ -399,6 +432,107 @@
         self.logger.debug('IP of instance: %s' % self.instance_ip)
         ipa_utils.clear_cache()
 
+    def install_package(self, client, package):
+        """
+        Install package using distro specific install method.
+        """
+        try:
+            out = self.distro.install_package(client, package)
+        except Exception as error:
+            raise IpaProviderException(
+                'Failed installing package, "{0}"; {1}.'.format(
+                    package, error
+                )
+            )
+        else:
+            with open(self.log_file, 'a') as log_file:
+                log_file.write('\n')
+                log_file.write(out)
+
+    def process_injection_file(self, client):
+        """
+        Load yaml file and process injection configuration.
+
+        There are 5 injection options:
+
+        :inject_packages: an rpm path or list of rpm paths which will be
+                          copied and installed on the test instance.
+        :inject_archives: an archive or list of archives which will
+                          be copied and extracted on the test instance.
+        :inject_files: a file path or list of file paths which
+                       will be copied to the test instance.
+        :execute: a command or list of commands to run on the test instance.
+        :install: a package name or list of package names to
+                 install from an existing repo on the test instance.
+
+        The order of processing is as follows: inject_packages,
+        inject_archives, inject_files, execute, install.
+        """
+        configuration = ipa_utils.get_yaml_config(self.inject)
+
+        if configuration.get('inject_packages'):
+            inject_packages = configuration['inject_packages']
+
+            if not isinstance(inject_packages, list):
+                inject_packages = [inject_packages]
+
+            for package in inject_packages:
+                package_path = self.put_file(client, package)
+                self.install_package(client, package_path)
+
+        if configuration.get('inject_archives'):
+            inject_archives = configuration['inject_archives']
+
+            if not isinstance(inject_archives, list):
+                inject_archives = [inject_archives]
+
+            for archive in inject_archives:
+                archive_path = self.put_file(client, archive)
+                self.extract_archive(client, archive_path)
+
+        if configuration.get('inject_files'):
+            inject_files = configuration['inject_files']
+
+            if not isinstance(inject_files, list):
+                inject_files = [inject_files]
+
+            for file_path in inject_files:
+                self.put_file(client, file_path)
+
+        if configuration.get('execute'):
+            execute = configuration['execute']
+
+            if not isinstance(execute, list):
+                execute = [execute]
+
+            for command in execute:
+                self.execute_ssh_command(client, command)
+
+        if configuration.get('install'):
+            install = configuration['install']
+
+            if not isinstance(install, list):
+                install = [install]
+
+            for package in install:
+                self.install_package(client, package)
+
+    def put_file(self, client, source_file):
+        """
+        Put file on instance in default SSH directory.
+        """
+        try:
+            file_name = os.path.basename(source_file)
+            ipa_utils.put_file(client, source_file, file_name)
+        except Exception as error:
+            raise IpaProviderException(
+                'Failed copying file, "{0}"; {1}.'.format(
+                    source_file, error
+                )
+            )
+        else:
+            return file_name
+
     def test_image(self):
         """
         The entry point for testing an image.
@@ -443,6 +577,9 @@
         self._set_distro()
         self._log_info()
 
+        if self.inject:
+            self.process_injection_file(self._get_ssh_client())
+
         status = 0
         with ipa_utils.ssh_config(self.ssh_user, self.ssh_private_key)\
                 as ssh_config:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/ipa/ipa_sles.py 
new/python3-ipa-1.1.1/ipa/ipa_sles.py
--- old/python3-ipa-1.0.0/ipa/ipa_sles.py       2017-08-15 16:50:28.000000000 
+0200
+++ new/python3-ipa-1.1.1/ipa/ipa_sles.py       2018-05-10 16:28:27.000000000 
+0200
@@ -43,6 +43,10 @@
         if out:
             self.init_system = out.strip()
 
+    def get_install_cmd(self):
+        """Return install package command for SLES."""
+        return 'zypper -n --no-gpg-checks in -y'
+
     def get_refresh_repo_cmd(self):
         """Return refresh repo command for SLES."""
         return 'zypper -n refresh'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/ipa/ipa_utils.py 
new/python3-ipa-1.1.1/ipa/ipa_utils.py
--- old/python3-ipa-1.0.0/ipa/ipa_utils.py      2018-03-30 16:39:26.000000000 
+0200
+++ new/python3-ipa-1.1.1/ipa/ipa_utils.py      2018-05-16 22:36:17.000000000 
+0200
@@ -47,9 +47,14 @@
 def clear_cache(ip=None):
     """Clear the client cache or remove key matching the given ip."""
     if ip:
-        with ignored(KeyError):
+        with ignored(Exception):
+            client = CLIENT_CACHE[ip]
             del CLIENT_CACHE[ip]
+            client.close()
     else:
+        for client in CLIENT_CACHE.values():
+            with ignored(Exception):
+                client.close()
         CLIENT_CACHE.clear()
 
 
@@ -142,6 +147,21 @@
     return parse_sync_points(expanded_names, tests)
 
 
+def extract_archive(client, archive_path, extract_path=None):
+    """
+    Extract the archive in current path using the provided client.
+
+    If extract_path is provided extract the archive there.
+    """
+    command = 'tar -xf {path}'.format(path=archive_path)
+
+    if extract_path:
+        command += ' -C {extract_path}'.format(extract_path=extract_path)
+
+    out = execute_ssh_command(client, command)
+    return out
+
+
 def find_test_file(name, tests):
     """
     Find test file by name, given a list of tests.
@@ -457,6 +477,22 @@
     return '::'.join(filter(None, [test_file, test_class, test_case]))
 
 
+def put_file(client, source_file, destination_file):
+    """
+    Copy file to instance using Paramiko client connection.
+    """
+    try:
+        sftp_client = client.open_sftp()
+        sftp_client.put(source_file, destination_file)
+    except Exception as error:
+        raise IpaUtilsException(
+            'Error copying file to instance: {0}.'.format(error)
+        )
+    finally:
+        with ignored(Exception):
+            sftp_client.close()
+
+
 @contextmanager
 def redirect_output(fileobj):
     """Redirect standard out to file."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/ipa/scripts/cli.py 
new/python3-ipa-1.1.1/ipa/scripts/cli.py
--- old/python3-ipa-1.0.0/ipa/scripts/cli.py    2018-03-30 16:39:26.000000000 
+0200
+++ new/python3-ipa-1.1.1/ipa/scripts/cli.py    2018-05-15 21:01:19.000000000 
+0200
@@ -127,6 +127,11 @@
     help='The ID of the image used for instance.'
 )
 @click.option(
+    '--inject',
+    help='Path to an injection yaml config file.',
+    type=click.Path(exists=True)
+)
[email protected](
     '-t',
     '--instance-type',
     help='Instance type to use for launching machine.'
@@ -228,6 +233,7 @@
          early_exit,
          history_log,
          image_id,
+         inject,
          instance_type,
          log_level,
          no_default_test_dirs,
@@ -260,6 +266,7 @@
             early_exit,
             history_log,
             image_id,
+            inject,
             instance_type,
             log_level,
             no_default_test_dirs,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/package/python3-ipa.spec 
new/python3-ipa-1.1.1/package/python3-ipa.spec
--- old/python3-ipa-1.0.0/package/python3-ipa.spec      2018-03-30 
18:11:43.000000000 +0200
+++ new/python3-ipa-1.1.1/package/python3-ipa.spec      2018-05-16 
22:44:47.000000000 +0200
@@ -17,7 +17,7 @@
 
 %bcond_without test
 Name:           python3-ipa
-Version:        1.0.0
+Version:        1.1.1
 Release:        0
 Summary:        Command line and API for testing custom images
 License:        GPL-3.0+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/python3_ipa.egg-info/PKG-INFO 
new/python3-ipa-1.1.1/python3_ipa.egg-info/PKG-INFO
--- old/python3-ipa-1.0.0/python3_ipa.egg-info/PKG-INFO 2018-03-30 
18:23:36.000000000 +0200
+++ new/python3-ipa-1.1.1/python3_ipa.egg-info/PKG-INFO 2018-05-16 
22:55:56.000000000 +0200
@@ -1,12 +1,11 @@
-Metadata-Version: 1.2
+Metadata-Version: 2.1
 Name: python3-ipa
-Version: 1.0.0
+Version: 1.1.1
 Summary: Package for automated testing of cloud images.
 Home-page: https://github.com/SUSE/pubcloud/ipa
 Author: SUSE
 Author-email: [email protected]
 License: GPLv3+
-Description-Content-Type: UNKNOWN
 Description: = ipa
         
         image:https://travis-ci.org/SUSE/ipa.svg?branch=master["Build Status", 
link="https://travis-ci.org/SUSE/ipa";]
@@ -78,8 +77,11 @@
         
         === Azure Configuration
         
-        There is no additional configuration file used for Azure. All options
-        should be placed in the *ipa* config file.
+        Azure uses service principals for authentication. See
+        
link:https://docs.microsoft.com/en-us/python/azure/python-sdk-azure-authenticate?view=azure-python#mgmt-auth-file[Azure
 docs]
+        for more info on creating a service principal json file. Additional
+        configuration options can be placed in the `azure` section of the
+        `ipa` configuration file.
         
         === EC2 Configuration
         
@@ -166,3 +168,6 @@
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Requires-Python: >=3.4
+Provides-Extra: tox
+Provides-Extra: test
+Provides-Extra: dev
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/python3_ipa.egg-info/SOURCES.txt 
new/python3-ipa-1.1.1/python3_ipa.egg-info/SOURCES.txt
--- old/python3-ipa-1.0.0/python3_ipa.egg-info/SOURCES.txt      2018-03-30 
18:23:36.000000000 +0200
+++ new/python3-ipa-1.1.1/python3_ipa.egg-info/SOURCES.txt      2018-05-16 
22:55:56.000000000 +0200
@@ -70,6 +70,7 @@
 tests/data/ida_test
 tests/data/invalid.history
 tests/data/test.results
+tests/data/injection/test_injection.yaml
 tests/data/tests/test_broken.py
 tests/data/tests/test_image.py
 tests/data/tests/test_image_desc.yaml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/setup.cfg 
new/python3-ipa-1.1.1/setup.cfg
--- old/python3-ipa-1.0.0/setup.cfg     2018-03-30 18:23:36.000000000 +0200
+++ new/python3-ipa-1.1.1/setup.cfg     2018-05-16 22:55:56.000000000 +0200
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 1.0.0
+current_version = 1.1.1
 commit = True
 tag = False
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/setup.py 
new/python3-ipa-1.1.1/setup.py
--- old/python3-ipa-1.0.0/setup.py      2018-03-30 18:11:43.000000000 +0200
+++ new/python3-ipa-1.1.1/setup.py      2018-05-16 22:44:47.000000000 +0200
@@ -60,7 +60,7 @@
 
 setup(
     name='python3-ipa',
-    version='1.0.0',
+    version='1.1.1',
     description="Package for automated testing of cloud images.",
     long_description=readme,
     author="SUSE",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python3-ipa-1.0.0/tests/data/injection/test_injection.yaml 
new/python3-ipa-1.1.1/tests/data/injection/test_injection.yaml
--- old/python3-ipa-1.0.0/tests/data/injection/test_injection.yaml      
1970-01-01 01:00:00.000000000 +0100
+++ new/python3-ipa-1.1.1/tests/data/injection/test_injection.yaml      
2018-05-15 21:01:19.000000000 +0200
@@ -0,0 +1,5 @@
+inject_packages: /home/user/test.noarch.rpm
+inject_archives: /home/user/test.tar.xz
+install: python3
+inject_files: /home/user/test.py
+execute: python test.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/tests/test_ipa_distro.py 
new/python3-ipa-1.1.1/tests/test_ipa_distro.py
--- old/python3-ipa-1.0.0/tests/test_ipa_distro.py      2017-08-23 
00:00:09.000000000 +0200
+++ new/python3-ipa-1.1.1/tests/test_ipa_distro.py      2018-05-10 
18:17:37.000000000 +0200
@@ -27,22 +27,33 @@
 
 import pytest
 
+methods = [
+    'get_install_cmd',
+    'get_refresh_repo_cmd',
+    'get_stop_ssh_service_cmd',
+    'get_update_cmd'
+]
 
-def test_distro_not_implemented_methods():
+
[email protected](
+    "method",
+    methods,
+    ids=methods
+)
+def test_distro_not_implemented_methods(method):
     """Confirm methods raise not implemented exception."""
     distro = Distro()
-    methods = [
-        'get_refresh_repo_cmd',
-        'get_stop_ssh_service_cmd',
-        'get_update_cmd'
-    ]
-    for method in methods:
-        pytest.raises(
-            NotImplementedError,
-            getattr(distro, method)
-        )
+    pytest.raises(
+        NotImplementedError,
+        getattr(distro, method)
+    )
 
+
+def test_distro_set_init_system():
+    """Test set init system raises not implemented exception."""
+    distro = Distro()
     client = MagicMock()
+
     pytest.raises(
         NotImplementedError,
         getattr(distro, '_set_init_system'),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/tests/test_ipa_provider.py 
new/python3-ipa-1.1.1/tests/test_ipa_provider.py
--- old/python3-ipa-1.0.0/tests/test_ipa_provider.py    2018-01-31 
20:21:42.000000000 +0100
+++ new/python3-ipa-1.1.1/tests/test_ipa_provider.py    2018-05-15 
21:01:19.000000000 +0200
@@ -21,6 +21,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import io
 import pytest
 
 from ipa import ipa_utils
@@ -28,7 +29,7 @@
 from ipa.ipa_exceptions import IpaProviderException, IpaSSHException
 from ipa.ipa_provider import IpaProvider
 
-from unittest.mock import MagicMock, patch
+from unittest.mock import call, MagicMock, patch
 
 args = ['ec2']
 
@@ -222,6 +223,49 @@
         assert mock_instance_running.call_count == 1
         assert mock_start_instance.call_count == 1
 
+    @patch('ipa.ipa_utils.execute_ssh_command')
+    def test_provider_execute_ssh_command(self, mock_exec_cmd):
+        client = MagicMock()
+        mock_exec_cmd.return_value = 'command executed successfully!'
+
+        provider = IpaProvider(*args, **self.kwargs)
+        provider.log_file = 'fake_file.name'
+
+        with patch('builtins.open', create=True) as mock_open:
+            mock_open.return_value = MagicMock(spec=io.IOBase)
+            file_handle = mock_open.return_value.__enter__.return_value
+
+            provider.execute_ssh_command(client, 'python test.py')
+
+            file_handle.write.assert_has_calls([
+                call('\n'),
+                call('command executed successfully!')
+            ])
+
+    @patch('ipa.ipa_utils.extract_archive')
+    def test_provider_extract_archive(self, mock_extract_archive):
+        client = MagicMock()
+
+        mock_extract_archive.return_value = 'archive extracted successfully!'
+
+        provider = IpaProvider(*args, **self.kwargs)
+        provider.log_file = 'fake_file.name'
+
+        with patch('builtins.open', create=True) as mock_open:
+            mock_open.return_value = MagicMock(spec=io.IOBase)
+            file_handle = mock_open.return_value.__enter__.return_value
+
+            provider.extract_archive(client, 'archive.tar.xz')
+
+            file_handle.write.assert_has_calls([
+                call('\n'),
+                call('archive extracted successfully!')
+            ])
+
+        mock_extract_archive.assert_called_once_with(
+            client, 'archive.tar.xz', None
+        )
+
     @patch.object(IpaProvider, '_set_instance_ip')
     @patch.object(IpaProvider, '_stop_instance')
     @patch.object(IpaProvider, '_start_instance')
@@ -242,6 +286,80 @@
         assert mock_start_instance.call_count == 1
         assert mock_set_instance_ip.call_count == 1
 
+    @patch('ipa.ipa_utils.put_file')
+    def test_provider_put_file(self, mock_put_file):
+        client = MagicMock()
+
+        file_path = '/home/user/test.file'
+        basename = 'test.file'
+
+        provider = IpaProvider(*args, **self.kwargs)
+        out = provider.put_file(client, file_path)
+
+        assert out == basename
+
+        mock_put_file.assert_called_once_with(
+            client, file_path, basename
+        )
+
+    def test_provider_install_package(self):
+        client = MagicMock()
+        distro = MagicMock()
+        distro.install_package.return_value = 'package install successful!'
+
+        provider = IpaProvider(*args, **self.kwargs)
+        provider.log_file = 'fake_file.name'
+        provider.distro = distro
+
+        with patch('builtins.open', create=True) as mock_open:
+            mock_open.return_value = MagicMock(spec=io.IOBase)
+            file_handle = mock_open.return_value.__enter__.return_value
+
+            provider.install_package(client, 'python')
+
+            file_handle.write.assert_has_calls([
+                call('\n'),
+                call('package install successful!')
+            ])
+
+    @patch.object(IpaProvider, 'execute_ssh_command')
+    @patch.object(IpaProvider, 'extract_archive')
+    @patch.object(IpaProvider, 'install_package')
+    @patch.object(IpaProvider, 'put_file')
+    def test_process_injection_file(self,
+                                    mock_put_file,
+                                    mock_install_package,
+                                    mock_extract_archive,
+                                    mock_execute_command):
+        client = MagicMock()
+        mock_put_file.side_effect = [
+            'test.noarch.rpm', 'test.tar.xz', 'test.py'
+        ]
+
+        provider = IpaProvider(*args, **self.kwargs)
+        provider.inject = 'tests/data/injection/test_injection.yaml'
+
+        provider.process_injection_file(client)
+
+        mock_put_file.assert_has_calls([
+            call(client, '/home/user/test.noarch.rpm'),
+            call(client, '/home/user/test.tar.xz'),
+            call(client, '/home/user/test.py')
+        ])
+
+        mock_install_package.assert_has_calls([
+            call(client, 'test.noarch.rpm'),
+            call(client, 'python3')
+        ])
+
+        mock_extract_archive.assert_called_once_with(
+            client, 'test.tar.xz'
+        )
+
+        mock_execute_command.assert_called_once_with(
+            client, 'python test.py'
+        )
+
     @patch.object(IpaProvider, '_set_instance_ip')
     @patch.object(IpaProvider, '_set_image_id')
     @patch.object(IpaProvider, '_start_instance_if_stopped')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/tests/test_ipa_sles_distro.py 
new/python3-ipa-1.1.1/tests/test_ipa_sles_distro.py
--- old/python3-ipa-1.0.0/tests/test_ipa_sles_distro.py 2017-08-23 
00:00:09.000000000 +0200
+++ new/python3-ipa-1.1.1/tests/test_ipa_sles_distro.py 2018-05-10 
18:17:37.000000000 +0200
@@ -62,6 +62,21 @@
         'The init system for SUSE distribution cannot be determined.'
 
 
+def test_sles_install_package():
+    """Test install package method for SLES distro."""
+    client = MagicMock()
+    sles = SLES()
+
+    with patch('ipa.ipa_utils.execute_ssh_command',
+               MagicMock(return_value='')) as mocked:
+        sles.install_package(client, 'python')
+
+    mocked.assert_called_once_with(
+        client,
+        "sudo sh -c 'zypper -n --no-gpg-checks in -y python'"
+    )
+
+
 def test_sles_reboot():
     """Test soft reboot method for SLES distro."""
     client = MagicMock()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python3-ipa-1.0.0/tests/test_ipa_utils.py 
new/python3-ipa-1.1.1/tests/test_ipa_utils.py
--- old/python3-ipa-1.0.0/tests/test_ipa_utils.py       2018-01-31 
20:21:42.000000000 +0100
+++ new/python3-ipa-1.1.1/tests/test_ipa_utils.py       2018-05-10 
18:17:37.000000000 +0200
@@ -174,6 +174,20 @@
     assert expanded[2] == 'test_hard_reboot'
 
 
+@patch('ipa.ipa_utils.execute_ssh_command')
+def test_utils_extract_archive(mock_exec_ssh_command):
+    client = MagicMock()
+
+    mock_exec_ssh_command.return_value = 'archive successfully extracted!'
+
+    # Test gzip
+    ipa_utils.extract_archive(client, 'archive.tar.gz', extract_path='/tmp/')
+    mock_exec_ssh_command.assert_called_once_with(
+        client, 'tar -xf archive.tar.gz -C /tmp/'
+    )
+    mock_exec_ssh_command.reset_mock()
+
+
 def test_utils_duplicate_files():
     """Test exception raised if duplicate test files exist."""
     test_dirs = ['tests/data/tests', 'tests/data/tests2']
@@ -262,6 +276,20 @@
     assert isinstance(rand_string, str)
 
 
+def test_utils_put_file():
+    client = MagicMock()
+    sftp_client = MagicMock()
+    client.open_sftp.return_value = sftp_client
+
+    source_file = '/home/user/temp.file'
+    destination_file = 'temp.file'
+
+    ipa_utils.put_file(client, source_file, destination_file)
+
+    sftp_client.put.assert_called_once_with(source_file, destination_file)
+    sftp_client.close.assert_called_once_with()
+
+
 def test_utils_redirect_output():
     """Test redirect output context manager."""
     temp_file = NamedTemporaryFile(mode='w+', delete=False)


Reply via email to