Skip to content

Commit ef3750e

Browse files
committed
Add container build/remove image commands
fixes: #424
1 parent 4657acc commit ef3750e

File tree

3 files changed

+130
-1
lines changed

3 files changed

+130
-1
lines changed

CHANGES/424.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added container build/remove image commands.

pulpcore/cli/container/context.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class PulpContainerRepositoryContext(PulpContainerBaseRepositoryContext):
108108
"pulpexport": [PluginRequirement("container", "2.8.0.dev")],
109109
"tag": [PluginRequirement("container", "2.3.0")],
110110
"roles": [PluginRequirement("container", "2.11.0.dev")],
111+
"build": [PluginRequirement("container", "1.1.0")],
111112
}
112113

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

147+
def build_image(
148+
self,
149+
container_artifact: str,
150+
tag: Optional[str],
151+
artifacts: Optional[str],
152+
) -> Any:
153+
body = {"containerfile_artifact": container_artifact, "tag": tag, "artifacts": artifacts}
154+
body = self.preprocess_body(body)
155+
return self.call("build_image", parameters={self.HREF: self.pulp_href}, body=body)
156+
146157

147158
class PulpContainerPushRepositoryContext(PulpContainerBaseRepositoryContext):
148159
HREF = "container_container_push_repository_href"
@@ -153,6 +164,10 @@ class PulpContainerPushRepositoryContext(PulpContainerBaseRepositoryContext):
153164
"roles": [PluginRequirement("container", "2.11.0.dev")],
154165
}
155166

167+
def remove_image(self, digest: str) -> Any:
168+
body = {"digest": digest}
169+
return self.call("remove_image", parameters={self.HREF: self.pulp_href}, body=body)
170+
156171

157172
registered_repository_contexts["container:container"] = PulpContainerRepositoryContext
158173
registered_repository_contexts["container:push"] = PulpContainerPushRepositoryContext

pulpcore/cli/container/repository.py

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
import json
12
import re
2-
from typing import Any, Dict, List, Optional
3+
from pathlib import Path
4+
from typing import Any, Dict, List, Optional, Union
35

46
import click
57

68
from pulpcore.cli.common.context import (
79
EntityFieldDefinition,
10+
PulpContext,
811
PulpEntityContext,
912
PulpRemoteContext,
1013
PulpRepositoryContext,
14+
pass_pulp_context,
1115
pass_repository_context,
1216
)
1317
from pulpcore.cli.common.generic import (
@@ -17,6 +21,7 @@
1721
label_command,
1822
label_select_option,
1923
list_command,
24+
load_json_callback,
2025
name_option,
2126
pulp_group,
2227
repository_content_command,
@@ -41,6 +46,7 @@
4146
PulpContainerRepositoryContext,
4247
PulpContainerTagContext,
4348
)
49+
from pulpcore.cli.core.context import PulpArtifactContext
4450
from pulpcore.cli.core.generic import task_command
4551

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

5965

66+
def _directory_or_json_callback(
67+
ctx: click.Context, param: click.Parameter, value: Optional[str]
68+
) -> Union[str, Path, None]:
69+
if not value:
70+
return value
71+
uvalue: Union[str, Path]
72+
try:
73+
uvalue = load_json_callback(ctx, param, value)
74+
except click.ClickException:
75+
uvalue = Path(value)
76+
if not uvalue.exists() or not uvalue.is_dir():
77+
raise click.ClickException(_("{} is not a valid directory").format(value))
78+
79+
return uvalue
80+
81+
6082
source_option = resource_option(
6183
"--source",
6284
default_plugin="container",
@@ -114,6 +136,7 @@ def repository() -> None:
114136
"blob": PulpContainerBlobContext,
115137
}
116138
container_context = (PulpContainerRepositoryContext,)
139+
push_container_context = (PulpContainerPushRepositoryContext,)
117140

118141
repository.add_command(list_command(decorators=[label_select_option]))
119142
repository.add_command(show_command(decorators=lookup_options))
@@ -286,3 +309,93 @@ def copy_manifest(
286309
digests=digests or None,
287310
media_types=media_types or None,
288311
)
312+
313+
314+
def upload_file(pulp_ctx: PulpContext, file_location: str) -> str:
315+
try:
316+
with click.open_file(file_location, "r") as fp:
317+
artifact_ctx = PulpArtifactContext(pulp_ctx)
318+
click.echo(_("Uploading {} as artifact").format(file_location))
319+
artifact_href = artifact_ctx.upload(fp)
320+
except OSError:
321+
raise click.ClickException(
322+
_("Failed to load content from {file}").format(file=file_location)
323+
)
324+
click.echo(_("Uploaded file: {}").format(artifact_href))
325+
return artifact_href # type: ignore
326+
327+
328+
@repository.command(allowed_with_contexts=container_context)
329+
@name_option
330+
@href_option
331+
@click.option(
332+
"--containerfile",
333+
help=_(
334+
"An artifact href of an uploaded Containerfile. Can also be a local Containerfile to be"
335+
" uploaded using @."
336+
),
337+
required=True,
338+
)
339+
@click.option("--tag", help=_("A tag name for the new image being built."))
340+
@click.option(
341+
"--artifacts",
342+
help=_(
343+
"Directory of files to be uploaded and used during the build. Or a JSON string where each"
344+
" key is an artifact href and the value is it's relative path (name) inside the "
345+
"/pulp_working_directory of the build container executing the Containerfile."
346+
),
347+
callback=_directory_or_json_callback,
348+
)
349+
@pass_repository_context
350+
@pass_pulp_context
351+
def build_image(
352+
pulp_ctx: PulpContext,
353+
repository_ctx: PulpContainerRepositoryContext,
354+
containerfile: str,
355+
tag: Optional[str],
356+
artifacts: Union[str, Path, None],
357+
) -> None:
358+
if not repository_ctx.capable("build"):
359+
raise click.ClickException(_("Repository does not support image building."))
360+
361+
container_artifact_href: str
362+
artifacts_json: Optional[str] = None
363+
# Upload necessary files as artifacts if specified
364+
if containerfile[0] == "@":
365+
container_artifact_href = upload_file(pulp_ctx, containerfile[1:])
366+
else:
367+
artifact_ctx = PulpArtifactContext(pulp_ctx, pulp_href=containerfile)
368+
container_artifact_href = artifact_ctx.pulp_href
369+
370+
if artifacts:
371+
if isinstance(artifacts, Path):
372+
# Upload files in directory
373+
artifact_hrefs = {}
374+
for child in artifacts.iterdir():
375+
# Can the directory structure be non-flat?
376+
if child.is_file():
377+
artifact_href = upload_file(pulp_ctx, str(child))
378+
artifact_hrefs[artifact_href] = child.name
379+
artifacts_json = json.dumps(artifact_hrefs)
380+
else:
381+
artifacts_json = artifacts
382+
383+
repository_ctx.build_image(container_artifact_href, tag, artifacts_json)
384+
385+
386+
@repository.command(allowed_with_contexts=push_container_context)
387+
@name_option
388+
@href_option
389+
@click.option("--digest", help=_("SHA256 digest of the Manifest file"), required=True)
390+
@pass_repository_context
391+
def remove_image(
392+
repository_ctx: PulpContainerPushRepositoryContext,
393+
digest: str,
394+
) -> None:
395+
digest = digest.strip()
396+
if not digest.startswith("sha256:"):
397+
digest = f"sha256:{digest}"
398+
if len(digest) != 71: # len("sha256:") + 64
399+
raise click.ClickException("Improper SHA256, please provide a valid 64 digit digest.")
400+
401+
repository_ctx.remove_image(digest)

0 commit comments

Comments
 (0)