From c1a54f79fa0c35536e420eda1e429723c532c891 Mon Sep 17 00:00:00 2001
From: Michael Calmer <mc@suse.de>
Date: Thu, 19 Jan 2017 15:39:10 +0100
Subject: [PATCH] add options for dockerng

* add buildargs option to dockerng.build
* docker buildargs require minimal dockerpy 1.6.0 and docker version 1.9.0
* update version in the docs
* support dryrun for dockerng.sls_build

provide the possibility to put extra modules into the thin

added unit test for dockerng.sls_build dryrun option
---
 salt/modules/dockerng.py            | 51 ++++++++++++++++++-----
 tests/unit/modules/dockerng_test.py | 82 +++++++++++++++++++++++++++++++++++++
 2 files changed, 122 insertions(+), 11 deletions(-)

diff --git a/salt/modules/dockerng.py b/salt/modules/dockerng.py
index 09d80bdd3f..8366e5e8ba 100644
--- a/salt/modules/dockerng.py
+++ b/salt/modules/dockerng.py
@@ -28,13 +28,13 @@ to replace references to ``dockerng`` with ``docker``.
 Installation Prerequisites
 --------------------------
 
-This execution module requires at least version 1.4.0 of both docker-py_ and
-Docker_. docker-py can easily be installed using :py:func:`pip.install
-<salt.modules.pip.install>`:
+This execution module requires at least version 1.6.0 of docker-py_ and
+version 1.9.0 of Docker_. docker-py can easily be installed using
+:py:func:`pip.install <salt.modules.pip.install>`:
 
 .. code-block:: bash
 
-    salt myminion pip.install docker-py>=1.4.0
+    salt myminion pip.install docker-py>=1.6.0
 
 .. _docker-py: https://pypi.python.org/pypi/docker-py
 .. _Docker: https://www.docker.com/
@@ -268,8 +268,8 @@ __func_alias__ = {
 }
 
 # Minimum supported versions
-MIN_DOCKER = (1, 6, 0)
-MIN_DOCKER_PY = (1, 4, 0)
+MIN_DOCKER = (1, 9, 0)
+MIN_DOCKER_PY = (1, 6, 0)
 
 VERSION_RE = r'([\d.]+)'
 
@@ -3479,7 +3479,8 @@ def build(path=None,
           rm=True,
           api_response=False,
           fileobj=None,
-          dockerfile=None):
+          dockerfile=None,
+          buildargs=None):
     '''
     Builds a docker image from a Dockerfile or a URL
 
@@ -3513,6 +3514,10 @@ def build(path=None,
 
         .. versionadded:: develop
 
+    buildargs
+        A dictionary of build arguments provided to the docker build process.
+
+
     **RETURN DATA**
 
     A dictionary containing one or more of the following keys:
@@ -3559,7 +3564,8 @@ def build(path=None,
                                fileobj=fileobj,
                                rm=rm,
                                nocache=not cache,
-                               dockerfile=dockerfile)
+                               dockerfile=dockerfile,
+                               buildargs=buildargs)
     ret = {'Time_Elapsed': time.time() - time_started}
     _clear_context()
 
@@ -5657,7 +5663,9 @@ def call(name, function, *args, **kwargs):
         raise CommandExecutionError('Missing function parameter')
 
     # move salt into the container
-    thin_path = salt.utils.thin.gen_thin(__opts__['cachedir'])
+    thin_path = salt.utils.thin.gen_thin(__opts__['cachedir'],
+                                         extra_mods=__salt__['config.option']("thin_extra_mods", ''),
+                                         so_mods=__salt__['config.option']("thin_so_mods", ''))
     with io.open(thin_path, 'rb') as file:
         _client_wrapper('put_archive', name, thin_dest_path, file)
     try:
@@ -5774,7 +5782,7 @@ def sls(name, mods=None, saltenv='base', **kwargs):
 
 
 def sls_build(name, base='opensuse/python', mods=None, saltenv='base',
-              **kwargs):
+              dryrun=False, **kwargs):
     '''
     Build a Docker image using the specified SLS modules on top of base image
 
@@ -5796,6 +5804,24 @@ def sls_build(name, base='opensuse/python', mods=None, saltenv='base',
         Specify the environment from which to retrieve the SLS indicated by the
         `mods` parameter.
 
+    base
+        the base image
+
+    mods
+        the state modules to execute during build
+
+    saltenv
+        the salt environment to use
+
+    dryrun: False
+        when set to True the container will not be commited at the end of
+        the build. The dryrun succeed also when the state contains errors.
+
+    **RETURN DATA**
+
+    A dictionary with the ID of the new container. In case of a dryrun,
+    the state result is returned and the container gets removed.
+
     CLI Example:
 
     .. code-block:: bash
@@ -5822,11 +5848,14 @@ def sls_build(name, base='opensuse/python', mods=None, saltenv='base',
         # Now execute the state into the container
         ret = __salt__['dockerng.sls'](id_, mods, saltenv, **kwargs)
         # fail if the state was not successful
-        if not salt.utils.check_state_result(ret):
+        if not dryrun and not salt.utils.check_state_result(ret):
             raise CommandExecutionError(ret)
     finally:
         __salt__['dockerng.stop'](id_)
 
+    if dryrun:
+        __salt__['dockerng.rm'](id_)
+        return ret
     return __salt__['dockerng.commit'](id_, name)
 
 
diff --git a/tests/unit/modules/dockerng_test.py b/tests/unit/modules/dockerng_test.py
index f213ef0d1f..36478cc4c6 100644
--- a/tests/unit/modules/dockerng_test.py
+++ b/tests/unit/modules/dockerng_test.py
@@ -755,6 +755,84 @@ class DockerngTestCase(TestCase):
         self.assertEqual(
             {'Id': 'ID2', 'Image': 'foo', 'Time_Elapsed': 42}, ret)
 
+    def test_sls_build_dryrun(self, *args):
+        '''
+        test build sls image in dryrun mode.
+        '''
+        docker_start_mock = MagicMock(
+            return_value={})
+        docker_create_mock = MagicMock(
+            return_value={'Id': 'ID', 'Name': 'NAME'})
+        docker_stop_mock = MagicMock(
+            return_value={'state': {'old': 'running', 'new': 'stopped'},
+                          'result': True})
+        docker_rm_mock = MagicMock(
+            return_value={})
+
+        docker_sls_mock = MagicMock(
+            return_value={
+                "file_|-/etc/test.sh_|-/etc/test.sh_|-managed": {
+                    "comment": "File /etc/test.sh is in the correct state",
+                    "name": "/etc/test.sh",
+                    "start_time": "07:04:26.834792",
+                    "result": True,
+                    "duration": 13.492,
+                    "__run_num__": 0,
+                    "changes": {}
+                },
+                "test_|-always-passes_|-foo_|-succeed_without_changes": {
+                    "comment": "Success!",
+                    "name": "foo",
+                    "start_time": "07:04:26.848915",
+                    "result": True,
+                    "duration": 0.363,
+                    "__run_num__": 1,
+                    "changes": {}
+                }
+            })
+
+        ret = None
+        with patch.dict(dockerng_mod.__salt__, {
+                'dockerng.start': docker_start_mock,
+                'dockerng.create': docker_create_mock,
+                'dockerng.stop': docker_stop_mock,
+                'dockerng.rm': docker_rm_mock,
+                'dockerng.sls': docker_sls_mock}):
+            ret = dockerng_mod.sls_build(
+                'foo',
+                mods='foo',
+                dryrun=True
+            )
+        docker_create_mock.assert_called_once_with(
+            cmd='sleep infinity',
+            image='opensuse/python', interactive=True, name='foo', tty=True)
+        docker_start_mock.assert_called_once_with('ID')
+        docker_sls_mock.assert_called_once_with('ID', 'foo', 'base')
+        docker_stop_mock.assert_called_once_with('ID')
+        docker_rm_mock.assert_called_once_with('ID')
+        self.assertEqual(
+                {
+                "file_|-/etc/test.sh_|-/etc/test.sh_|-managed": {
+                    "comment": "File /etc/test.sh is in the correct state",
+                    "name": "/etc/test.sh",
+                    "start_time": "07:04:26.834792",
+                    "result": True,
+                    "duration": 13.492,
+                    "__run_num__": 0,
+                    "changes": {}
+                },
+                "test_|-always-passes_|-foo_|-succeed_without_changes": {
+                    "comment": "Success!",
+                    "name": "foo",
+                    "start_time": "07:04:26.848915",
+                    "result": True,
+                    "duration": 0.363,
+                    "__run_num__": 1,
+                    "changes": {}
+                }
+                },
+                ret)
+
     def test_call_success(self):
         '''
         test module calling inside containers
@@ -769,6 +847,9 @@ class DockerngTestCase(TestCase):
             return_value={
                 'retcode': 0
             })
+        docker_config_mock = MagicMock(
+            return_value=''
+            )
         client = Mock()
         client.put_archive = Mock()
 
@@ -779,6 +860,7 @@ class DockerngTestCase(TestCase):
                     dockerng_mod.__salt__, {
                         'dockerng.run_all': docker_run_all_mock,
                         'dockerng.copy_to': docker_copy_to_mock,
+                        'config.option': docker_config_mock
                     }),
                 patch.dict(
                     dockerng_mod.__context__, {
-- 
2.13.6


