diff --git a/src/ploigos_step_runner/step_implementers/create_container_image/__init__.py b/src/ploigos_step_runner/step_implementers/create_container_image/__init__.py
index bd9a4515f..b3a18b169 100644
--- a/src/ploigos_step_runner/step_implementers/create_container_image/__init__.py
+++ b/src/ploigos_step_runner/step_implementers/create_container_image/__init__.py
@@ -5,3 +5,5 @@
Buildah
from ploigos_step_runner.step_implementers.create_container_image.maven_jkube_k8sbuild import \
MavenJKubeK8sBuild
+from ploigos_step_runner.step_implementers.create_container_image.source_to_image import \
+ SourceToImage
diff --git a/src/ploigos_step_runner/step_implementers/create_container_image/source_to_image.py b/src/ploigos_step_runner/step_implementers/create_container_image/source_to_image.py
new file mode 100644
index 000000000..d8f719961
--- /dev/null
+++ b/src/ploigos_step_runner/step_implementers/create_container_image/source_to_image.py
@@ -0,0 +1,231 @@
+"""`StepImplementer` for the `create-container-image step` using Source-to-Image (s2i)
+to create a Containerfile that can be built by another `StepImplementer`, such as Buildah.
+
+Step Configuration
+------------------
+Step configuration expected as input to this step.
+Could come from:
+
+ * static configuration
+ * runtime configuration
+ * previous step results
+
+Configuration Key | Required? | Default | Description
+------------------------------|-----------|---------|-----------
+`s2i-builder-image` | True | | Container image tag to use as the s2i builder image.
+`s2i-image-scripts-url` | False | | Location of the s2i scripts in the given `s2i-builder-image`. \
+ If not given s2i will assume they are in `image:///usr/libexec/s2i`. \
+
\
+ EX:
\
+ s2i-builder-image: registry.redhat.io/redhat-openjdk-18/openjdk18-openshift
\
+ s2i-image-scripts-url: `image:///usr/local/s2i`
+`s2i-loglevel` | False | 1 | s2i log level to specify. See s2i docs.
+`s2i-additional-arguments` | False | `[]` | List of additional arguments to append to s2i call.
+`context` | True | `'.'` | Context to build the container image in
+`tls-verify` | True | `True` | Whether to verify TLS when pulling parent images
+`containers-config-auth-file` | False | | Path to the container registry authentication \
+ file to use for container registry authentication. \
+ If one is not provided one will be created in the \
+ working directory.
+`container-registries` | False | | Hash of container registries to authenticate with.
+
+
+Result Artifacts
+----------------
+Results artifacts output by this step.
+
+Result Artifact Key | Description
+----------------------------------------|------------
+`container-image-registry-uri` | Registry URI poriton of the container image tag of the built container image.
+`container-image-registry-organization` | Organization portion of the container image tag of the built container image.
+`container-image-repository` | Repository portion of the container image tag of the built container image.
+`container-image-name` | Another way to reference the repository portion of the container image tag \
+ of the built container image.
+`container-image-version` | Version portion of the container image tag of the built container image.
+`container-image-tag` | Full container image tag of the built container, including the registry URI.
\
+ Takes the form of: \
+ `container-image-registry-organization/container-image-repository:container-image-version`
+`container-image-short-tag` | Short container image tag of the built container image, excluding the registry URI.
\
+ Takes the form of: \
+ `container-image-registry-uri/container-image-registry-organization/container-image-repository:container-image-version`
+
+"""# pylint: disable=line-too-long
+
+import os
+import sys
+from distutils import util
+
+import sh
+from ploigos_step_runner import StepImplementer, StepResult
+from ploigos_step_runner.utils.containers import (container_registries_login,
+ inspect_container_image)
+
+DEFAULT_CONFIG = {
+ 'context': '.',
+ 'tls-verify': True,
+ 's2i-additional-arguments': [],
+ 's2i-loglevel': 1
+}
+
+REQUIRED_CONFIG_OR_PREVIOUS_STEP_RESULT_ARTIFACT_KEYS = [
+ 'context',
+ 'tls-verify',
+ 's2i-builder-image',
+ 'organization',
+ 'service-name',
+ 'application-name'
+]
+
+class SourceToImage(StepImplementer):
+ """`StepImplementer` for the `create-container-image step` using Source-to-Image (s2i)
+ to create a Containerfile that can be built by another `StepImplementer`, such as Buildah.
+ """
+
+ CONTAINER_LABEL_SCRIPTS_URL = 'io.openshift.s2i.scripts-url'
+
+ @staticmethod
+ def step_implementer_config_defaults():
+ """Getter for the StepImplementer's configuration defaults.
+
+ Notes
+ -----
+ These are the lowest precedence configuration values.
+
+ Returns
+ -------
+ dict
+ Default values to use for step configuration values.
+ """
+ return DEFAULT_CONFIG
+
+ @staticmethod
+ def _required_config_or_result_keys():
+ """Getter for step configuration or previous step result artifacts that are required before
+ running this step.
+
+ See Also
+ --------
+ _validate_required_config_or_previous_step_result_artifact_keys
+
+ Returns
+ -------
+ array_list
+ Array of configuration keys or previous step result artifacts
+ that are required before running the step.
+ """
+ return REQUIRED_CONFIG_OR_PREVIOUS_STEP_RESULT_ARTIFACT_KEYS
+
+ def _run_step(self):
+ """Runs the step implemented by this StepImplementer.
+
+ Returns
+ -------
+ StepResult
+ Object containing the dictionary results of this step.
+ """
+ step_result = StepResult.from_step_implementer(self)
+
+ # get values
+ builder_image = self.get_value('s2i-builder-image')
+
+ # determine tls flag
+ tls_verify = self.get_value('tls-verify')
+ if isinstance(tls_verify, str):
+ tls_verify = bool(util.strtobool(tls_verify))
+ if tls_verify:
+ s2i_tls_flags = ['--tlsverify']
+ else:
+ s2i_tls_flags = []
+
+ # determine the generated imagespec file
+ s2i_working_dir = self.create_working_dir_sub_dir('s2i-context')
+ imagespecfile = self.write_working_file(
+ os.path.join(s2i_working_dir, 'Containerfile.s2i-gen')
+ )
+
+ # determine image scripts url flags
+ # use user provided url if given,
+ # else try and inspect from builder image
+ s2i_image_scripts_url = self.get_value('s2i-image-scripts-url')
+ if not s2i_image_scripts_url:
+ print('Attempt to inspect builder image for label for image scripts url')
+
+ # attempt to auth with container image registries
+ # login to any provider container registries
+ # NOTE: important to specify the auth file because depending on the context this is
+ # being run in python process may not have permissions to default location
+ containers_config_auth_file = self.get_value('containers-config-auth-file')
+ if not containers_config_auth_file:
+ containers_config_auth_file = os.path.join(
+ self.work_dir_path,
+ 'container-auth.json'
+ )
+ try:
+ container_registries_login(
+ registries=self.get_value('container-registries'),
+ containers_config_auth_file=containers_config_auth_file,
+ containers_config_tls_verify=tls_verify
+ )
+ except RuntimeError as error:
+ step_result.message += "WARNING: error authenticating with" \
+ " container image registries to be able to pull s2i builder image" \
+ f" to inspect for image scripts url: {error}\n"
+
+ # if not given, attempt to get from builder image labels
+ try:
+ container_image_details = inspect_container_image(
+ container_image_uri=builder_image,
+ containers_config_auth_file=containers_config_auth_file
+ )
+
+ s2i_image_scripts_url = container_image_details['OCIv1']['config']['Labels']\
+ [SourceToImage.CONTAINER_LABEL_SCRIPTS_URL]
+ except RuntimeError as error:
+ step_result.message += "WARNING: failed to inspect s2i builder image" \
+ f" ({builder_image}) to dynamically determine image scripts url." \
+ f" S2I default will be used: {error}\n"
+ except KeyError as error:
+ step_result.message += "WARNING: failed to find s2i scripts url label" \
+ f" ({SourceToImage.CONTAINER_LABEL_SCRIPTS_URL}) on s2i builder image" \
+ f" ({builder_image}) to dynamically determine image scripts url." \
+ f" S2I default will be used: Could not find key ({error}).\n"
+
+ # if determined image scripts url set the flag
+ # else s2i will use its default (image:///usr/libexec/s2i)
+ if s2i_image_scripts_url:
+ s2i_image_scripts_url_flags = ['--image-scripts-url', s2i_image_scripts_url]
+ else:
+ s2i_image_scripts_url_flags = []
+
+ try:
+ # perform build
+ print('Use s2i to generate imagespecfile and accompanying resources')
+ sh.s2i.build( # pylint: disable=no-member
+ self.get_value('context'),
+ builder_image,
+ '--loglevel', self.get_value('s2i-loglevel'),
+ *s2i_tls_flags,
+ '--as-dockerfile', imagespecfile,
+ *s2i_image_scripts_url_flags,
+ *self.get_value('s2i-additional-arguments'),
+ _out=sys.stdout,
+ _err=sys.stderr,
+ _tee='err'
+ )
+ except sh.ErrorReturnCode as error: # pylint: disable=undefined-variable
+ step_result.success = False
+ step_result.message += f'Issue invoking s2i build: {error}'
+
+ # add artifacts
+ step_result.add_artifact(
+ name='imagespecfile',
+ value=imagespecfile,
+ description='File defining the container image to build generated by s2i'
+ )
+ step_result.add_artifact(
+ name='context',
+ value=s2i_working_dir,
+ description='Context to use when building the imagespecfile generated by S2I.'
+ )
+
+ return step_result
diff --git a/src/ploigos_step_runner/utils/containers.py b/src/ploigos_step_runner/utils/containers.py
index e42560d49..10e15378e 100644
--- a/src/ploigos_step_runner/utils/containers.py
+++ b/src/ploigos_step_runner/utils/containers.py
@@ -1,6 +1,7 @@
"""Shared utils for dealing with containers.
"""
+import json
import sys
from io import StringIO
@@ -415,3 +416,63 @@ def determine_container_image_build_tag_info(
build_full_tag = f"{image_registry_uri}/{build_short_tag}"
return build_full_tag, build_short_tag, image_registry_uri, image_repository, image_version
+
+def inspect_container_image(
+ container_image_uri,
+ containers_config_auth_file=None
+):
+ """Inspects a given container image for all its details. Useful for getting image labels
+ and such.
+
+ Parameters
+ ----------
+ container_image_uri : str
+ URI to the container image to inspect
+ containers_config_auth_file : str
+ Path to container image registries authentication file.
+
+ Raises
+ ------
+ RuntimeError
+ If issue running `buildah inspect`
+
+ Returns
+ -------
+ dict
+ Container image details from `buildah inspect`
+ """
+ buildah_inspect = None
+
+ # determine auth flags
+ if containers_config_auth_file:
+ buildah_authfile_flags = ['--authfile', containers_config_auth_file]
+ else:
+ buildah_authfile_flags = []
+
+ # pull container image (can't inspect remote image)
+ try:
+ sh.buildah.pull( # pylint: disable=no-member
+ *buildah_authfile_flags,
+ container_image_uri
+ )
+ except sh.ErrorReturnCode as error: # pylint: disable=undefined-variable
+ raise RuntimeError(
+ f"Error pulling container image ({container_image_uri}) for inspection: {error}"
+ ) from error
+
+ # get container image information
+ try:
+
+ buildah_inspect_out_buff = StringIO()
+ sh.buildah.inspect( # pylint: disable=no-member
+ container_image_uri,
+ _out=buildah_inspect_out_buff
+ )
+ buildah_inspect_out = buildah_inspect_out_buff.getvalue().rstrip()
+ buildah_inspect = json.loads(buildah_inspect_out)
+ except sh.ErrorReturnCode as error: # pylint: disable=undefined-variable
+ raise RuntimeError(
+ f"Error inspecting container image ({container_image_uri}): {error}"
+ ) from error
+
+ return buildah_inspect
diff --git a/tests/step_implementers/create_container_image/test_source_to_image.py b/tests/step_implementers/create_container_image/test_source_to_image.py
new file mode 100644
index 000000000..82281bc8f
--- /dev/null
+++ b/tests/step_implementers/create_container_image/test_source_to_image.py
@@ -0,0 +1,657 @@
+from unittest.mock import ANY, patch
+
+import sh
+from ploigos_step_runner import StepResult
+from ploigos_step_runner.step_implementers.create_container_image import \
+ SourceToImage
+from tests.helpers.base_step_implementer_test_case import \
+ BaseStepImplementerTestCase
+
+
+class TestStepImplementerSourceToImage_step_implementer_config_defaults(
+ BaseStepImplementerTestCase
+):
+ def test_result(self):
+ self.assertEqual(
+ SourceToImage.step_implementer_config_defaults(),
+ {
+ 'context': '.',
+ 'tls-verify': True,
+ 's2i-additional-arguments': [],
+ 's2i-loglevel': 1
+ }
+ )
+
+class TestStepImplementerSourceToImage__required_config_or_result_keys(
+ BaseStepImplementerTestCase
+):
+ def test_result(self):
+ self.assertEqual(
+ SourceToImage._required_config_or_result_keys(),
+ [
+ 'context',
+ 'tls-verify',
+ 's2i-builder-image',
+ 'organization',
+ 'service-name',
+ 'application-name'
+ ]
+ )
+
+@patch('sh.s2i', create=True)
+@patch('ploigos_step_runner.step_implementers.create_container_image.source_to_image.inspect_container_image')
+@patch('ploigos_step_runner.step_implementers.create_container_image.source_to_image.container_registries_login')
+@patch.object(
+ SourceToImage,
+ 'write_working_file',
+ return_value='/mock/working/s2i-context/Containerfile.s2i-gen'
+)
+@patch.object(
+ SourceToImage,
+ 'create_working_dir_sub_dir',
+ return_value='/mock/working/s2i-context'
+)
+class TestStepImplementerSourceToImage__run_step(
+ BaseStepImplementerTestCase
+):
+ def create_step_implementer(
+ self,
+ step_config={},
+ workflow_result=None,
+ parent_work_dir_path=''
+ ):
+ return self.create_given_step_implementer(
+ step_implementer=SourceToImage,
+ step_config=step_config,
+ step_name='create-container-image',
+ implementer='SourceToImage',
+ workflow_result=workflow_result,
+ parent_work_dir_path=parent_work_dir_path
+ )
+
+ def test_success_builder_image_with_scripts_url_label(
+ self,
+ mock_create_working_dir_sub_dir,
+ mock_write_working_file,
+ mock_container_registries_login,
+ mock_inspect_container_image,
+ mock_s2i
+ ):
+ # setup
+ step_config = {
+ 's2i-builder-image': 'mock.io/awesome-image:v42'
+ }
+ step_implementer = self.create_step_implementer(
+ step_config=step_config
+ )
+
+ # setup mocks
+ mock_inspect_container_image.return_value = {
+ 'OCIv1': {
+ 'config': {
+ 'Labels': {
+ 'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
+ }
+ }
+ }
+ }
+
+ # run test
+ actual_step_result= step_implementer._run_step()
+
+ # validate
+ expected_step_result = StepResult(
+ step_name='create-container-image',
+ sub_step_name='SourceToImage',
+ sub_step_implementer_name='SourceToImage'
+ )
+ expected_step_result.add_artifact(
+ name='imagespecfile',
+ value='/mock/working/s2i-context/Containerfile.s2i-gen',
+ description='File defining the container image to build generated by s2i'
+ )
+ expected_step_result.add_artifact(
+ name='context',
+ value='/mock/working/s2i-context',
+ description='Context to use when building the imagespecfile generated by S2I.'
+ )
+ self.assertEqual(actual_step_result, expected_step_result)
+
+ mock_inspect_container_image.assert_called_once_with(
+ container_image_uri='mock.io/awesome-image:v42',
+ containers_config_auth_file='create-container-image/container-auth.json'
+ )
+ mock_container_registries_login.assert_called_once_with(
+ registries=None,
+ containers_config_auth_file='create-container-image/container-auth.json',
+ containers_config_tls_verify=True
+ )
+ mock_s2i.build.assert_called_once_with(
+ '.',
+ 'mock.io/awesome-image:v42',
+ '--loglevel', 1,
+ '--tlsverify',
+ '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
+ '--image-scripts-url', 'image:///moock-image-label/s2i',
+ _out=ANY,
+ _err=ANY,
+ _tee='err'
+ )
+
+ def test_success_given_containers_config_auth_file(
+ self,
+ mock_create_working_dir_sub_dir,
+ mock_write_working_file,
+ mock_container_registries_login,
+ mock_inspect_container_image,
+ mock_s2i
+ ):
+ # setup
+ step_config = {
+ 's2i-builder-image': 'mock.io/awesome-image:v42',
+ 'containers-config-auth-file': '/mock/mock-regs-auths.json'
+ }
+ step_implementer = self.create_step_implementer(
+ step_config=step_config
+ )
+
+ # setup mocks
+ mock_inspect_container_image.return_value = {
+ 'OCIv1': {
+ 'config': {
+ 'Labels': {
+ 'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
+ }
+ }
+ }
+ }
+
+ # run test
+ actual_step_result= step_implementer._run_step()
+
+ # validate
+ expected_step_result = StepResult(
+ step_name='create-container-image',
+ sub_step_name='SourceToImage',
+ sub_step_implementer_name='SourceToImage'
+ )
+ expected_step_result.add_artifact(
+ name='imagespecfile',
+ value='/mock/working/s2i-context/Containerfile.s2i-gen',
+ description='File defining the container image to build generated by s2i'
+ )
+ expected_step_result.add_artifact(
+ name='context',
+ value='/mock/working/s2i-context',
+ description='Context to use when building the imagespecfile generated by S2I.'
+ )
+ self.assertEqual(actual_step_result, expected_step_result)
+
+ mock_inspect_container_image.assert_called_once_with(
+ container_image_uri='mock.io/awesome-image:v42',
+ containers_config_auth_file='/mock/mock-regs-auths.json'
+ )
+ mock_container_registries_login.assert_called_once_with(
+ registries=None,
+ containers_config_auth_file='/mock/mock-regs-auths.json',
+ containers_config_tls_verify=True
+ )
+ mock_s2i.build.assert_called_once_with(
+ '.',
+ 'mock.io/awesome-image:v42',
+ '--loglevel', 1,
+ '--tlsverify',
+ '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
+ '--image-scripts-url', 'image:///moock-image-label/s2i',
+ _out=ANY,
+ _err=ANY,
+ _tee='err'
+ )
+
+ def test_success_error_inspecting_builder_image_to_get_scripts_url(
+ self,
+ mock_create_working_dir_sub_dir,
+ mock_write_working_file,
+ mock_container_registries_login,
+ mock_inspect_container_image,
+ mock_s2i
+ ):
+ # setup
+ step_config = {
+ 's2i-builder-image': 'mock.io/awesome-image:v42'
+ }
+ step_implementer = self.create_step_implementer(
+ step_config=step_config
+ )
+
+ # setup mocks
+ mock_inspect_container_image.side_effect = RuntimeError('mock buildah inspect error')
+
+ # run test
+ actual_step_result= step_implementer._run_step()
+
+ # validate
+ expected_step_result = StepResult(
+ step_name='create-container-image',
+ sub_step_name='SourceToImage',
+ sub_step_implementer_name='SourceToImage'
+ )
+ expected_step_result.message = "WARNING: failed to inspect s2i builder image" \
+ " (mock.io/awesome-image:v42) to dynamically determine image scripts url." \
+ " S2I default will be used: mock buildah inspect error\n"
+ expected_step_result.add_artifact(
+ name='imagespecfile',
+ value='/mock/working/s2i-context/Containerfile.s2i-gen',
+ description='File defining the container image to build generated by s2i'
+ )
+ expected_step_result.add_artifact(
+ name='context',
+ value='/mock/working/s2i-context',
+ description='Context to use when building the imagespecfile generated by S2I.'
+ )
+ self.assertEqual(actual_step_result, expected_step_result)
+
+ mock_inspect_container_image.assert_called_once_with(
+ container_image_uri='mock.io/awesome-image:v42',
+ containers_config_auth_file='create-container-image/container-auth.json'
+ )
+ mock_s2i.build.assert_called_once_with(
+ '.',
+ 'mock.io/awesome-image:v42',
+ '--loglevel', 1,
+ '--tlsverify',
+ '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
+ _out=ANY,
+ _err=ANY,
+ _tee='err'
+ )
+
+ def test_success_error_finding_label_on_build_image_inspection_details(
+ self,
+ mock_create_working_dir_sub_dir,
+ mock_write_working_file,
+ mock_container_registries_login,
+ mock_inspect_container_image,
+ mock_s2i
+ ):
+ # setup
+ step_config = {
+ 's2i-builder-image': 'mock.io/awesome-image:v42'
+ }
+ step_implementer = self.create_step_implementer(
+ step_config=step_config
+ )
+
+ # setup mocks
+ mock_inspect_container_image.return_value = {}
+
+ # run test
+ actual_step_result= step_implementer._run_step()
+
+ # validate
+ expected_step_result = StepResult(
+ step_name='create-container-image',
+ sub_step_name='SourceToImage',
+ sub_step_implementer_name='SourceToImage'
+ )
+ expected_step_result.message = "WARNING: failed to find s2i scripts url label" \
+ " (io.openshift.s2i.scripts-url) on s2i builder image" \
+ " (mock.io/awesome-image:v42) to dynamically determine image scripts url." \
+ " S2I default will be used: Could not find key ('OCIv1').\n"
+ expected_step_result.add_artifact(
+ name='imagespecfile',
+ value='/mock/working/s2i-context/Containerfile.s2i-gen',
+ description='File defining the container image to build generated by s2i'
+ )
+ expected_step_result.add_artifact(
+ name='context',
+ value='/mock/working/s2i-context',
+ description='Context to use when building the imagespecfile generated by S2I.'
+ )
+ self.assertEqual(actual_step_result, expected_step_result)
+
+ mock_inspect_container_image.assert_called_once_with(
+ container_image_uri='mock.io/awesome-image:v42',
+ containers_config_auth_file='create-container-image/container-auth.json'
+ )
+ mock_s2i.build.assert_called_once_with(
+ '.',
+ 'mock.io/awesome-image:v42',
+ '--loglevel', 1,
+ '--tlsverify',
+ '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
+ _out=ANY,
+ _err=ANY,
+ _tee='err'
+ )
+
+ def test_success_no_tls_verify_bool(
+ self,
+ mock_create_working_dir_sub_dir,
+ mock_write_working_file,
+ mock_container_registries_login,
+ mock_inspect_container_image,
+ mock_s2i
+ ):
+ # setup
+ step_config = {
+ 's2i-builder-image': 'mock.io/awesome-image:v42',
+ 'tls-verify': False
+ }
+ step_implementer = self.create_step_implementer(
+ step_config=step_config
+ )
+
+ # setup mocks
+ mock_inspect_container_image.return_value = {
+ 'OCIv1': {
+ 'config': {
+ 'Labels': {
+ 'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
+ }
+ }
+ }
+ }
+
+ # run test
+ actual_step_result= step_implementer._run_step()
+
+ # validate
+ expected_step_result = StepResult(
+ step_name='create-container-image',
+ sub_step_name='SourceToImage',
+ sub_step_implementer_name='SourceToImage'
+ )
+ expected_step_result.add_artifact(
+ name='imagespecfile',
+ value='/mock/working/s2i-context/Containerfile.s2i-gen',
+ description='File defining the container image to build generated by s2i'
+ )
+ expected_step_result.add_artifact(
+ name='context',
+ value='/mock/working/s2i-context',
+ description='Context to use when building the imagespecfile generated by S2I.'
+ )
+ self.assertEqual(actual_step_result, expected_step_result)
+
+ mock_inspect_container_image.assert_called_once_with(
+ container_image_uri='mock.io/awesome-image:v42',
+ containers_config_auth_file='create-container-image/container-auth.json'
+ )
+ mock_s2i.build.assert_called_once_with(
+ '.',
+ 'mock.io/awesome-image:v42',
+ '--loglevel', 1,
+ '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
+ '--image-scripts-url', 'image:///moock-image-label/s2i',
+ _out=ANY,
+ _err=ANY,
+ _tee='err'
+ )
+
+ def test_success_no_tls_verify_str(
+ self,
+ mock_create_working_dir_sub_dir,
+ mock_write_working_file,
+ mock_container_registries_login,
+ mock_inspect_container_image,
+ mock_s2i
+ ):
+ # setup
+ step_config = {
+ 's2i-builder-image': 'mock.io/awesome-image:v42',
+ 'tls-verify': 'false'
+ }
+ step_implementer = self.create_step_implementer(
+ step_config=step_config
+ )
+
+ # setup mocks
+ mock_inspect_container_image.return_value = {
+ 'OCIv1': {
+ 'config': {
+ 'Labels': {
+ 'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
+ }
+ }
+ }
+ }
+
+ # run test
+ actual_step_result= step_implementer._run_step()
+
+ # validate
+ expected_step_result = StepResult(
+ step_name='create-container-image',
+ sub_step_name='SourceToImage',
+ sub_step_implementer_name='SourceToImage'
+ )
+ expected_step_result.add_artifact(
+ name='imagespecfile',
+ value='/mock/working/s2i-context/Containerfile.s2i-gen',
+ description='File defining the container image to build generated by s2i'
+ )
+ expected_step_result.add_artifact(
+ name='context',
+ value='/mock/working/s2i-context',
+ description='Context to use when building the imagespecfile generated by S2I.'
+ )
+ self.assertEqual(actual_step_result, expected_step_result)
+
+ mock_inspect_container_image.assert_called_once_with(
+ container_image_uri='mock.io/awesome-image:v42',
+ containers_config_auth_file='create-container-image/container-auth.json'
+ )
+ mock_s2i.build.assert_called_once_with(
+ '.',
+ 'mock.io/awesome-image:v42',
+ '--loglevel', 1,
+ '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
+ '--image-scripts-url', 'image:///moock-image-label/s2i',
+ _out=ANY,
+ _err=ANY,
+ _tee='err'
+ )
+
+ def test_success_given_image_scripts_url(
+ self,
+ mock_create_working_dir_sub_dir,
+ mock_write_working_file,
+ mock_container_registries_login,
+ mock_inspect_container_image,
+ mock_s2i
+ ):
+ # setup
+ step_config = {
+ 's2i-builder-image': 'mock.io/awesome-image:v42',
+ 's2i-image-scripts-url': 'image:///mock-user-given/s2i'
+ }
+ step_implementer = self.create_step_implementer(
+ step_config=step_config
+ )
+
+ # setup mocks
+ mock_inspect_container_image.return_value = {
+ 'OCIv1': {
+ 'config': {
+ 'Labels': {
+ 'io.openshift.s2i.scripts-url': 'image:///moock/s2i'
+ }
+ }
+ }
+ }
+
+ # run test
+ actual_step_result= step_implementer._run_step()
+
+ # validate
+ expected_step_result = StepResult(
+ step_name='create-container-image',
+ sub_step_name='SourceToImage',
+ sub_step_implementer_name='SourceToImage'
+ )
+ expected_step_result.add_artifact(
+ name='imagespecfile',
+ value='/mock/working/s2i-context/Containerfile.s2i-gen',
+ description='File defining the container image to build generated by s2i'
+ )
+ expected_step_result.add_artifact(
+ name='context',
+ value='/mock/working/s2i-context',
+ description='Context to use when building the imagespecfile generated by S2I.'
+ )
+ self.assertEqual(actual_step_result, expected_step_result)
+
+ mock_inspect_container_image.assert_not_called()
+ mock_s2i.build.assert_called_once_with(
+ '.',
+ 'mock.io/awesome-image:v42',
+ '--loglevel', 1,
+ '--tlsverify',
+ '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
+ '--image-scripts-url', 'image:///mock-user-given/s2i',
+ _out=ANY,
+ _err=ANY,
+ _tee='err'
+ )
+
+ def test_fail_builder_image_with_scripts_url_label(
+ self,
+ mock_create_working_dir_sub_dir,
+ mock_write_working_file,
+ mock_container_registries_login,
+ mock_inspect_container_image,
+ mock_s2i
+ ):
+ # setup
+ step_config = {
+ 's2i-builder-image': 'mock.io/awesome-image:v42'
+ }
+ step_implementer = self.create_step_implementer(
+ step_config=step_config
+ )
+
+ # setup mocks
+ mock_s2i.build.side_effect = sh.ErrorReturnCode('s2i', b'mock out', b'mock err')
+ mock_inspect_container_image.return_value = {
+ 'OCIv1': {
+ 'config': {
+ 'Labels': {
+ 'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
+ }
+ }
+ }
+ }
+
+ # run test
+ actual_step_result= step_implementer._run_step()
+
+ # validate
+ expected_step_result = StepResult(
+ step_name='create-container-image',
+ sub_step_name='SourceToImage',
+ sub_step_implementer_name='SourceToImage'
+ )
+ expected_step_result.success = False
+ expected_step_result.message = "Issue invoking s2i build:" \
+ " \n\n RAN: s2i\n\n STDOUT:\nmock out\n\n STDERR:\nmock err"
+ expected_step_result.add_artifact(
+ name='imagespecfile',
+ value='/mock/working/s2i-context/Containerfile.s2i-gen',
+ description='File defining the container image to build generated by s2i'
+ )
+ expected_step_result.add_artifact(
+ name='context',
+ value='/mock/working/s2i-context',
+ description='Context to use when building the imagespecfile generated by S2I.'
+ )
+ self.assertEqual(actual_step_result, expected_step_result)
+
+ mock_inspect_container_image.assert_called_once_with(
+ container_image_uri='mock.io/awesome-image:v42',
+ containers_config_auth_file='create-container-image/container-auth.json'
+ )
+ mock_s2i.build.assert_called_once_with(
+ '.',
+ 'mock.io/awesome-image:v42',
+ '--loglevel', 1,
+ '--tlsverify',
+ '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
+ '--image-scripts-url', 'image:///moock-image-label/s2i',
+ _out=ANY,
+ _err=ANY,
+ _tee='err'
+ )
+
+ def test_success_error_container_registries_login(
+ self,
+ mock_create_working_dir_sub_dir,
+ mock_write_working_file,
+ mock_container_registries_login,
+ mock_inspect_container_image,
+ mock_s2i
+ ):
+ # setup
+ step_config = {
+ 's2i-builder-image': 'mock.io/awesome-image:v42'
+ }
+ step_implementer = self.create_step_implementer(
+ step_config=step_config
+ )
+
+ # setup mocks
+ mock_inspect_container_image.return_value = {
+ 'OCIv1': {
+ 'config': {
+ 'Labels': {
+ 'io.openshift.s2i.scripts-url': 'image:///moock-image-label/s2i'
+ }
+ }
+ }
+ }
+ mock_container_registries_login.side_effect = RuntimeError('mock error loging in')
+
+ # run test
+ actual_step_result= step_implementer._run_step()
+
+ # validate
+ expected_step_result = StepResult(
+ step_name='create-container-image',
+ sub_step_name='SourceToImage',
+ sub_step_implementer_name='SourceToImage'
+ )
+ expected_step_result.message = "WARNING: error authenticating with" \
+ " container image registries to be able to pull s2i builder image" \
+ " to inspect for image scripts url: mock error loging in\n"
+ expected_step_result.add_artifact(
+ name='imagespecfile',
+ value='/mock/working/s2i-context/Containerfile.s2i-gen',
+ description='File defining the container image to build generated by s2i'
+ )
+ expected_step_result.add_artifact(
+ name='context',
+ value='/mock/working/s2i-context',
+ description='Context to use when building the imagespecfile generated by S2I.'
+ )
+ self.assertEqual(actual_step_result, expected_step_result)
+
+ mock_inspect_container_image.assert_called_once_with(
+ container_image_uri='mock.io/awesome-image:v42',
+ containers_config_auth_file='create-container-image/container-auth.json'
+ )
+ mock_container_registries_login.assert_called_once_with(
+ registries=None,
+ containers_config_auth_file='create-container-image/container-auth.json',
+ containers_config_tls_verify=True
+ )
+ mock_s2i.build.assert_called_once_with(
+ '.',
+ 'mock.io/awesome-image:v42',
+ '--loglevel', 1,
+ '--tlsverify',
+ '--as-dockerfile', '/mock/working/s2i-context/Containerfile.s2i-gen',
+ '--image-scripts-url', 'image:///moock-image-label/s2i',
+ _out=ANY,
+ _err=ANY,
+ _tee='err'
+ )
\ No newline at end of file
diff --git a/tests/utils/test_containers.py b/tests/utils/test_containers.py
index 1206fbf6a..815a32277 100644
--- a/tests/utils/test_containers.py
+++ b/tests/utils/test_containers.py
@@ -1,6 +1,6 @@
import re
from io import IOBase
-from unittest.mock import call, patch
+from unittest.mock import ANY, call, patch
import sh
from ploigos_step_runner.config import ConfigValue
@@ -1196,3 +1196,120 @@ def test_default_image_version(self):
actual_image_version,
'latest'
)
+
+@patch('sh.buildah', create=True)
+class Test_inspect_container_image(BaseTestCase):
+ def test_success_no_auth(self, mock_buildah):
+ # setup mock
+ def buildah_inspect_side_effect(container_image_uri, _out):
+ _out.write('''{
+ "mock-value": "mock container details"
+}''')
+ mock_buildah.inspect.side_effect = buildah_inspect_side_effect
+
+ # run test
+ actual_container_details = inspect_container_image(
+ container_image_uri='mock.io/mock/awesome-image:latest'
+ )
+
+ # validate
+ self.assertEqual(
+ actual_container_details,
+ {
+ 'mock-value': 'mock container details'
+ }
+ )
+ mock_buildah.pull.assert_called_once_with(
+ 'mock.io/mock/awesome-image:latest'
+ )
+ mock_buildah.inspect.assert_called_once_with(
+ 'mock.io/mock/awesome-image:latest',
+ _out=ANY
+ )
+
+ def test_success_with_auth(self, mock_buildah):
+ # setup mock
+ def buildah_inspect_side_effect(*args, _out):
+ _out.write('''{
+ "mock-value": "mock container details"
+}''')
+ mock_buildah.inspect.side_effect = buildah_inspect_side_effect
+
+ # run test
+ actual_container_details = inspect_container_image(
+ container_image_uri='mock.io/mock/awesome-image:latest',
+ containers_config_auth_file='/mock/auth-file'
+ )
+
+ # validate
+ self.assertEqual(
+ actual_container_details,
+ {
+ 'mock-value': 'mock container details'
+ }
+ )
+ mock_buildah.pull.assert_called_once_with(
+ '--authfile', '/mock/auth-file',
+ 'mock.io/mock/awesome-image:latest'
+ )
+ mock_buildah.inspect.assert_called_once_with(
+ 'mock.io/mock/awesome-image:latest',
+ _out=ANY
+ )
+
+ def test_failure_pull_no_auth(self, mock_buildah):
+ # setup mock
+ mock_buildah.pull.side_effect = sh.ErrorReturnCode('buildah pull', b'mock out', b'mock error')
+
+ # run test
+ with self.assertRaisesRegex(
+ RuntimeError,
+ re.compile(
+ "Error pulling container image \(mock.io/mock/awesome-image:latest\) for inspection:"
+ r".*RAN: buildah pull"
+ r".*STDOUT:"
+ r".*mock out"
+ r".*STDERR:"
+ r".*mock error",
+ re.DOTALL
+ )
+ ):
+ inspect_container_image(
+ container_image_uri='mock.io/mock/awesome-image:latest'
+ )
+
+ # validate
+ mock_buildah.pull.assert_called_once_with(
+ 'mock.io/mock/awesome-image:latest'
+ )
+ mock_buildah.inspect.assert_not_called()
+
+ def test_failure_inspect_no_auth(self, mock_buildah):
+ # setup mock
+ mock_buildah.inspect.side_effect = sh.ErrorReturnCode('buildah', b'mock out', b'mock error')
+
+ # run test
+ with self.assertRaisesRegex(
+ RuntimeError,
+ re.compile(
+ "Error inspecting container image \(mock.io/mock/awesome-image:latest\)"
+ r".*RAN: buildah"
+ r".*STDOUT:"
+ r".*mock out"
+ r".*STDERR:"
+ r".*mock error",
+ re.DOTALL
+ )
+ ):
+ inspect_container_image(
+ container_image_uri='mock.io/mock/awesome-image:latest'
+ )
+
+ # validate
+ mock_buildah.pull.assert_called_once_with(
+ 'mock.io/mock/awesome-image:latest'
+ )
+ mock_buildah.inspect.assert_called_once_with(
+ 'mock.io/mock/awesome-image:latest',
+ _out=ANY
+ )