Skip to content

Commit

Permalink
mirror validation should pass with local image only; more testing for…
Browse files Browse the repository at this point in the history
… mirroring (#47)

* mirror validation should pass with local image only; more testing for mirror

* address review
  • Loading branch information
apesternikov authored Aug 31, 2024
1 parent d1ce6c5 commit d358aba
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 46 deletions.
108 changes: 65 additions & 43 deletions mirror/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ def _replace_colon_except_last_segment(input_string):
output_string = "/".join(segments)
return output_string

def _mirror_image_impl(ctx):
# Common implementation for mirror_image and mirror_image_test
# Uses the following ctx attributes: src_image, digest, dst, dst_prefix
# Returns the src_image, digest, and dst_without_hash
def _impl_common(ctx):
digest = ctx.attr.digest
src_image = ctx.attr.src_image
v = src_image.split("@", 1)
Expand Down Expand Up @@ -39,6 +42,11 @@ def _mirror_image_impl(ctx):
dst_prefix = ctx.expand_make_variables("dst_prefix", ctx.attr.dst_prefix, {})
dst_without_hash = dst_prefix.strip("/") + "/" + src_repository

return src_image, digest, dst_without_hash

def _mirror_image_impl(ctx):
src_image, digest, dst_without_hash = _impl_common(ctx)

digest_file = ctx.actions.declare_file(ctx.label.name + ".digest")
ctx.actions.write(
output = digest_file,
Expand Down Expand Up @@ -81,9 +89,6 @@ mirror_image_rule = rule(
mandatory = True,
doc = "The image to mirror",
),
"image_name": attr.string(
doc = "The name that could be referred in manifests. This field is deprecated and unused.",
),
"digest": attr.string(
mandatory = False,
doc = "The digest of the image. If not provided, it will be extracted from the src_image.",
Expand Down Expand Up @@ -114,46 +119,63 @@ Implements GitopsPushInfo and K8sPushInfo providers so the returned image can be
""",
)

def validate_image_test(name, image, digest, tags = [], **kwargs):
"""
Create a test that validates the image existance using crane validate.
Image tag will be ignored if provided and only the digest will be used.
if digest is provided as a part of the image, it will be used.
It is an error to provide both digest and image with digest if they do not match.
"""
src_image = image
v = src_image.split("@", 1)
s = v[0]
if len(v) > 1:
# If the image has a digest, use that.
if digest and v[1] != digest:
fail("digest mismatch: %s != %s" % (v[1], digest))
digest = v[1]
else:
# If the image does not have a digest, use the one provided.
src_image = s + "@" + digest

if not digest:
fail("digest must be provided as an attribute to mirror_image or in the src_image")
def _validate_mirror_impl(ctx):
src_image, digest, dst_without_hash = _impl_common(ctx)

native.sh_test(
name = name,
size = "small",
srcs = ["@rules_gitops//mirror:validate_image.sh"],
data = [
"@rules_gitops//vendor/github.com/google/go-containerregistry/cmd/crane:crane",
],
args = [
src_image,
],
tags = ["requires-network"] + tags,
env = {
"CRANE_BIN": "$(location @rules_gitops//vendor/github.com/google/go-containerregistry/cmd/crane:crane)",
ctx.actions.expand_template(
template = ctx.file._validate_image_script,
output = ctx.outputs.executable,
substitutions = {
"{crane_tool}": ctx.executable.crane_tool.short_path,
"{src_image}": src_image,
"{digest}": digest,
"{dst_image}": dst_without_hash,
},
**kwargs
is_executable = True,
)

def mirror_image(name, src_image, digest, tags = [], **kwargs):
visibility = kwargs.pop("visibility", None)
mirror_image_rule(name = name, src_image = src_image, digest = digest, tags = tags, visibility = visibility, **kwargs)
validate_image_test(name = name + "_validate_src", image = src_image, digest = digest, visibility = visibility, tags = tags)
runfiles = ctx.runfiles(files = [ctx.file._validate_image_script]).merge(ctx.attr.crane_tool[DefaultInfo].default_runfiles)

return DefaultInfo(
runfiles = runfiles,
executable = ctx.outputs.executable,
)

validate_mirror_test = rule(
implementation = _validate_mirror_impl,
test = True,
attrs = {
"src_image": attr.string(
mandatory = True,
doc = "The image to mirror",
),
"digest": attr.string(
mandatory = False,
doc = "The digest of the image. If not provided, it will be extracted from the src_image.",
),
"dst_prefix": attr.string(
doc = "The prefix of the destination image, should include the registry and repository. Either dst_prefix or dst_image must be specified.",
),
"dst": attr.string(
doc = "The destination image location, should include the registry and repository. Either dst_prefix or dst_image must be specified.",
),
"crane_tool": attr.label(
default = Label("//vendor/github.com/google/go-containerregistry/cmd/crane:crane"),
executable = True,
cfg = "exec",
),
"_validate_image_script": attr.label(
default = ":validate_image.sh",
allow_single_file = True,
),
},
executable = True,
doc = """Validate a mirrored image. It checks if at least one of remote or local image exists.
""",
)

def mirror_image(name, image_name = None, push_timeout = "30s", **kwargs):
if image_name:
fail("image_name is deprecated and unused")
mirror_image_rule(name = name, push_timeout = push_timeout, **kwargs)
validate_mirror_test(name = name + "_validate_src", **kwargs)
2 changes: 1 addition & 1 deletion mirror/mirror_image.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/bash -x
set -eu

function guess_runfiles() {
Expand Down
22 changes: 22 additions & 0 deletions mirror/tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,25 @@ sh_test(
},
tags = ["exclusive"], # this test starts a registry on fixed port 1338
)

sh_test(
name = "image_mirror_verify_test",
srcs = ["image_mirror_test.sh"],
data = [
":image_mirror",
":image_mirror_validate_src",
":push_image",
"//vendor/github.com/google/go-containerregistry/cmd/crane",
"//vendor/github.com/google/go-containerregistry/cmd/registry",
],
env = {
"LOCAL": "localhost:1338/mirror/localhost1338/image@sha256:b812c0570a7c369b2863c64e22760dc1b1dbc025a739f02db376bac62862f4cc",
"REMOTE": "localhost:1338/image@sha256:b812c0570a7c369b2863c64e22760dc1b1dbc025a739f02db376bac62862f4cc",
"PUSH_IMAGE": "$(location :push_image)",
"IMAGE_MIRROR": "$(location :image_mirror)",
"IMAGE_MIRROR_VALIDATE_SRC": "$(location :image_mirror_validate_src)",
"CRANE_BIN": "$(location //vendor/github.com/google/go-containerregistry/cmd/crane)",
"REGISTRY_BIN": "$(location //vendor/github.com/google/go-containerregistry/cmd/registry)",
},
tags = ["exclusive"], # this test starts a registry on fixed port 1338
)
111 changes: 111 additions & 0 deletions mirror/tests/image_mirror_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/bin/bash -x
#
# This test runs the script passed as the first argument and verifies the image is pushed to the registry
#
${REGISTRY_BIN}&
registry_pid=$!
trap "kill -9 $registry_pid" EXIT

echo verifying image $REMOTE does not exist
${CRANE_BIN} validate -v --fast --remote $REMOTE
if [ $? -eq 0 ]; then
echo "Image $REMOTE should not exist"
exit 1
fi

echo verifying image $LOCAL does not exist
${CRANE_BIN} validate -v --fast --remote $LOCAL
if [ $? -eq 0 ]; then
echo "Image $LOCAL should not exist"
exit 1
fi

#test should fail before pushing the image (no src, no dst)
echo verifying mirror image validation fails
${IMAGE_MIRROR_VALIDATE_SRC}
if [ $? -eq 0 ]; then
echo "Image verification should fail"
exit 1
fi

echo pushing image $SRC_IMAGE
${PUSH_IMAGE}

echo verifying image $REMOTE exists
${CRANE_BIN} validate -v --fast --remote $REMOTE
if [ $? -ne 0 ]; then
echo "Image $REMOTE should exist"
exit 1
fi

echo verifying image $LOCAL does not exist
${CRANE_BIN} validate -v --fast --remote $LOCAL
if [ $? -eq 0 ]; then
echo "Image $LOCAL should not exist"
exit 1
fi

#test should succeed with src image only
echo verifying mirror image validation fails
${IMAGE_MIRROR_VALIDATE_SRC}
if [ $? -ne 0 ]; then
echo "Image verification should succeed"
exit 1
fi

echo running image mirror
${IMAGE_MIRROR}
if [ $? -ne 0 ]; then
echo "Image mirroring should succeed"
exit 1
fi

echo verifying image $LOCAL exists
${CRANE_BIN} validate -v --fast --remote $LOCAL
if [ $? -ne 0 ]; then
echo "Image $LOCAL should exist"
exit 1
fi

echo verifying image $REMOTE exists
${CRANE_BIN} validate -v --fast --remote $REMOTE
if [ $? -ne 0 ]; then
echo "Image $REMOTE should exist"
exit 1
fi

#test should succeed with src and dst images
echo verifying mirror image validation succeeds
${IMAGE_MIRROR_VALIDATE_SRC}
if [ $? -ne 0 ]; then
echo "Image verification should succeed"
exit 1
fi

echo removing image $REMOTE
${CRANE_BIN} delete $REMOTE || exit 1

echo verifying image $REMOTE does not exist
${CRANE_BIN} validate -v --fast --remote $REMOTE
if [ $? -eq 0 ]; then
echo "Image $REMOTE should not exist"
exit 1
fi

echo verifying image $LOCAL exists
${CRANE_BIN} validate -v --fast --remote $LOCAL
if [ $? -ne 0 ]; then
echo "Image $LOCAL should exist"
exit 1
fi

#test should succeed with dst image only
echo verifying mirror image validation succeeds
${IMAGE_MIRROR_VALIDATE_SRC}
if [ $? -ne 0 ]; then
echo "Image verification should succeed"
exit 1
fi


# exit 1
21 changes: 19 additions & 2 deletions mirror/validate_image.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
#!/bin/bash
echo "Validating image $1"
$CRANE_BIN validate -v --fast --remote $1
REMOTE="{src_image}"
LOCAL="{dst_image}@{digest}"
echo "Validating images ${REMOTE} and ${LOCAL}"
{crane_tool} validate -v --fast --remote ${REMOTE}
if [ $? -eq 0 ]; then
echo "Image ${REMOTE} exist"
exit 0
fi

echo "Image ${REMOTE} does not exist, checking ${LOCAL}"

{crane_tool} validate -v --fast --remote ${LOCAL}
if [ $? -eq 0 ]; then
echo "Image ${LOCAL} exist"
exit 0
fi

echo "Image ${LOCAL} does not exist"
exit 1

0 comments on commit d358aba

Please sign in to comment.