Skip to content

Commit

Permalink
Add container repository content management commands
Browse files Browse the repository at this point in the history
fixes: pulp#422
  • Loading branch information
gerrod3 committed Apr 12, 2022
1 parent 9cee6bd commit 971b80d
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGES/422.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added container repository content management commands.
35 changes: 34 additions & 1 deletion pulpcore/cli/container/context.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any
from typing import Any, List, Optional

from pulpcore.cli.common.context import (
EntityDefinition,
Expand Down Expand Up @@ -110,6 +110,39 @@ class PulpContainerRepositoryContext(PulpContainerBaseRepositoryContext):
"roles": [PluginRequirement("container", "2.11.0.dev")],
}

def modify(
self,
href: str,
add_content: Optional[List[str]] = None,
remove_content: Optional[List[str]] = None,
base_version: Optional[str] = None,
) -> Any:
if remove_content:
self.call(
"remove", parameters={self.HREF: href}, body={"content_units": remove_content}
)
if add_content:
self.call("add", parameters={self.HREF: href}, body={"content_units": add_content})

def copy_tag(self, source_href: str, tags: Optional[List[str]]) -> Any:
body = {"source_repository_version": source_href, "names": tags}
body = self.preprocess_body(body)
return self.call("copy_tags", parameters={self.HREF: self.pulp_href}, body=body)

def copy_manifest(
self,
source_href: str,
digests: Optional[List[str]],
media_types: Optional[List[str]],
) -> Any:
body = {
"source_repository_version": source_href,
"digests": digests,
"media_types": media_types,
}
body = self.preprocess_body(body)
return self.call("copy_manifests", parameters={self.HREF: self.pulp_href}, body=body)


class PulpContainerPushRepositoryContext(PulpContainerBaseRepositoryContext):
HREF = "container_container_push_repository_href"
Expand Down
163 changes: 157 additions & 6 deletions pulpcore/cli/container/repository.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import re
from typing import Any, Dict
from typing import Any, Dict, List, Optional, Type

import click

from pulpcore.cli.common.context import (
EntityDefinition,
EntityFieldDefinition,
PulpContext,
PulpEntityContext,
PulpRemoteContext,
PulpRepositoryContext,
PulpRepositoryVersionContext,
pass_repository_context,
)
from pulpcore.cli.common.generic import (
Expand All @@ -19,6 +22,7 @@
list_command,
name_option,
pulp_group,
repository_content_command,
repository_href_option,
repository_option,
resource_option,
Expand All @@ -30,11 +34,17 @@
version_command,
)
from pulpcore.cli.common.i18n import get_translation
from pulpcore.cli.container.content import show_options
from pulpcore.cli.container.context import (
PulpContainerBaseRepositoryContext,
PulpContainerBlobContext,
PulpContainerManifestContext,
PulpContainerPushRepositoryContext,
PulpContainerPushRepositoryVersionContext,
PulpContainerRemoteContext,
PulpContainerRepositoryContext,
PulpContainerRepositoryVersionContext,
PulpContainerTagContext,
)
from pulpcore.cli.core.generic import task_command

Expand All @@ -45,22 +55,76 @@

def _tag_callback(ctx: click.Context, param: click.Parameter, value: str) -> str:
if len(value) == 0:
raise click.ClickException("Please pass a non empty tag name.")
raise click.ClickException(_("Please pass a non empty tag name."))
if re.match(VALID_TAG_REGEX, value) is None:
raise click.ClickException("Please pass a valid tag.")
raise click.ClickException(_("Please pass a valid tag."))

return value


def _source_callback(
ctx: click.Context, param: click.Parameter, value: str
) -> PulpRepositoryVersionContext:
pulp_ctx = ctx.find_object(PulpContext)
assert pulp_ctx is not None
if len(value) == 0:
raise click.ClickException(_("Please pass in a non empty source repository."))

version: Optional[str] = None
pulp_href: Optional[str] = None
entity: Optional[EntityDefinition] = None
isp: Any = None
model_ctx: Type[PulpContainerBaseRepositoryContext] = PulpContainerRepositoryContext
model_ver_ctx: Type[PulpRepositoryVersionContext] = PulpContainerRepositoryVersionContext
if value.startswith("/"):
cpattern = rf"^{pulp_ctx.api_path}{PulpContainerRepositoryContext.HREF_PATTERN}"
ppattern = rf"^{pulp_ctx.api_path}{PulpContainerPushRepositoryContext.HREF_PATTERN}"
if not re.match(cpattern, value) or not (isp := re.match(ppattern, value)):
raise click.ClickException(
_("'{value}' is not a valid href for {option_name}.").format(
value=value, option_name=param.name
)
)
pulp_href = value
if "versions" in value:
pulp_href = value.partition("versions")[0]
version = value.split("/")[-2]
else:
split_value = value.split(":", maxsplit=2)
if len(split_value) == 3:
if split_value[0] not in ["container", "push"]:
raise click.ClickException(_("Improper repository type {}").format(split_value[0]))
isp = split_value.pop(0) == "push"
if len(split_value) == 2:
version = split_value[1]
entity = {"name": split_value[0]}

if isp:
model_ctx = PulpContainerPushRepositoryContext
model_ver_ctx = PulpContainerPushRepositoryVersionContext
repo_ctx = model_ctx(pulp_ctx, pulp_href=pulp_href, entity=entity)
repo_ver_ctx = model_ver_ctx(pulp_ctx, repository_ctx=repo_ctx)
repo_ver_ctx.pulp_href = repo_ctx.entity["latest_version_href"]
if version:
# Check that the version makes sense
latest_version = int(repo_ctx.entity["latest_version_href"].split("/")[-2])
if not (0 < int(version) <= latest_version):
raise click.ClickException(
_("Please specify a version that between 0 and the latest version {}").format(
latest_version
)
)
repo_ver_ctx.pulp_href = f"{repo_ctx.entity['versions_href']}{version}/"
return repo_ver_ctx


remote_option = resource_option(
"--remote",
default_plugin="container",
default_type="container",
context_table={"container:container": PulpContainerRemoteContext},
href_pattern=PulpRemoteContext.HREF_PATTERN,
help=_(
"Remote used for synching in the form '[[<plugin>:]<resource_type>:]<name>' or by href."
),
help=_("Remote used for syncing in the form '[[<plugin>:]<resource_type>:]<name>' or by href."),
)


Expand All @@ -84,6 +148,11 @@ def repository() -> None:
retained_versions_option,
]
create_options = update_options + [click.option("--name", required=True)]
contexts = {
"tag": PulpContainerTagContext,
"manifest": PulpContainerManifestContext,
"blob": PulpContainerBlobContext,
}
container_context = (PulpContainerRepositoryContext,)

repository.add_command(list_command(decorators=[label_select_option]))
Expand All @@ -103,6 +172,14 @@ def repository() -> None:
repository.add_command(version_command(decorators=nested_lookup_options))
repository.add_command(role_command(decorators=lookup_options))
repository.add_command(label_command(decorators=nested_lookup_options))
repository.add_command(
repository_content_command(
contexts=contexts,
add_decorators=show_options,
remove_decorators=show_options,
allowed_with_contexts=container_context,
)
)


@repository.command(allowed_with_contexts=container_context)
Expand Down Expand Up @@ -165,3 +242,77 @@ def add_tag(
@pass_repository_context
def remove_tag(repository_ctx: PulpContainerBaseRepositoryContext, tag: str) -> None:
repository_ctx.untag(tag)


@repository.command(allowed_with_contexts=container_context)
@name_option
@href_option
@click.option(
"--source",
help=_(
"Source repository to copy tags from. Specify in format of href or [<resource_type>:]<name>"
"[:<version>]"
),
required=True,
callback=_source_callback,
)
@click.option(
"--tag",
"tags",
help=_("Multiple option of tag names to copy, leave blank to copy all"),
multiple=True,
)
@pass_repository_context
def copy_tag(
repository_ctx: PulpContainerRepositoryContext,
source: PulpRepositoryVersionContext,
tags: List[str],
) -> None:
repository_ctx.copy_tag(source_href=source.pulp_href, tags=tags or None)


@repository.command(allowed_with_contexts=container_context)
@name_option
@href_option
@click.option(
"--source",
help=_(
"Source repository to copy manifests from. Specify in format of href or [<resource_type>:]"
"<name>[:<version>]"
),
required=True,
callback=_source_callback,
)
@click.option(
"--digest",
"digests",
help=_("Multiple option of manifest digests to copy, leave blank to copy all"),
multiple=True,
)
@click.option(
"--media-type",
"media_types",
help=_("Multiple option of media-types to copy, leave blank to copy all types"),
type=click.Choice(
[
"application/vnd.docker.distribution.manifest.v1+json",
"application/vnd.docker.distribution.manifest.v2+json",
"application/vnd.docker.distribution.manifest.list.v2+json",
"application/vnd.oci.image.manifest.v1+json",
"application/vnd.oci.image.index.v1+json",
]
),
multiple=True,
)
@pass_repository_context
def copy_manifest(
repository_ctx: PulpContainerRepositoryContext,
source: PulpRepositoryVersionContext,
digests: List[str],
media_types: List[str],
) -> None:
repository_ctx.copy_manifest(
source_href=source.pulp_href,
digests=digests or None,
media_types=media_types or None,
)
14 changes: 14 additions & 0 deletions tests/scripts/pulp_container/test_content.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,17 @@ tag_digest="$(echo "$OUTPUT" | jq -r .digest)"

expect_succ pulp container content -t tag show --name "$tag_name" --digest "$tag_digest"
test "$(echo "$OUTPUT" | jq -r .pulp_href)" = "$tag_href"

# Test repository content commands
expect_succ pulp container repository content list --repository "cli_test_container_repository" --all-types
expect_succ pulp container repository content --type "tag" list --repository "cli_test_container_repository"
expect_succ pulp container repository content --type "manifest" list --repository "cli_test_container_repository"
expect_succ pulp container repository content --type "blob" list --repository "cli_test_container_repository"

expect_succ pulp container repository content --type "blob" remove --repository "cli_test_container_repository" --digest "$blob_digest"
expect_succ pulp container repository content --type "manifest" remove --repository "cli_test_container_repository" --digest "$manifest_digest"
expect_succ pulp container repository content --type "tag" remove --repository "cli_test_container_repository" --name "$tag_name" --digest "$tag_digest"

expect_succ pulp container repository content add --repository "cli_test_container_repository" --href "$blob_href"
expect_succ pulp container repository content add --repository "cli_test_container_repository" --href "$manifest_href"
expect_succ pulp container repository content add --repository "cli_test_container_repository" --href "$tag_href"
52 changes: 52 additions & 0 deletions tests/scripts/pulp_container/test_copy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash

# shellcheck source=tests/scripts/config.source
. "$(dirname "$(dirname "$(realpath "$0")")")"/config.source

pulp debug has-plugin --name "container" || exit 3

cleanup() {
pulp container remote destroy --name "cli_test_container_remote" || true
pulp container repository destroy --name "cli_test_source_container_repository" || true
pulp container repository destroy --name "cli_test_dest_container_repository" || true
pulp orphan cleanup || true
}
trap cleanup EXIT

# Prepare
pulp container remote create --name "cli_test_container_remote" --url "$CONTAINER_REMOTE_URL" --upstream-name "$CONTAINER_IMAGE"
source_href="$(pulp container repository create --name "cli_test_source_container_repository" | jq -r .pulp_href)"
pulp container repository create --name "cli_test_dest_container_repository"
pulp container repository sync --name "cli_test_source_container_repository" --remote "cli_test_container_remote"
tag="$(pulp container repository content -t 'tag' list --repository "cli_test_source_container_repository" | jq -r .[0].name)"
digest="$(pulp container repository content -t 'manifest' list --repository "cli_test_source_container_repository" | jq -r '.[] | select(.listed_manifests == []) | .digest' | sed -n '1p')"

# Test copying manifests
expect_succ pulp container repository copy-manifest --name "cli_test_dest_container_repository" --source "cli_test_source_container_repository" --digest "$digest"
expect_succ pulp container repository content -t 'manifest' list --repository "cli_test_dest_container_repository"
test "$(echo "$OUTPUT" | jq -r length)" -eq 1
test "$(echo "$OUTPUT" | jq -r .[0].digest)" = "$digest"

expect_succ pulp container repository copy-manifest --name "cli_test_dest_container_repository" --source "cli_test_source_container_repository:1" --media-type "application/vnd.docker.distribution.manifest.v2+json"
expect_succ pulp container repository content -t 'manifest' list --repository "cli_test_dest_container_repository" --version "2"
copied="$(echo "$OUTPUT" | jq -r length)"
test "$copied" -gt 1

expect_succ pulp container repository copy-manifest --name "cli_test_dest_container_repository" --source "$source_href"
expect_succ pulp container repository content -t 'manifest' list --repository "cli_test_dest_container_repository" --version "3"
test "$(echo "$OUTPUT" | jq -r length)" -gt "$copied"

# Test copying tags
expect_succ pulp container repository copy-tag --name "cli_test_dest_container_repository" --source "cli_test_source_container_repository" --tag "$tag"
expect_succ pulp container repository content -t 'tag' list --repository "cli_test_dest_container_repository" --version "4"
test "$(echo "$OUTPUT" | jq -r length)" -eq 1
test "$(echo "$OUTPUT" | jq -r .[0].name)" = "$tag"

expect_succ pulp container repository copy-tag --name "cli_test_dest_container_repository" --source "$source_href""versions/1/"
expect_succ pulp container repository content -t 'tag' list --repository "cli_test_dest_container_repository" --version "5"
test "$(echo "$OUTPUT" | jq -r length)" -gt 1

# Test bad versions
expect_fail pulp container repository copy-tag --name "cli_test_source_container_repository" --source "cli_test_dest_container_repository:0"
expect_fail pulp container repository copy-tag --name "cli_test_source_container_repository" --source "cli_test_dest_container_repository:6"
test "$ERROUTPUT" = "Error: Please specify a version that between 0 and the latest version 5"

0 comments on commit 971b80d

Please sign in to comment.