Repository: cekit/cekit

Issue:
"cekit test" invocation should support overrides again
With the new refactor cekit CLI, it appears that `cekit test` does not support overrides.

 * you can specify an alternative to `image.yaml`
 * you can specify an alternative image name to test (`--image`)

I though the above would probably be sufficient but test collection from modules can be impacted by module choices specified in overrides files. For example

```
cekit test --image openjdk/openjdk-11-rhel8 behave  --wip
2019-04-11 15:18:24,452 cekit        WARNING  You are running unreleased development version of CEKit, use it only at your own risk!
2019-04-11 15:18:24,525 cekit        INFO     You are running on known platform: Red Hat Enterprise Linux Server 7.6 (Maipo)
2019-04-11 15:18:24,531 cekit        INFO     Initializing image descriptor...
2019-04-11 15:18:24,556 cekit        INFO     Preparing resource 'cct_module'
2019-04-11 15:18:25,314 cekit        WARNING  Module version not specified for jboss.container.maven.35.bash, using 3.5scl version.
```

Here `cekit test` has chosen a module "3.5scl" to provide "jboss.container.maven.35.bash" but the correct module for the image I am testing is a different version (3.5).

In my case the module version is set in `rhel8-jdk11-overrides.yaml`, and none of the providers of that module name actually provide tests, so it isn't a problem… but if they did provide tests ( they might in future) then there would seem to be no way to help `cekit test` resolve the module correctly for some images.

Discussion:

Patch:
diff --git a/cekit/cli.py b/cekit/cli.py
index a9d6e8c..e14ea1f 100644
--- a/cekit/cli.py
+++ b/cekit/cli.py
@@ -173,7 +173,9 @@ def build_osbs(ctx, release, tech_preview, user, nowait, stage, koji_target, com
 
 @cli.group(short_help="Execute container image tests")
 @click.option('--image', help="Image to run tests against.")
-def test(image):  # pylint: disable=unused-argument
+@click.option('--overrides', metavar="JSON", help="Inline overrides in JSON format.", multiple=True)
+@click.option('--overrides-file', 'overrides', metavar="PATH", help="Path to overrides file in YAML format.", multiple=True)
+def test(image, overrides):  # pylint: disable=unused-argument
     """
     DESCRIPTION
 
@@ -323,7 +325,7 @@ class Cekit(object):
                 LOGGER.debug("Removing dirty directory: '{}'".format(directory))
                 try:
                     shutil.rmtree(directory)
-                except PermissionError as ex:
+                except:
                     raise CekitError("Unable to clean directory '{}'".format(directory))
 
     def run(self, clazz, params):
diff --git a/docs/descriptor/common_keys.rst b/docs/descriptor/common_keys.rst
index 7e199d5..1386bd5 100644
--- a/docs/descriptor/common_keys.rst
+++ b/docs/descriptor/common_keys.rst
@@ -6,8 +6,7 @@
 .. include:: includes/artifacts.rst
 .. include:: includes/packages.rst
 .. include:: includes/ports.rst
-.. include:: includes/user.rst
-.. include:: includes/volumes.rst	     
+.. include:: includes/volumes.rst
 .. include:: includes/modules.rst
 .. include:: includes/run.rst
-.. include:: includes/help.rst
\ No newline at end of file
+.. include:: includes/help.rst
diff --git a/docs/descriptor/includes/artifacts.rst b/docs/descriptor/includes/artifacts.rst
index 2320e1e..aea3c54 100644
--- a/docs/descriptor/includes/artifacts.rst
+++ b/docs/descriptor/includes/artifacts.rst
@@ -8,7 +8,7 @@ Artifacts section is meant exactly for this. CEKit will *automatically*
 fetch any artifacts specified in this section.
 
 If for some reason automatic fetching of artifacts is not an option for you,
-yoy should define artifacts as plain artifacts and use the ``cekit-cache``
+you should define artifacts as plain artifacts and use the ``cekit-cache``
 command to add the artifact to local cache, making it available for the build
 process automatically. See :doc:`/handbook/caching` chapter.
 
diff --git a/docs/descriptor/includes/run.rst b/docs/descriptor/includes/run.rst
index 82531a3..6d441da 100644
--- a/docs/descriptor/includes/run.rst
+++ b/docs/descriptor/includes/run.rst
@@ -57,6 +57,22 @@ Entrypoint that should be executed by the container at run time.
         entrypoint:
             - "/opt/eap/bin/wrapper.sh"
 
+User
+^^^^^^^^^^^^^^^^^^^^
+
+Key
+    ``user``
+Required
+    No
+
+Specifies the user (can be username or uid) that should be used to launch the main
+process.
+
+.. code-block:: yaml
+
+    run:
+        user: "alice"
+
 Working directory
 ^^^^^^^^^^^^^^^^^^^^
 
diff --git a/docs/descriptor/includes/user.rst b/docs/descriptor/includes/user.rst
deleted file mode 100644
index a54fc43..0000000
--- a/docs/descriptor/includes/user.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-User
-------
-
-Key
-    ``user``
-Required
-    No
-
-Specifies the user (can be username or uid) that should be used to launch the main
-process.
-
-.. code-block:: yaml
-
-    run:
-        user: "alice"
diff --git a/docs/descriptor/module.rst b/docs/descriptor/module.rst
index 3a92f25..acab43c 100644
--- a/docs/descriptor/module.rst
+++ b/docs/descriptor/module.rst
@@ -4,12 +4,12 @@ Module descriptor
 Module descriptor contains all information CEKit needs to introduce a feature to an image.
 Modules are used as libraries or shared building blocks across images.
 
-It is very important to make a module self-containg which means that executing
+It is very important to make a module self-contained which means that executing
 scripts defined in the module's definition file should always end up in a state
 where you could define the module as being `installed`.
 
-Modules can be stacked -- some modules will be run before, some after you module.
-Please keep that in mind that at the time when you are developing a module -- you don't
+Modules can be stacked -- some modules will be run before, some after your module.
+Please keep that in mind at the time you are developing your module -- you don't
 know how and when it'll be executed.
 
 .. contents::
diff --git a/docs/getting-started/before-you-start.rst b/docs/getting-started/before-you-start.rst
index 8f63084..975318a 100644
--- a/docs/getting-started/before-you-start.rst
+++ b/docs/getting-started/before-you-start.rst
@@ -1,7 +1,7 @@
 Before you start
 =================
 
-.. todo::
-    Write this!
+Please ensure that you have followed the installation instructions that may be found :doc:`here </handbook/installation/index>`.
 
-    * Link to installation instructions
+While several different build engines to construct container images are supported, this guide will use
+the :doc:`podman </handbook/building/builder-engines>` engine.
diff --git a/docs/getting-started/build.rst b/docs/getting-started/build.rst
index 2e95306..228586d 100644
--- a/docs/getting-started/build.rst
+++ b/docs/getting-started/build.rst
@@ -1,5 +1,35 @@
 Building your image
 ====================
 
-.. todo::
-    Write this!
\ No newline at end of file
+Now that a fully assembled image definition file has been constructed it is time to try building it. As mentioned previously we will use ``podman`` to build this image; for other build engines see :doc:`here </handbook/building/builder-engines>`
+
+.. code-block:: sh
+
+  $ cekit build podman
+
+This will output various logs (extra detail is possible via the verbose ``-v`` option).
+
+.. code-block:: none
+
+   cekit -v build podman
+   2019-04-05 13:23:37,408 cekit        INFO     You are running on known platform: Fedora 29 (Twenty Nine)
+   2019-04-05 13:23:37,482 cekit        INFO     Generating files for podman engine
+   2019-04-05 13:23:37,482 cekit        INFO     Initializing image descriptor...
+   2019-04-05 13:23:37,498 cekit        INFO     Preparing resource 'modules'
+   2019-04-05 13:23:37,510 cekit        INFO     Preparing resource 'example-common-module.git'
+   ...
+   STEP 41: FROM 850380a44a2b458cdadb0306fca831201c32d5c38ad1b8fb82968ab0637c40d0
+   STEP 42: CMD ["/home/user/apache-tomcat-8.5.24/bin/catalina.sh", "run"]
+   --> c55d3613c6a8d510c23fc56e2b56cf7a0eff58b97c262bef4f75675f1d0f9636
+   STEP 43: COMMIT my-example:1.0
+   2019-04-05 13:27:48,975 cekit        INFO     Image built and available under following tags: my-example:1.0, my-example:latest
+   2019-04-05 13:27:48,977 cekit        INFO     Finished!
+
+It is possible to use ``podman`` to list the new image e.g.
+
+.. code-block:: sh
+
+ $ podman images
+ REPOSITORY                  TAG      IMAGE ID       CREATED          SIZE
+ localhost/my-example        latest   c55d3613c6a8   48 seconds ago   709 MB
+ localhost/my-example        1.0      c55d3613c6a8   48 seconds ago   709 MB
diff --git a/docs/getting-started/image-descriptor.rst b/docs/getting-started/image-descriptor.rst
index d054cee..2b1614b 100644
--- a/docs/getting-started/image-descriptor.rst
+++ b/docs/getting-started/image-descriptor.rst
@@ -1,5 +1,32 @@
 Preparing image descriptor
 ============================
 
-.. todo::
-    Write this!
\ No newline at end of file
+This section will guide you through a very simple example.
+
+1. Using a standard text editor create an empty ``image.yml`` file. It is recommended to use the ``image.yaml`` naming scheme.
+2. As described in :doc:`Image Descriptor </descriptor/index>` several values are mandatory. Add the following to the file:
+
+.. code-block:: yaml
+
+   name: my-example
+   version: 1.0
+   from: centos:7
+
+* Next, while optional, it is recommended to add a suitable ``description`` tag e.g.
+
+.. code-block:: yaml
+
+    description: My Example Tomcat Image
+
+While this configuration will build in CEKit it isn't very interesting as it will simply create another image layered on top of CentOS 7. The descriptor should now look like:
+
+.. code-block:: yaml
+   :caption: image.yaml
+
+    name: my-example
+    version: 1.0
+    from: centos:7
+    description: My Example Tomcat Image
+
+
+It is possible to directly add further content to the image at this point through a variety of methods. Labels, ports, packages etc can be used - see :doc:`here </descriptor/image>`. In general modules are used as the 'building blocks' to assemble the image - they can be used as individual libraries or shared blocks across multiple images. So, move onto to :doc:`modules </getting-started/modules>` to discover more about these.
diff --git a/docs/getting-started/index.rst b/docs/getting-started/index.rst
index 8bd31f2..3e11038 100644
--- a/docs/getting-started/index.rst
+++ b/docs/getting-started/index.rst
@@ -10,10 +10,11 @@ If you need any help, just jump into the `#cekit IRC channel on Freenode <irc://
 
 .. toctree::
     :titlesonly:
-    
+
     before-you-start
     image-descriptor
     modules
     build
     run
     test
+    summary
diff --git a/docs/getting-started/modules.rst b/docs/getting-started/modules.rst
index 9d116e3..eef19fc 100644
--- a/docs/getting-started/modules.rst
+++ b/docs/getting-started/modules.rst
@@ -1,5 +1,156 @@
 First modules
 ============================
 
-.. todo::
-    Write this!
\ No newline at end of file
+As described in the :doc:`module reference </descriptor/module>` modules are used as libraries or shared building blocks across images.
+
+To add a module, the ``image.yaml`` file must be modified to add a modules section. This is responsible for defining module repositories and providing the list of modules to be installed in order. Modules may come from the local file system or from remote git based repositories e.g. on github.
+
+Edit the file to add the following
+
+.. code-block:: yaml
+
+  modules:
+    repositories:
+        - path: modules
+
+As per the below diagram a number of directories must be created next to ``image.yaml``.
+
+.. graphviz::
+    :align: center
+    :alt: CEKit simple build process diagram
+
+    digraph modules_diagram {
+        rankdir="LR";
+        graph [fontsize="11", fontname="Open Sans", compound="true", splines=ortho];
+        node [shape="box", fontname="Open Sans", fontsize="11"];
+
+        subgraph cluster_0 {
+            node [style="filled"];
+            penwidth = "2";
+            color="dimgrey";
+            rank=same;
+
+            modules [label="modules", group="m"];
+            jdk8 [label="jdk8"];
+            user [label="user"];
+            tomcat [label="tomcat", group="m"];
+        }
+        modules -> tomcat[lhead=cluster_0, arrowhead=none]
+        modules -> jdk8[lhead=cluster_0, arrowhead=none]
+        modules -> user[lhead=cluster_0, arrowhead=none]
+        descriptor [label="image.yaml"];
+    }
+
+Once the modules subdirectory and the respective module directories below that have been created they can be added to the image. In order to select a module component from a repository it is necessary to add an ``install`` section.
+
+.. code-block:: yaml
+
+    # Install selected modules (in order)
+    install:
+        - name: jdk8
+        - name: user
+        - name: tomcat
+
+In order to add and populate the local file system modules follow the below instructions.
+
+.. note::
+   All module yaml files should be named ``module.yaml``
+
+JDK8
+^^^^^^^^^
+* Create an empty ``module.yaml`` file within the ``jdk8`` directory.
+* Enter the following code:
+
+.. code-block:: yaml
+   :caption: module.yaml
+
+   schema_version: 1
+
+   name: jdk8
+   version: 1.0
+   description: Module installing OpenJDK 8
+
+   envs:
+      - name: "JAVA_HOME"
+        value: "/usr/lib/jvm/java-1.8.0-openjdk"
+
+   packages:
+      install:
+         - java-1.8.0-openjdk-devel
+
+* An :doc:`environment variable</descriptor/includes/envs>` has been defined that will be present in the container after running the image.
+* :doc:`packages</descriptor/includes/packages>` have been used to add the JDK RPM.
+
+
+User
+^^^^^^^^^
+* Create the ``module.yaml`` and ``create.sh`` files within the ``user`` directory.
+* Enter the following code:
+
+.. code-block:: yaml
+   :caption: module.yaml
+
+   schema_version: 1
+
+   name: user
+   version: 1.0
+   description: "Creates a regular user that could be used to run any service, gui/uid: 1000"
+
+   execute:
+     - script: create.sh
+
+   run:
+      user: 1000
+      workdir: "/home/user"
+
+
+.. code-block:: sh
+   :caption: create.sh
+
+   #!/bin/sh
+
+   set -e
+
+   groupadd -r user -g 1000 && useradd -u 1000 -r -g user -m -d /home/user -s /sbin/nologin -c "Regular user" user
+
+
+* An :doc:`execute</descriptor/includes/module/execute>` command is used to define what needs to be done to install this module in the image. It will be run at build time.
+* A :doc:`run</descriptor/includes/run>` command sets the working directory and user that is used to launch the main process.
+
+
+Tomcat
+^^^^^^^^^
+* Finally, create the following two files inside the ``tomcat`` directory:
+
+.. code-block:: sh
+   :caption: install.sh
+
+   #!/bin/sh
+   set -e
+   tar -C /home/user -xf /tmp/artifacts/tomcat.tar.gz
+   chown user:user -R /home/user
+
+.. code-block:: yaml
+   :caption: module.yml
+
+   name: tomcat
+   version: 1.0
+   description: "Module used to install Tomcat 8"
+   # Defined artifacts that are used to build the image
+   artifacts:
+     - name: tomcat.tar.gz
+        url: https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.24/bin/apache-tomcat-8.5.24.tar.gz
+        md5: 080075877a66adf52b7f6d0013fa9730
+   execute:
+     - script: install.sh
+
+   run:
+      cmd:
+        - "/home/user/apache-tomcat-8.5.24/bin/catalina.sh"
+        - "run"
+
+* The :doc:`artifact</descriptor/includes/artifacts>` command is used to retrieve external artifacts that need to be added to the image.
+
+
+
+Move onto the :doc:`build section </getting-started/build>` to build this new image.
diff --git a/docs/getting-started/run.rst b/docs/getting-started/run.rst
index 8b6fd84..6a4a9fa 100644
--- a/docs/getting-started/run.rst
+++ b/docs/getting-started/run.rst
@@ -1,5 +1,29 @@
 Is it running?
 ====================
 
-.. todo::
-    Write this!
\ No newline at end of file
+Now lets try running the image. As was shown in the preceeding example it is possible to obtain the image id through the ``podman images`` command. To ensure the local host machine can see the image the following command will map port 8080 to 32597.
+
+.. code-block:: sh
+
+ $ podman run -p 32597:8080 localhost/my-example:1.0
+
+When the image is built it is automatically tagged using the ``name`` key in the image descriptor combined with the ``version`` key. As the tomcat module that was specified earlier included a :doc:`run </descriptor/include/run>` command it will automatically start the Tomcat webserver.
+
+Using your browser go to http://localhost:32597 ; if successful then the image is running correctly.
+
+Note: if you want to interactively explore the new image use the following command:
+
+.. code-block:: sh
+
+ $ podman run -it --rm localhost/my-example:1.0 /bin/bash
+
+.. note::
+   It is also possible to reference using the image id e.g. ``podman run -it --rm $(podman images -q | head -1) /bin/bash``.
+
+Once an interactive shell has been started on the image it is possible to verify the JDK has been installed e.g.
+
+.. code-block:: sh
+
+   $ podman run -it --rm my-example:latest /bin/bash
+   [user@ff7b60ea4d7c ~]$ rpm -qa | grep openjdk-devel
+   java-1.8.0-openjdk-devel-1.8.0.201.b09-2.el7_6.x86_64
diff --git a/docs/getting-started/summary.rst b/docs/getting-started/summary.rst
new file mode 100644
index 0000000..4d280eb
--- /dev/null
+++ b/docs/getting-started/summary.rst
@@ -0,0 +1,20 @@
+Summary
+=================
+
+**Congratulations! You have now completed the getting started guide.**
+
+The examples used are available at github:
+
+* https://github.com/cekit/example-image-tomcat/
+* https://github.com/cekit/example-common-module/
+
+The only difference is that the ``example-image-tomcat`` utilises a remote module repository reference to load the ``jdk8`` and ``user`` modules which are within ``example-common-module`` e.g.
+
+.. code-block:: yaml
+
+  modules:
+    repositories:
+        - path: modules
+        - git:
+            url: https://github.com/cekit/example-common-module.git
+            ref: master


Test patch:
diff --git a/tests/test_integ_test_behave.py b/tests/test_integ_test_behave.py
index b429772..a03c262 100644
--- a/tests/test_integ_test_behave.py
+++ b/tests/test_integ_test_behave.py
@@ -24,27 +24,32 @@ image_descriptor = {
 }
 
 
-@pytest.fixture(scope="module", name="test_image_dir")
-def build_test_image_dir():
-    image_dir = tempfile.mkdtemp(prefix="tmp-cekit-test")
+@pytest.fixture(scope="function", name="build_image")
+def fixture_build_image():
+    def _build_image(overrides=None):
+        image_dir = tempfile.mkdtemp(prefix="tmp-cekit-test")
 
-    with open(os.path.join(image_dir, 'image.yaml'), 'w') as fd:
-        yaml.dump(image_descriptor, fd, default_flow_style=False)
+        with open(os.path.join(image_dir, 'image.yaml'), 'w') as fd:
+            yaml.dump(image_descriptor, fd, default_flow_style=False)
 
-    with Chdir(image_dir):
-        result = CliRunner().invoke(cli, ["-v", "build", "docker"], catch_exceptions=False)
+        args = ["-v", "build"]
 
-    assert result.exit_code == 0
+        if overrides:
+            args += ["--overrides", overrides]
 
-    return image_dir
+        args.append("docker")
 
+        with Chdir(image_dir):
+            result = CliRunner().invoke(cli, args, catch_exceptions=False)
 
-@pytest.fixture(autouse=True)
-def clean_target_directory(test_image_dir):
-    shutil.rmtree(os.path.join(test_image_dir, 'target'), ignore_errors=True)
+        assert result.exit_code == 0
+
+        return image_dir
 
+    return _build_image
 
-def test_execute_simple_behave_test(test_image_dir):
+
+def test_execute_simple_behave_test(build_image):
     feature = """@test
 Feature: Basic tests
 
@@ -54,6 +59,8 @@ Feature: Basic tests
      And the image should contain label labela with value a
     """
 
+    test_image_dir = build_image()
+
     features_dir = os.path.join(test_image_dir, 'tests', 'features')
 
     os.makedirs(features_dir)
@@ -70,3 +77,40 @@ Feature: Basic tests
     assert "1 feature passed, 0 failed, 0 skipped" in result.output
     assert "1 scenario passed, 0 failed, 0 skipped" in result.output
     assert "3 steps passed, 0 failed, 0 skipped, 0 undefined" in result.output
+
+    shutil.rmtree(os.path.join(test_image_dir, 'target'), ignore_errors=True)
+
+
+def test_execute_simple_behave_test_with_overrides(build_image):
+    feature = """@different
+Feature: Basic tests
+
+  Scenario: Check that the labels are correctly set
+    Given image is built
+    Then the image should contain label foo with value bar
+     And the image should contain label labela with value a
+    """
+
+    overrides = '{"name": "different/image"}'
+
+    test_image_dir = build_image(overrides)
+
+    features_dir = os.path.join(test_image_dir, 'tests', 'features')
+
+    os.makedirs(features_dir)
+
+    with open(os.path.join(features_dir, 'basic.feature'), 'w') as fd:
+        fd.write(feature)
+
+    with Chdir(test_image_dir):
+        result = CliRunner().invoke(
+            cli, ["-v", "test", "--overrides", overrides, "behave"], catch_exceptions=False)
+
+    print(result.output)
+
+    assert result.exit_code == 0
+    assert "1 feature passed, 0 failed, 0 skipped" in result.output
+    assert "1 scenario passed, 0 failed, 0 skipped" in result.output
+    assert "3 steps passed, 0 failed, 0 skipped, 0 undefined" in result.output
+
+    shutil.rmtree(os.path.join(test_image_dir, 'target'), ignore_errors=True)
diff --git a/tests/test_unit_args.py b/tests/test_unit_args.py
index 80b5ffb..b9e4f87 100644
--- a/tests/test_unit_args.py
+++ b/tests/test_unit_args.py
@@ -123,7 +123,7 @@ def get_class_by_name(clazz):
             'descriptor': 'image.yaml', 'verbose': False, 'work_dir': '~/.cekit', 'config': '~/.cekit/config', 'redhat': False, 'target': 'target'
         },
         {
-            'image': 'image:1.0', 'steps_url': 'https://github.com/cekit/behave-test-steps.git', 'wip': False, 'names': ()
+            'overrides': (), 'image': 'image:1.0', 'steps_url': 'https://github.com/cekit/behave-test-steps.git', 'wip': False, 'names': ()
         }
     ),
     (