Skip to content

Commit

Permalink
Add container build/remove image commands
Browse files Browse the repository at this point in the history
fixes: pulp#424
  • Loading branch information
gerrod3 committed Jul 29, 2022
1 parent 14fed69 commit 0c713c8
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGES/424.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added container build/remove image commands.
15 changes: 15 additions & 0 deletions pulpcore/cli/container/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ class PulpContainerRepositoryContext(PulpContainerBaseRepositoryContext):
"pulpexport": [PluginRequirement("container", "2.8.0")],
"tag": [PluginRequirement("container", "2.3.0")],
"roles": [PluginRequirement("container", "2.11.0")],
"build": [PluginRequirement("container", "1.1.0")],
}

def modify(
Expand Down Expand Up @@ -143,6 +144,16 @@ def copy_manifest(
body = self.preprocess_body(body)
return self.call("copy_manifests", parameters={self.HREF: self.pulp_href}, body=body)

def build_image(
self,
container_artifact: str,
tag: Optional[str],
artifacts: Optional[str],
) -> Any:
body = {"containerfile_artifact": container_artifact, "tag": tag, "artifacts": artifacts}
body = self.preprocess_body(body)
return self.call("build_image", parameters={self.HREF: self.pulp_href}, body=body)


class PulpContainerPushRepositoryContext(PulpContainerBaseRepositoryContext):
HREF = "container_container_push_repository_href"
Expand All @@ -153,6 +164,10 @@ class PulpContainerPushRepositoryContext(PulpContainerBaseRepositoryContext):
"roles": [PluginRequirement("container", "2.11.0")],
}

def remove_image(self, digest: str) -> Any:
body = {"digest": digest}
return self.call("remove_image", parameters={self.HREF: self.pulp_href}, body=body)


registered_repository_contexts["container:container"] = PulpContainerRepositoryContext
registered_repository_contexts["container:push"] = PulpContainerPushRepositoryContext
113 changes: 112 additions & 1 deletion pulpcore/cli/container/repository.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import json
import re
from typing import Any, Dict, List, Optional
from pathlib import Path
from typing import Any, Dict, List, Optional, Union

import click

from pulpcore.cli.common.context import (
EntityFieldDefinition,
PulpContext,
PulpEntityContext,
PulpRemoteContext,
PulpRepositoryContext,
pass_pulp_context,
pass_repository_context,
)
from pulpcore.cli.common.generic import (
Expand All @@ -17,6 +21,7 @@
label_command,
label_select_option,
list_command,
load_json_callback,
name_option,
pulp_group,
repository_content_command,
Expand All @@ -41,6 +46,7 @@
PulpContainerRepositoryContext,
PulpContainerTagContext,
)
from pulpcore.cli.core.context import PulpArtifactContext
from pulpcore.cli.core.generic import task_command

translation = get_translation(__name__)
Expand All @@ -57,6 +63,22 @@ def _tag_callback(ctx: click.Context, param: click.Parameter, value: str) -> str
return value


def _directory_or_json_callback(
ctx: click.Context, param: click.Parameter, value: Optional[str]
) -> Union[str, Path, None]:
if not value:
return value
uvalue: Union[str, Path]
try:
uvalue = load_json_callback(ctx, param, value)
except click.ClickException:
uvalue = Path(value)
if not uvalue.exists() or not uvalue.is_dir():
raise click.ClickException(_("{} is not a valid directory").format(value))

return uvalue


source_option = resource_option(
"--source",
default_plugin="container",
Expand Down Expand Up @@ -114,6 +136,7 @@ def repository() -> None:
"blob": PulpContainerBlobContext,
}
container_context = (PulpContainerRepositoryContext,)
push_container_context = (PulpContainerPushRepositoryContext,)

repository.add_command(list_command(decorators=[label_select_option]))
repository.add_command(show_command(decorators=lookup_options))
Expand Down Expand Up @@ -286,3 +309,91 @@ def copy_manifest(
digests=digests or None,
media_types=media_types or None,
)


def upload_file(pulp_ctx: PulpContext, file_location: str) -> str:
try:
with click.open_file(file_location, "r") as fp:
artifact_ctx = PulpArtifactContext(pulp_ctx)
artifact_href = artifact_ctx.upload(fp)
except OSError:
raise click.ClickException(
_("Failed to load content from {file}").format(file=file_location)
)
click.echo(_("Uploaded file: {}").format(artifact_href))
return artifact_href # type: ignore


@repository.command(allowed_with_contexts=container_context)
@name_option
@href_option
@click.option(
"--containerfile",
help=_(
"An artifact href of an uploaded Containerfile. Can also be a local Containerfile to be"
" uploaded using @."
),
required=True,
)
@click.option("--tag", help=_("A tag name for the new image being built."))
@click.option(
"--artifacts",
help=_(
"Directory of files to be uploaded and used during the build. Or a JSON string where each"
" key is an artifact href and the value is it's relative path (name) inside the "
"/pulp_working_directory of the build container executing the Containerfile."
),
callback=_directory_or_json_callback,
)
@pass_repository_context
@pass_pulp_context
def build_image(
pulp_ctx: PulpContext,
repository_ctx: PulpContainerRepositoryContext,
containerfile: str,
tag: Optional[str],
artifacts: Union[str, Path, None],
) -> None:
if not repository_ctx.capable("build"):
raise click.ClickException(_("Repository does not support image building."))

container_artifact_href: str
artifacts_json: Optional[str] = None
# Upload necessary files as artifacts if specified
if containerfile[0] == "@":
container_artifact_href = upload_file(pulp_ctx, containerfile[1:])
else:
artifact_ctx = PulpArtifactContext(pulp_ctx, pulp_href=containerfile)
container_artifact_href = artifact_ctx.pulp_href

if artifacts:
if isinstance(artifacts, Path):
# Upload files in directory
artifact_hrefs = {}
for child in artifacts.rglob("*"):
if child.is_file():
artifact_href = upload_file(pulp_ctx, str(child))
artifact_hrefs[artifact_href] = str(child.relative_to(artifacts))
artifacts_json = json.dumps(artifact_hrefs)
else:
artifacts_json = artifacts

repository_ctx.build_image(container_artifact_href, tag, artifacts_json)


@repository.command(allowed_with_contexts=push_container_context)
@name_option
@href_option
@click.option("--digest", help=_("SHA256 digest of the Manifest file"), required=True)
@pass_repository_context
def remove_image(
repository_ctx: PulpContainerPushRepositoryContext,
digest: str,
) -> None:
digest = digest.strip()
if not digest.startswith("sha256:"):
digest = f"sha256:{digest}"
if len(digest) != 71: # len("sha256:") + 64
raise click.ClickException("Improper SHA256, please provide a valid 64 digit digest.")

repository_ctx.remove_image(digest)

0 comments on commit 0c713c8

Please sign in to comment.