Repository: cekit/cekit

Issue:
Start using fetch-artifacts-pnc
**Describe the solution you'd like**
CEKit should start generated fetch artifacts configurations using [fetch-artifacts-pnc](https://osbs.readthedocs.io/en/latest/users.html#fetch-artifacts-pnc-yaml). This new mechanism offers close integration with PNC and can fetch artifacts as well as sources directly from PNC builds.

**Additional context**
Using fetch-artifacts-pnc will also allow OSBS to build source containers for middleware product images, fetching sources directly from PNC as well.


Discussion:

Patch:
diff --git a/cekit/builders/osbs.py b/cekit/builders/osbs.py
index bcedf4f..938d8ca 100644
--- a/cekit/builders/osbs.py
+++ b/cekit/builders/osbs.py
@@ -416,6 +416,10 @@ class DistGit(object):
                 LOGGER.info("Removing old 'fetch-artifacts-url.yaml' file")
                 subprocess.check_call(["git", "rm", "-rf", "fetch-artifacts-url.yaml"])
 
+            if os.path.exists("fetch-artifacts-pnc.yaml"):
+                LOGGER.info("Removing old 'fetch-artifacts-pnc.yaml' file")
+                subprocess.check_call(["git", "rm", "-rf", "fetch-artifacts-pnc.yaml"])
+
     def add(self, artifacts):
         LOGGER.debug("Adding files to git stage...")
 
diff --git a/cekit/descriptor/resource.py b/cekit/descriptor/resource.py
index c11d6d6..05305f9 100644
--- a/cekit/descriptor/resource.py
+++ b/cekit/descriptor/resource.py
@@ -46,6 +46,10 @@ def create_resource(descriptor, **kwargs):
 
         return _PathResource(descriptor, directory)
 
+    # PNC first so it can have an optional URL component
+    if 'pnc_build_id' in descriptor:
+        return _PncResource(descriptor)
+
     if 'url' in descriptor:
         return _UrlResource(descriptor)
 
@@ -560,3 +564,38 @@ class _ImageContentResource(Resource):
         in an image that should be built in earlier stage of the image build process.
         """
         return target
+
+
+class _PncResource(Resource):
+    """
+    Documentation: http://docs.cekit.io/en/latest/descriptor/image.html#pnc-artifacts
+    """
+
+    SCHEMA = {
+        'map': {
+            'name': {'type': 'str', 'desc': 'Key used to identify the resource'},
+            'target': {'type': 'str', 'required': True, 'desc': 'Target file name for the resource'},
+            'dest': {'type': 'str', 'desc': 'Destination directory inside of the container', 'default': artifact_dest},
+            'description': {'type': 'str', 'desc': 'Description of the resource'},
+            'pnc_artifact_id': {'type': 'str', 'required': True, 'desc': 'The ID of the artifact'},
+            'pnc_build_id': {'type': 'str', 'required': True, 'desc': 'The ID of the build'},
+            'url': {'type': 'str', 'desc': 'Optional URL where the resource can be found'}
+        }
+    }
+
+    def __init__(self, descriptor):
+        self.schema = _PncResource.SCHEMA
+
+        super(_PncResource, self).__init__(descriptor)
+
+    def _get_default_name_value(self, descriptor):
+        """
+        Default identifier is the target file name.
+        """
+        return descriptor.get('target')
+
+    def _copy_impl(self, target):
+        """
+        For PNC artifacts there is nothing to copy as the artifact is held remotely on PNC.
+        """
+        return target
diff --git a/cekit/generator/base.py b/cekit/generator/base.py
index 9760d3f..e28be70 100644
--- a/cekit/generator/base.py
+++ b/cekit/generator/base.py
@@ -53,6 +53,7 @@ class Generator(object):
 
         if overrides:
             for override in overrides:
+
                 LOGGER.debug("Loading override '{}'".format(override))
 
                 override_artifact_dir = os.path.dirname(os.path.abspath(override))
@@ -314,8 +315,7 @@ class Generator(object):
     def render_help(self):
         """
         If requested, renders image help page based on the image descriptor.
-        It is generated to the $target/image/help.md file and added later
-        to the root of the image (/).
+        It is generated to the $target/image/help.md file.
         """
 
         if not self.image.help.get('add', False):
diff --git a/cekit/generator/osbs.py b/cekit/generator/osbs.py
index dd54f22..16091ce 100644
--- a/cekit/generator/osbs.py
+++ b/cekit/generator/osbs.py
@@ -3,6 +3,9 @@ import logging
 import os
 import sys
 import tempfile
+from collections import OrderedDict
+from contextlib import closing
+
 import yaml
 
 try:
@@ -10,9 +13,10 @@ try:
 except ImportError:
     from urlparse import urlparse
 
+from cekit import version
 from cekit import crypto
 from cekit.config import Config
-from cekit.descriptor.resource import _PlainResource, _UrlResource
+from cekit.descriptor.resource import _PlainResource, _UrlResource, _PncResource
 from cekit.errors import CekitError
 from cekit.generator.base import Generator
 from cekit.tools import get_brew_url, copy_recursively
@@ -89,7 +93,8 @@ class OSBSGenerator(Generator):
         logger.info("Handling artifacts for OSBS...")
         target_dir = os.path.join(self.target, 'image')
         fetch_artifacts_url = []
-        url_description = {}
+        fetch_artifacts_pnc = OrderedDict()
+        file_comments = {}
 
         fetch_domains = config.get('common', 'fetch_artifact_domains')
 
@@ -135,7 +140,7 @@ class OSBSGenerator(Generator):
                     for c in intersected_hash:
                         fetch_artifacts_url[len(fetch_artifacts_url) - 1].update({c: artifact[c]})
                     if 'description' in artifact:
-                        url_description[artifact['url']] = artifact['description']
+                        file_comments[artifact['url']] = artifact['description']
                     logger.debug(
                         "Artifact '{}' (as URL) added to fetch-artifacts-url.yaml with contents {}".format(
                             artifact['target'], fetch_artifacts_url[len(fetch_artifacts_url) - 1]))
@@ -157,19 +162,43 @@ class OSBSGenerator(Generator):
                         # TODO: This is ugly, rewrite this!
                         artifact['lookaside'] = True
 
+                elif isinstance(artifact, _PncResource):
+                    logger.info("Handling pnc resources for {}".format(artifact))
+                    build = fetch_artifacts_pnc.setdefault(artifact['pnc_build_id'], [])
+                    build.append({'id': artifact['pnc_artifact_id'], 'target': artifact['dest'] + artifact['target']})
+                    if 'url' in artifact:
+                        file_comments[artifact['pnc_artifact_id']] = artifact['url']
                 else:
                     logger.debug("Copying artifact {} to {}".format(artifact, target_dir))
                     artifact.copy(target_dir)
 
-        fetch_artifacts_file = os.path.join(self.target, 'image', 'fetch-artifacts-url.yaml')
-
+        if fetch_artifacts_pnc:
+            fetch_artifacts_file = os.path.join(self.target, 'image', 'fetch-artifacts-pnc.yaml')
+            pnc = {
+                'metadata': {'author': 'CEKit ' + version.__version__},
+                'builds': [{'build_id': key, 'artifacts': fetch_artifacts_pnc.get(key)} for key in fetch_artifacts_pnc]
+            }
+            logger.debug("Writing {} to fetch-artifacts-pnc.yaml".format(pnc))
+            with open(fetch_artifacts_file, 'w') as _file:
+                _file.write("# Created by CEKit version {}\n".format(version.__version__))
+                yaml.safe_dump(pnc, _file, default_flow_style=False, sort_keys=False)
+            patch_file(file_comments, fetch_artifacts_file)
         if fetch_artifacts_url:
+            fetch_artifacts_file = os.path.join(self.target, 'image', 'fetch-artifacts-url.yaml')
             with open(fetch_artifacts_file, 'w') as _file:
-                yaml.safe_dump(fetch_artifacts_url, _file, default_flow_style=False)
-            if config.get('common', 'redhat'):
-                for key, value in url_description.items():
-                    logger.debug("Processing to add build references for {} -> {}".format(key, value))
-                    for line in fileinput.input(fetch_artifacts_file, inplace=1):
-                        line = line.replace(key, key + ' # ' + value)
-                        sys.stdout.write(line)
+                _file.write("# Created by CEKit version {}\n".format(version.__version__))
+                yaml.safe_dump(fetch_artifacts_url, _file, default_flow_style=False, sort_keys=False)
+            patch_file(file_comments, fetch_artifacts_file)
         logger.debug("Artifacts handled")
+
+
+# Used to modify either the fetch-artifact or fetch-pnc files to add extra human readable information.
+def patch_file(file_comments, file):
+    # Can't use it as a context manager with plain with as that is >= 3.2
+    with closing(fileinput.input(file, inplace=1)) as input_list:
+        for line in input_list:
+            r = [line.replace('\n', ' # ' + value + '\n') for (key, value) in file_comments.items() if key in line]
+            if r:
+                sys.stdout.write(r[0])
+            else:
+                sys.stdout.write(line)
diff --git a/docs/contribution-guide/releasing.rst b/docs/contribution-guide/releasing.rst
index 3468c90..91ba5a7 100644
--- a/docs/contribution-guide/releasing.rst
+++ b/docs/contribution-guide/releasing.rst
@@ -16,7 +16,6 @@ The automated process uses https://pypi.org/project/zest.releaser to perform the
 * Clone ``git@github.com:cekit/cekit.git`` (from https://github.com/cekit/cekit)
 * Run ``make release`` and follow the prompts.
 
-
     .. note:: A ``.pypirc`` should be configured according to https://packaging.python.org/specifications/pypirc for PyPi uploads.
 
     .. note:: It is recommended to use a 3 digit version specification when prompted (e.g. ``3.9.10``, ``3.10.2``).
@@ -25,7 +24,7 @@ Manual Process (reference only)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 * Switch to master branch
-+ Ensure you are latest code: ``git reset --hard upstream/master``
+* Ensure you have the latest code: ``git reset --hard upstream/master``
 * Merge develop: ``git merge develop -X theirs`` and in the commit message enter: ``Release <version>``
 * Edit ``cekit/version.py`` file and put ``<next-version>``, add that file and amend the previous commit.
 * Tag repository: ``git tag <version>``
diff --git a/docs/descriptor/includes/artifacts.rst b/docs/descriptor/includes/artifacts.rst
index da121d7..5aa5500 100644
--- a/docs/descriptor/includes/artifacts.rst
+++ b/docs/descriptor/includes/artifacts.rst
@@ -147,6 +147,7 @@ CEKit supports following artifact types:
 * URL artifacts
 * Path artifacts
 * Image source artifacts
+* `PNC <https://github.com/project-ncl/pnc>`_ artifacts
 
 Plain artifacts
 ******************
@@ -259,3 +260,25 @@ multi-stage builds.
           # Path to the artifact within the image
           path: /path/to/application/inside/the/builder/image.jar
 
+PNC artifacts
+************************
+
+PNC artifacts are created via the `ProjectNCL <https://github.com/project-ncl/pnc>`_ build system and are
+intended to be used with `OSBS <https://osbs.readthedocs.io/en/latest/users.html#fetch-artifacts-pnc-yaml>`_
+``fetch-artifacts-pnc.yaml``.
+
+
+.. schema:: cekit.descriptor.resource._PncResource
+    :name: pnc-artifact-schema
+
+.. code-block:: yaml
+    :name: pnc-artifact-examples
+    :caption: Examples
+
+    artifacts:
+        - name: jolokia
+          pnc_build_id: 123456
+          pnc_artifact_id: 00001
+          target: jolokia.tar.gz
+          # Optional URL to denote the link to the artifact
+          url: https://www.pnc.build.system.com/builds/123456/jolokia-1.3.6-bin.tar.gz
diff --git a/docs/handbook/redhat.rst b/docs/handbook/redhat.rst
index 04c9005..780b9da 100644
--- a/docs/handbook/redhat.rst
+++ b/docs/handbook/redhat.rst
@@ -47,7 +47,7 @@ directly from Brew. To enable this set :ref:`redhat configuration option <handbo
 to ``True`` (or use ``--redhat`` switch) and define plain artifacts which have ``md5`` checksum.
 
 .. warning::
-    Using different checksum thn ``md5`` will not work!
+    Using a different checksum than ``md5`` will not work!
 
 CEKit will fetch artifacts automatically from Brew, adding them to local cache.
 


Test patch:
diff --git a/tests/test_integ_builder_osbs.py b/tests/test_integ_builder_osbs.py
index 73408d1..6372060 100644
--- a/tests/test_integ_builder_osbs.py
+++ b/tests/test_integ_builder_osbs.py
@@ -9,10 +9,8 @@ import shutil
 import subprocess
 import sys
 
-import yaml
-
 import pytest
-
+import yaml
 from click.testing import CliRunner
 
 from cekit.cli import cli
@@ -761,9 +759,6 @@ def test_osbs_builder_with_fetch_artifacts_url_file_creation_1(tmpdir, mocker, c
 
     tmpdir.mkdir('osbs').mkdir('repo')
 
-    tmpdir.join('osbs').join('repo').join(
-        'fetch-artifacts-url.yaml').write_text(u'Some content', 'utf8')
-
     with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
         subprocess.call(["git", "init"])
         subprocess.call(["git", "add", "fetch-artifacts-url.yaml"])
@@ -805,9 +800,6 @@ def test_osbs_builder_with_fetch_artifacts_url_file_creation_2(tmpdir, mocker, c
 
     tmpdir.mkdir('osbs').mkdir('repo')
 
-    tmpdir.join('osbs').join('repo').join(
-        'fetch-artifacts-url.yaml').write_text(u'Some content', 'utf8')
-
     with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
         subprocess.call(["git", "init"])
         subprocess.call(["git", "add", "fetch-artifacts-url.yaml"])
@@ -845,9 +837,6 @@ def test_osbs_builder_with_fetch_artifacts_url_file_creation_3(tmpdir, mocker, c
 
     tmpdir.mkdir('osbs').mkdir('repo')
 
-    tmpdir.join('osbs').join('repo').join(
-        'fetch-artifacts-url.yaml').write_text(u'Some content', 'utf8')
-
     with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
         subprocess.call(["git", "init"])
         subprocess.call(["git", "add", "fetch-artifacts-url.yaml"])
@@ -889,9 +878,6 @@ def test_osbs_builder_with_fetch_artifacts_url_file_creation_4(tmpdir, mocker, c
 
     tmpdir.mkdir('osbs').mkdir('repo')
 
-    tmpdir.join('osbs').join('repo').join(
-        'fetch-artifacts-url.yaml').write_text(u'Some content', 'utf8')
-
     with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
         subprocess.call(["git", "init"])
         subprocess.call(["git", "add", "fetch-artifacts-url.yaml"])
@@ -929,9 +915,6 @@ def test_osbs_builder_with_fetch_artifacts_url_file_creation_5(tmpdir, mocker, c
 
     tmpdir.mkdir('osbs').mkdir('repo')
 
-    tmpdir.join('osbs').join('repo').join(
-        'fetch-artifacts-url.yaml').write_text(u'Some content', 'utf8')
-
     res = mocker.Mock()
     res.getcode.return_value = 200
     res.read.side_effect = [b'test', None]
@@ -994,9 +977,6 @@ def test_osbs_builder_with_fetch_artifacts_url_file_creation_multiple_hash(tmpdi
 
     tmpdir.mkdir('osbs').mkdir('repo')
 
-    tmpdir.join('osbs').join('repo').join(
-        'fetch-artifacts-url.yaml').write_text(u'Some content', 'utf8')
-
     with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
         subprocess.call(["git", "init"])
         subprocess.call(["git", "add", "fetch-artifacts-url.yaml"])
@@ -1038,9 +1018,6 @@ def test_osbs_builder_with_fetch_artifacts_url_file_creation_naming(tmpdir, mock
 
     tmpdir.mkdir('osbs').mkdir('repo')
 
-    tmpdir.join('osbs').join('repo').join(
-        'fetch-artifacts-url.yaml').write_text(u'Some content', 'utf8')
-
     with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
         subprocess.call(["git", "init"])
         subprocess.call(["git", "add", "fetch-artifacts-url.yaml"])
@@ -1082,9 +1059,6 @@ def test_osbs_builder_with_fetch_artifacts_url_file_creation_naming_with_target(
 
     tmpdir.mkdir('osbs').mkdir('repo')
 
-    tmpdir.join('osbs').join('repo').join(
-        'fetch-artifacts-url.yaml').write_text(u'Some content', 'utf8')
-
     with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
         subprocess.call(["git", "init"])
         subprocess.call(["git", "add", "fetch-artifacts-url.yaml"])
@@ -1126,9 +1100,6 @@ def test_osbs_builder_with_fetch_artifacts_url_validate_dockerfile(tmpdir, mocke
 
     tmpdir.mkdir('osbs').mkdir('repo')
 
-    tmpdir.join('osbs').join('repo').join(
-        'fetch-artifacts-url.yaml').write_text(u'Some content', 'utf8')
-
     with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
         subprocess.call(["git", "init"])
         subprocess.call(["git", "add", "fetch-artifacts-url.yaml"])
@@ -1156,7 +1127,7 @@ def test_osbs_builder_with_fetch_artifacts_url_validate_dockerfile(tmpdir, mocke
     assert "Artifact 'myfile.jar' (as URL) added to fetch-artifacts-url.yaml" in caplog.text
 
 
-def test_osbs_builder_with_fetch_artifacts_file_removal(tmpdir, mocker, caplog):
+def test_osbs_builder_with_fetch_artifacts_url_file_removal(tmpdir, mocker, caplog):
     """
     Checks whether the fetch-artifacts-url.yaml file is removed if exists
     and is not used anymore.
@@ -1188,6 +1159,36 @@ def test_osbs_builder_with_fetch_artifacts_file_removal(tmpdir, mocker, caplog):
     assert "Removing old 'fetch-artifacts-url.yaml' file" in caplog.text
 
 
+def test_osbs_builder_with_fetch_artifacts_pnc_file_removal(tmpdir, mocker, caplog):
+    """
+    Checks whether the fetch-artifacts-pnc.yaml file is removed if exists
+    and is not used anymore.
+    """
+
+    caplog.set_level(logging.DEBUG, logger="cekit")
+
+    mocker.patch('cekit.tools.decision', return_value=True)
+    mocker.patch('cekit.descriptor.resource.urlopen')
+    mocker.patch('cekit.generator.osbs.get_brew_url', return_value='http://random.url/path')
+    mocker.patch.object(subprocess, 'check_output')
+    mocker.patch('cekit.builders.osbs.DistGit.push')
+
+    tmpdir.mkdir('osbs').mkdir('repo')
+
+    tmpdir.join('osbs').join('repo').join(
+        'fetch-artifacts-pnc.yaml').write_text(u'Some content', 'utf8')
+
+    with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
+        subprocess.call(["git", "init"])
+        subprocess.call(["git", "add", "fetch-artifacts-pnc.yaml"])
+        subprocess.call(["git", "commit", "-m", "Dummy"])
+
+    run_osbs(image_descriptor, str(tmpdir), mocker)
+
+    assert not os.path.exists(os.path.join(str(tmpdir), 'osbs', 'repo', 'fetch-artifacts-pnc.yaml'))
+    assert "Removing old 'fetch-artifacts-pnc.yaml' file" in caplog.text
+
+
 @pytest.mark.parametrize('flag', [[], ['--redhat']])
 def test_osbs_builder_container_yaml_existence(tmpdir, mocker, caplog, flag):
     """
@@ -1596,3 +1597,87 @@ remote_source:
   - gomod
   ref: db4a5d18f5f52a64083d8f1bd1776ad60a46904c
   repo: https://github.com/kiegroup/rhpam-kogito-operator""" in containerfile
+
+
+def test_osbs_builder_with_fetch_artifacts_pnc_file_creation_1(tmpdir, mocker, caplog):
+    """
+    Checks whether the fetch-artifacts-pnc.yaml file is generated with correct artifact ids.
+    """
+
+    caplog.set_level(logging.DEBUG, logger="cekit")
+
+    mocker.patch('cekit.tools.decision', return_value=True)
+    mocker.patch('cekit.descriptor.resource.urlopen')
+    mocker.patch.object(subprocess, 'check_output')
+    mocker.patch('cekit.builders.osbs.DistGit.push')
+
+    tmpdir.mkdir('osbs').mkdir('repo')
+
+    with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
+        subprocess.call(["git", "init"])
+        subprocess.call(["git", "add", "fetch-artifacts-pnc.yaml"])
+        subprocess.call(["git", "commit", "-m", "Dummy"])
+
+    descriptor = image_descriptor.copy()
+
+    descriptor['artifacts'] = [
+        {'target': 'foo.jar', 'pnc_build_id': '123456', 'pnc_artifact_id': '54321'}
+    ]
+
+    run_osbs(descriptor, str(tmpdir), mocker)
+
+    with open(os.path.join(str(tmpdir), 'target', 'image', 'fetch-artifacts-pnc.yaml'), 'r') as _file:
+        fetch_artifacts = yaml.safe_load(_file)
+
+    assert fetch_artifacts['builds'] == [{'build_id': '123456', 'artifacts': [{'id': '54321', 'target': '/tmp/artifacts/foo.jar'}]}]
+
+
+def test_osbs_builder_with_fetch_artifacts_pnc_file_creation_2(tmpdir, mocker, caplog):
+    """
+    Checks whether the fetch-artifacts-pnc.yaml file is generated with correct artifact ids.
+    """
+
+    caplog.set_level(logging.DEBUG, logger="cekit")
+
+    mocker.patch('cekit.tools.decision', return_value=True)
+    mocker.patch('cekit.descriptor.resource.urlopen')
+    mocker.patch.object(subprocess, 'check_output')
+    mocker.patch('cekit.builders.osbs.DistGit.push')
+
+    tmpdir.mkdir('osbs').mkdir('repo')
+
+    with Chdir(os.path.join(str(tmpdir), 'osbs', 'repo')):
+        subprocess.call(["git", "init"])
+        subprocess.call(["git", "add", "fetch-artifacts-pnc.yaml"])
+        subprocess.call(["git", "commit", "-m", "Dummy"])
+
+    descriptor = image_descriptor.copy()
+
+    descriptor['artifacts'] = [
+        {'target': 'boo.jar', 'pnc_build_id': '123456', 'pnc_artifact_id': '00001'},
+        {'target': 'foo.jar', 'pnc_build_id': '123456', 'pnc_artifact_id': '54321', 'url': 'http://www.dummy.com/build/artifact.jar'},
+        {'target': 'goo.zip', 'pnc_build_id': '987654', 'pnc_artifact_id': '00002'}
+    ]
+
+    run_osbs(descriptor, str(tmpdir), mocker)
+
+    with open(os.path.join(str(tmpdir), 'target', 'image', 'fetch-artifacts-pnc.yaml'), 'r') as _file:
+        fetch_artifacts = _file.read()
+    with open(os.path.join(str(tmpdir), 'target', 'image', 'fetch-artifacts-pnc.yaml'), 'r') as _file:
+        fetch_artifacts_yaml = yaml.safe_load(_file)
+    print("Read fetch_pnc_artifacts {}\n".format(fetch_artifacts))
+    assert "# Created by CEKit version" in fetch_artifacts
+    assert fetch_artifacts_yaml['builds'] == [{'build_id': '123456', 'artifacts': [{'id': '00001', 'target': '/tmp/artifacts/boo.jar'}, {'id': '54321', 'target': '/tmp/artifacts/foo.jar'}]}, {'build_id': '987654', 'artifacts': [{'id': '00002', 'target': '/tmp/artifacts/goo.zip'}]}]
+    # Skip the following test under Python 2.7 as there can be ordering differences.
+    if sys.version_info >= (3, 7):
+        assert """builds:
+- build_id: '123456'
+  artifacts:
+  - id: '00001'
+    target: /tmp/artifacts/boo.jar
+  - id: '54321' # http://www.dummy.com/build/artifact.jar
+    target: /tmp/artifacts/foo.jar
+- build_id: '987654'
+  artifacts:
+  - id: '00002'
+    target: /tmp/artifacts/goo.zip""" in fetch_artifacts
diff --git a/tests/test_unit_descriptor.py b/tests/test_unit_descriptor.py
index 64a69c1..f88986d 100644
--- a/tests/test_unit_descriptor.py
+++ b/tests/test_unit_descriptor.py
@@ -249,3 +249,38 @@ def test_image_descriptor_with_execute(caplog):
         """), 'foo')
 
     assert "Cannot validate schema: Image" in excinfo.value.message
+
+
+def test_image_pnc_artifacts_1(caplog):
+    image = Image(yaml.safe_load("""
+            from: foo
+            name: test/foo
+            version: 1.9
+            artifacts:
+              - name: foobar
+                target: jolokia.jar
+                pnc_artifact_id: '12345'
+                pnc_build_id: '54321'
+            """), 'foo')
+    assert image.artifacts[0]['name'] == 'foobar'
+    assert image.artifacts[0]['pnc_build_id'] == '54321'
+    assert image.artifacts[0]['target'] == 'jolokia.jar'
+    assert image.artifacts[0]['dest'] == '/tmp/artifacts/'
+
+
+def test_image_pnc_artifacts_2(caplog):
+    image = Image(yaml.safe_load("""
+            from: foo
+            name: test/foo
+            version: 1.9
+            artifacts:
+              - target: jolokia.jar
+                dest: '/installation/'
+                pnc_artifact_id: '12345'
+                pnc_build_id: '54321'
+            """), 'foo')
+    print("Got {}".format(image.artifacts[0]))
+    assert image.artifacts[0]['name'] == 'jolokia.jar'
+    assert image.artifacts[0]['pnc_build_id'] == '54321'
+    assert image.artifacts[0]['target'] == 'jolokia.jar'
+    assert image.artifacts[0]['dest'] == '/installation/'
diff --git a/tests/test_unit_resource.py b/tests/test_unit_resource.py
index 1f290ec..8edebc3 100644
--- a/tests/test_unit_resource.py
+++ b/tests/test_unit_resource.py
@@ -340,7 +340,7 @@ def test_copy_plain_resource_from_brew(mocker, tmpdir):
     urlopen_class_mock.assert_called_with('http://cache/abc', context=ctx)
 
 
-def test_overide_resource_remove_chksum():
+def test_override_resource_remove_chksum():
     image = Image(yaml.safe_load("""
     from: foo
     name: test/foo