Skip to content

Commit

Permalink
create-container-image/SourceToImage - init add
Browse files Browse the repository at this point in the history
  • Loading branch information
itewk committed Sep 14, 2021
1 parent e30ed2c commit 8d539b9
Show file tree
Hide file tree
Showing 5 changed files with 1,069 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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`. \
<br/> \
EX:<br/> \
s2i-builder-image: registry.redhat.io/redhat-openjdk-18/openjdk18-openshift <br/> \
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. <br/> \
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. <br/> \
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
61 changes: 61 additions & 0 deletions src/ploigos_step_runner/utils/containers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Shared utils for dealing with containers.
"""

import json
import sys
from io import StringIO

Expand Down Expand Up @@ -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
Loading

0 comments on commit 8d539b9

Please sign in to comment.