Skip to content

Conversation

@rchincha
Copy link
Contributor

@rchincha rchincha commented Dec 4, 2025

Fixes #3601

What type of PR is this?

Which issue does this PR fix:

What does this PR do / Why do we need it:

If an issue # is not available please add repro steps and logs showing the issue:

Testing done on this change:

Automation added to e2e:

Will this break upgrades or downgrades?

Does this PR introduce any user-facing change?:


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@rchincha
Copy link
Contributor Author

rchincha commented Dec 4, 2025

Need this to make sure we don't break stuff after our cache refactoring.

@rchincha rchincha requested a review from Copilot December 4, 2025 17:01
Copilot finished reviewing on behalf of rchincha December 4, 2025 17:04
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds upgrade testing capabilities for the zot container registry by introducing two new BATS test files that verify compatibility when upgrading from a released version to a development version. The tests download the latest release binary, run comprehensive tests against it, then upgrade to the new binary and repeat the tests to ensure backward compatibility.

  • Adds upgrade test infrastructure with helper functions to download and run release binaries
  • Creates comprehensive upgrade test suites for both full and minimal zot builds
  • Integrates new tests into the CI pipeline

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
test/ports.json Allocates port ranges (11500-11509 and 11510-11519) for the two new upgrade test suites
test/blackbox/upgrade.bats Comprehensive upgrade test suite for full zot build, testing release→upgrade→new binary flow
test/blackbox/upgrade_minimal.bats Comprehensive upgrade test suite for minimal zot build, testing release→upgrade→new binary flow
test/blackbox/helpers_zot.bash Adds helper functions zot_rel_serve and zot_rel_min_serve to download and run release binaries
test/blackbox/ci.sh Registers the new upgrade test suites in the CI test array

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

local zot_path=${BATS_FILE_TMPDIR}/zot-rel-${OS}-${ARCH}

if [ ! -f "${zot_path}" ]; then
curl -L -o "${zot_path}" https://github.com/project-zot/zot/releases/latest/download/zot-${OS}-${ARCH}
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The curl command lacks error handling. If the download fails (e.g., network issues, 404 error), the function will continue to chmod +x and execute a potentially non-existent or incomplete file. Add the -f (fail on HTTP errors) flag to curl and check the exit status to ensure the download succeeded before proceeding.

Copilot uses AI. Check for mistakes.
local zot_path=${BATS_FILE_TMPDIR}/zot-rel-${OS}-${ARCH}-minimal

if [ ! -f "${zot_path}" ]; then
curl -L -o "${zot_path}" https://github.com/project-zot/zot/releases/latest/download/zot-${OS}-${ARCH}-minimal
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The curl command lacks error handling. If the download fails (e.g., network issues, 404 error), the function will continue to chmod +x and execute a potentially non-existent or incomplete file. Add the -f (fail on HTTP errors) flag to curl and check the exit status to ensure the download succeeded before proceeding.

Suggested change
curl -L -o "${zot_path}" https://github.com/project-zot/zot/releases/latest/download/zot-${OS}-${ARCH}-minimal
curl -f -L -o "${zot_path}" https://github.com/project-zot/zot/releases/latest/download/zot-${OS}-${ARCH}-minimal
if [ $? -ne 0 ]; then
echo "Error: Failed to download zot binary from GitHub." >&2
return 1
fi

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +719
# Note: Intended to be run as "make run-blackbox-tests" or "make run-blackbox-ci"
# Makefile target installs & checks all necessary tooling
# Extra tools that are not covered in Makefile target needs to be added in verify_prerequisites()

load helpers_zot
load ../port_helper

function verify_prerequisites {
if [ ! $(command -v curl) ]; then
echo "you need to install curl as a prerequisite to running the tests" >&3
return 1
fi

if [ ! $(command -v jq) ]; then
echo "you need to install jq as a prerequisite to running the tests" >&3
return 1
fi

return 0
}

function setup_file() {
# Verify prerequisites are available
if ! $(verify_prerequisites); then
exit 1
fi
# Download test data to folder common for the entire suite, not just this file
skopeo --insecure-policy copy --format=oci docker://ghcr.io/project-zot/golang:1.20 oci:${TEST_DATA_DIR}/golang:1.20
# Setup zot server
local zot_root_dir=${BATS_FILE_TMPDIR}/zot
local zot_config_file=${BATS_FILE_TMPDIR}/zot_config.json
local oci_data_dir=${BATS_FILE_TMPDIR}/oci
mkdir -p ${zot_root_dir}
mkdir -p ${oci_data_dir}
zot_port=$(get_free_port_for_service "zot")
echo ${zot_port} > ${BATS_FILE_TMPDIR}/zot.port
cat > ${zot_config_file}<<JSON
{
"distSpecVersion": "1.1.1",
"storage": {
"rootDirectory": "${zot_root_dir}"
},
"http": {
"address": "0.0.0.0",
"port": "${zot_port}"
},
"log": {
"level": "debug",
"output": "${BATS_FILE_TMPDIR}/zot.log"
}
}
JSON
git -C ${BATS_FILE_TMPDIR} clone https://github.com/project-zot/helm-charts.git
zot_rel_serve ${ZOT_PATH} ${zot_config_file}
wait_zot_reachable ${zot_port}
}

function teardown() {
# conditionally printing on failure is possible from teardown but not from from teardown_file
cat ${BATS_FILE_TMPDIR}/zot.log
}

function teardown_file() {
zot_stop_all
}

@test "[release] push image" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run skopeo --insecure-policy copy --dest-tls-verify=false \
oci:${TEST_DATA_DIR}/golang:1.20 \
docker://127.0.0.1:${zot_port}/golang:1.20
[ "$status" -eq 0 ]
run curl http://127.0.0.1:${zot_port}/v2/_catalog
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.repositories[]') = '"golang"' ]
run curl http://127.0.0.1:${zot_port}/v2/golang/tags/list
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.tags[]') = '"1.20"' ]
}

@test "[release] pull image" {
local oci_data_dir=${BATS_FILE_TMPDIR}/oci
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run skopeo --insecure-policy copy --src-tls-verify=false \
docker://127.0.0.1:${zot_port}/golang:1.20 \
oci:${oci_data_dir}/golang:1.20
[ "$status" -eq 0 ]
run cat ${BATS_FILE_TMPDIR}/oci/golang/index.json
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests[].annotations."org.opencontainers.image.ref.name"') = '"1.20"' ]
}

@test "[release] push image index" {
# --multi-arch below pushes an image index (containing many images) instead
# of an image manifest (single image)
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run skopeo --insecure-policy copy --format=oci --dest-tls-verify=false --multi-arch=all \
docker://public.ecr.aws/docker/library/busybox:latest \
docker://127.0.0.1:${zot_port}/busybox:latest
[ "$status" -eq 0 ]
run curl http://127.0.0.1:${zot_port}/v2/_catalog
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.repositories[0]') = '"busybox"' ]
run curl http://127.0.0.1:${zot_port}/v2/busybox/tags/list
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.tags[]') = '"latest"' ]
}

@test "[release] pull image index" {
local oci_data_dir=${BATS_FILE_TMPDIR}/oci
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run skopeo --insecure-policy copy --src-tls-verify=false --multi-arch=all \
docker://127.0.0.1:${zot_port}/busybox:latest \
oci:${oci_data_dir}/busybox:latest
[ "$status" -eq 0 ]
run cat ${BATS_FILE_TMPDIR}/oci/busybox/index.json
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests[].annotations."org.opencontainers.image.ref.name"') = '"latest"' ]
run skopeo --insecure-policy --override-arch=arm64 --override-os=linux copy --src-tls-verify=false --multi-arch=all \
docker://127.0.0.1:${zot_port}/busybox:latest \
oci:${oci_data_dir}/busybox:latest
[ "$status" -eq 0 ]
run cat ${BATS_FILE_TMPDIR}/oci/busybox/index.json
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests[].annotations."org.opencontainers.image.ref.name"') = '"latest"' ]
run curl -X DELETE http://127.0.0.1:${zot_port}/v2/busybox/manifests/latest
[ "$status" -eq 0 ]
}

@test "[release] push oras artifact" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
echo "{\"name\":\"foo\",\"value\":\"bar\"}" > config.json
echo "hello world" > artifact.txt
run oras push --plain-http 127.0.0.1:${zot_port}/hello-artifact:v2 \
--config config.json:application/vnd.acme.rocket.config.v1+json artifact.txt:text/plain -d -v
[ "$status" -eq 0 ]
rm -f artifact.txt
rm -f config.json
}

@test "[release] pull oras artifact" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run oras pull --plain-http 127.0.0.1:${zot_port}/hello-artifact:v2 -d -v
[ "$status" -eq 0 ]
grep -q "hello world" artifact.txt
rm -f artifact.txt
}

@test "[release] attach oras artifacts" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
# attach signature
echo "{\"artifact\": \"\", \"signature\": \"pat hancock\"}" > ${BATS_FILE_TMPDIR}/signature.json
run oras attach --disable-path-validation --plain-http 127.0.0.1:${zot_port}/golang:1.20 --artifact-type 'signature/example' ${BATS_FILE_TMPDIR}/signature.json:application/json
[ "$status" -eq 0 ]
# attach sbom
echo "{\"version\": \"0.0.0.0\", \"artifact\": \"'127.0.0.1:${zot_port}/golang:1.20'\", \"contents\": \"good\"}" > ${BATS_FILE_TMPDIR}/sbom.json
run oras attach --disable-path-validation --plain-http 127.0.0.1:${zot_port}/golang:1.20 --artifact-type 'sbom/example' ${BATS_FILE_TMPDIR}/sbom.json:application/json
[ "$status" -eq 0 ]
}

@test "[release] discover oras artifacts" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run oras discover --plain-http --format json 127.0.0.1:${zot_port}/golang:1.20
[ "$status" -eq 0 ]
[ $(echo "$output" | jq -r ".manifests | length") -eq 2 ]
}

@test "[release] add and list tags using oras" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run skopeo --insecure-policy copy --dest-tls-verify=false \
oci:${TEST_DATA_DIR}/golang:1.20 \
docker://127.0.0.1:${zot_port}/oras-tags:1.20
[ "$status" -eq 0 ]
run oras tag --plain-http 127.0.0.1:${zot_port}/oras-tags:1.20 1 new latest
[ "$status" -eq 0 ]
run oras repo tags --plain-http 127.0.0.1:${zot_port}/oras-tags
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 4 ]
[ "${lines[-1]}" == "new" ]
[ "${lines[-2]}" == "latest" ]
[ "${lines[-3]}" == "1.20" ]
[ "${lines[-4]}" == "1" ]
run oras repo tags --plain-http --last new 127.0.0.1:${zot_port}/oras-tags
[ "$status" -eq 0 ]
echo "$output"
[ -z $output ]
run oras repo tags --plain-http --last latest 127.0.0.1:${zot_port}/oras-tags
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 1 ]
[ "${lines[-1]}" == "new" ]
run oras repo tags --plain-http --last "1.20" 127.0.0.1:${zot_port}/oras-tags
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 2 ]
[ "${lines[-2]}" == "latest" ]
[ "${lines[-1]}" == "new" ]
run oras repo tags --plain-http --last "1" 127.0.0.1:${zot_port}/oras-tags
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 3 ]
[ "${lines[-3]}" == "1.20" ]
[ "${lines[-2]}" == "latest" ]
[ "${lines[-1]}" == "new" ]
}

@test "[release] push helm chart" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run helm package ${BATS_FILE_TMPDIR}/helm-charts/charts/zot -d ${BATS_FILE_TMPDIR}
[ "$status" -eq 0 ]
local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
run helm push ${BATS_FILE_TMPDIR}/zot-${chart_version}.tgz oci://localhost:${zot_port}/zot-chart
[ "$status" -eq 0 ]
}

@test "[release] pull helm chart" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
run helm pull oci://localhost:${zot_port}/zot-chart/zot --version ${chart_version} -d ${BATS_FILE_TMPDIR}
[ "$status" -eq 0 ]
}

@test "[release] push image with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl registry set localhost:${zot_port} --tls disabled
[ "$status" -eq 0 ]
run regctl image copy ocidir://${TEST_DATA_DIR}/golang:1.20 localhost:${zot_port}/test-regclient
[ "$status" -eq 0 ]
}

@test "[release] pull image with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl image copy localhost:${zot_port}/test-regclient ocidir://${TEST_DATA_DIR}/golang:1.20
[ "$status" -eq 0 ]
}

@test "[release] list repositories with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl repo ls localhost:${zot_port}
[ "$status" -eq 0 ]

found=0
for i in "${lines[@]}"
do

if [ "$i" = 'test-regclient' ]; then
found=1
fi
done
[ "$found" -eq 1 ]

run regctl repo ls --limit 2 localhost:${zot_port}
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 2 ]
[ "${lines[-2]}" == "busybox" ]
[ "${lines[-1]}" == "golang" ]

run regctl repo ls --last busybox --limit 1 localhost:${zot_port}
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 1 ]
[ "${lines[-1]}" == "golang" ]
}

@test "[release] list image tags with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl tag ls localhost:${zot_port}/test-regclient
[ "$status" -eq 0 ]

found=0
for i in "${lines[@]}"
do

if [ "$i" = 'latest' ]; then
found=1
fi
done
[ "$found" -eq 1 ]
}

@test "[release] push manifest with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
manifest=$(regctl manifest get localhost:${zot_port}/test-regclient --format=raw-body)
run regctl manifest put localhost:${zot_port}/test-regclient:1.0.0 --format oci --content-type application/vnd.oci.image.manifest.v1+json --format oci <<JSON
$manifest
JSON
[ "$status" -eq 0 ]
}

@test "[release] pull manifest with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl manifest get localhost:${zot_port}/test-regclient
[ "$status" -eq 0 ]
}

@test "[release] pull manifest with docker client" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run docker pull localhost:${zot_port}/test-regclient
[ "$status" -eq 0 ]
}

@test "[release] pull manifest with crictl" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run crictl pull localhost:${zot_port}/test-regclient
[ "$status" -eq 0 ]
}

@test "[release] push OCI artifact with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl artifact put localhost:${zot_port}/artifact:demo <<TXT
this is an artifact
TXT
[ "$status" -eq 0 ]
}

@test "[release] pull OCI artifact with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl manifest get localhost:${zot_port}/artifact:demo
[ "$status" -eq 0 ]
run regctl artifact get localhost:${zot_port}/artifact:demo
[ "$status" -eq 0 ]
[ "${lines[-1]}" == "this is an artifact" ]
}

@test "[release] push OCI artifact references with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl artifact put localhost:${zot_port}/manifest-ref:demo <<TXT
test artifact
TXT
[ "$status" -eq 0 ]
run regctl artifact list localhost:${zot_port}/manifest-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
run regctl artifact put --annotation demo=true --annotation format=oci --artifact-type "application/vnd.example.icecream.v1" --subject localhost:${zot_port}/manifest-ref:demo << TXT
test reference
TXT
[ "$status" -eq 0 ]
# with artifact media-type
run regctl artifact put localhost:${zot_port}/artifact-ref:demo <<TXT
test artifact
TXT
[ "$status" -eq 0 ]
run regctl artifact list localhost:${zot_port}/artifact-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
run regctl artifact put --annotation demo=true --annotation format=oci --artifact-type "application/vnd.example.icecream.v1" --subject localhost:${zot_port}/artifact-ref:demo << TXT
test reference
TXT
[ "$status" -eq 0 ]
}

@test "[release] pull OCI artifact references with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl artifact list localhost:${zot_port}/manifest-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
run regctl artifact list --filter-artifact-type "application/vnd.example.icecream.v1" localhost:${zot_port}/manifest-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
run regctl artifact list --filter-artifact-type "application/invalid" localhost:${zot_port}/manifest-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
# with artifact media-type
run regctl artifact list localhost:${zot_port}/artifact-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
run regctl artifact list --filter-artifact-type "application/vnd.example.icecream.v1" localhost:${zot_port}/artifact-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
run regctl artifact list --filter-artifact-type "application/invalid" localhost:${zot_port}/artifact-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
}

@test "[release] push docker image" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
cat > Dockerfile <<DOCKERFILE
FROM ghcr.io/project-zot/test-images/busybox-docker:1.37
RUN echo "hello world" > /testfile
DOCKERFILE
docker build -f Dockerfile . -t localhost:${zot_port}/test
run docker push localhost:${zot_port}/test
[ "$status" -eq 1 ]
run docker pull localhost:${zot_port}/test
[ "$status" -eq 1 ]
}

@test "[upgrade] upgrade to new binary" {
zot_stop_all
local zot_config_file=${BATS_FILE_TMPDIR}/zot_config.json
local zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
zot_serve ${ZOT_PATH} ${zot_config_file}
wait_zot_reachable ${zot_port}
}

@test "[new] push image" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run skopeo --insecure-policy copy --dest-tls-verify=false \
oci:${TEST_DATA_DIR}/golang:1.20 \
docker://127.0.0.1:${zot_port}/golang:1.20
[ "$status" -eq 0 ]
run curl http://127.0.0.1:${zot_port}/v2/_catalog
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.repositories[]') = '"golang"' ]
run curl http://127.0.0.1:${zot_port}/v2/golang/tags/list
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.tags[]') = '"1.20"' ]
}

@test "[new] pull image" {
local oci_data_dir=${BATS_FILE_TMPDIR}/oci
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run skopeo --insecure-policy copy --src-tls-verify=false \
docker://127.0.0.1:${zot_port}/golang:1.20 \
oci:${oci_data_dir}/golang:1.20
[ "$status" -eq 0 ]
run cat ${BATS_FILE_TMPDIR}/oci/golang/index.json
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests[].annotations."org.opencontainers.image.ref.name"') = '"1.20"' ]
}

@test "[new] push image index" {
# --multi-arch below pushes an image index (containing many images) instead
# of an image manifest (single image)
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run skopeo --insecure-policy copy --format=oci --dest-tls-verify=false --multi-arch=all \
docker://public.ecr.aws/docker/library/busybox:latest \
docker://127.0.0.1:${zot_port}/busybox:latest
[ "$status" -eq 0 ]
run curl http://127.0.0.1:${zot_port}/v2/_catalog
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.repositories[0]') = '"busybox"' ]
run curl http://127.0.0.1:${zot_port}/v2/busybox/tags/list
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.tags[]') = '"latest"' ]
}

@test "[new] pull image index" {
local oci_data_dir=${BATS_FILE_TMPDIR}/oci
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run skopeo --insecure-policy copy --src-tls-verify=false --multi-arch=all \
docker://127.0.0.1:${zot_port}/busybox:latest \
oci:${oci_data_dir}/busybox:latest
[ "$status" -eq 0 ]
run cat ${BATS_FILE_TMPDIR}/oci/busybox/index.json
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests[].annotations."org.opencontainers.image.ref.name"') = '"latest"' ]
run skopeo --insecure-policy --override-arch=arm64 --override-os=linux copy --src-tls-verify=false --multi-arch=all \
docker://127.0.0.1:${zot_port}/busybox:latest \
oci:${oci_data_dir}/busybox:latest
[ "$status" -eq 0 ]
run cat ${BATS_FILE_TMPDIR}/oci/busybox/index.json
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests[].annotations."org.opencontainers.image.ref.name"') = '"latest"' ]
run curl -X DELETE http://127.0.0.1:${zot_port}/v2/busybox/manifests/latest
[ "$status" -eq 0 ]
}

@test "[new] push oras artifact" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
echo "{\"name\":\"foo\",\"value\":\"bar\"}" > config.json
echo "hello world" > artifact.txt
run oras push --plain-http 127.0.0.1:${zot_port}/hello-artifact:v2 \
--config config.json:application/vnd.acme.rocket.config.v1+json artifact.txt:text/plain -d -v
[ "$status" -eq 0 ]
rm -f artifact.txt
rm -f config.json
}

@test "[new] pull oras artifact" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run oras pull --plain-http 127.0.0.1:${zot_port}/hello-artifact:v2 -d -v
[ "$status" -eq 0 ]
grep -q "hello world" artifact.txt
rm -f artifact.txt
}

@test "[new] attach oras artifacts" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
# attach signature
echo "{\"artifact\": \"\", \"signature\": \"pat hancock\"}" > ${BATS_FILE_TMPDIR}/signature.json
run oras attach --disable-path-validation --plain-http 127.0.0.1:${zot_port}/golang:1.20 --artifact-type 'signature/example' ${BATS_FILE_TMPDIR}/signature.json:application/json
[ "$status" -eq 0 ]
# attach sbom
echo "{\"version\": \"0.0.0.0\", \"artifact\": \"'127.0.0.1:${zot_port}/golang:1.20'\", \"contents\": \"good\"}" > ${BATS_FILE_TMPDIR}/sbom.json
run oras attach --disable-path-validation --plain-http 127.0.0.1:${zot_port}/golang:1.20 --artifact-type 'sbom/example' ${BATS_FILE_TMPDIR}/sbom.json:application/json
[ "$status" -eq 0 ]
}

@test "[new] discover oras artifacts" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run oras discover --plain-http --format json 127.0.0.1:${zot_port}/golang:1.20
[ "$status" -eq 0 ]
[ $(echo "$output" | jq -r ".manifests | length") -eq 2 ]
}

@test "[new] add and list tags using oras" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run skopeo --insecure-policy copy --dest-tls-verify=false \
oci:${TEST_DATA_DIR}/golang:1.20 \
docker://127.0.0.1:${zot_port}/oras-tags:1.20
[ "$status" -eq 0 ]
run oras tag --plain-http 127.0.0.1:${zot_port}/oras-tags:1.20 1 new latest
[ "$status" -eq 0 ]
run oras repo tags --plain-http 127.0.0.1:${zot_port}/oras-tags
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 4 ]
[ "${lines[-1]}" == "new" ]
[ "${lines[-2]}" == "latest" ]
[ "${lines[-3]}" == "1.20" ]
[ "${lines[-4]}" == "1" ]
run oras repo tags --plain-http --last new 127.0.0.1:${zot_port}/oras-tags
[ "$status" -eq 0 ]
echo "$output"
[ -z $output ]
run oras repo tags --plain-http --last latest 127.0.0.1:${zot_port}/oras-tags
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 1 ]
[ "${lines[-1]}" == "new" ]
run oras repo tags --plain-http --last "1.20" 127.0.0.1:${zot_port}/oras-tags
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 2 ]
[ "${lines[-2]}" == "latest" ]
[ "${lines[-1]}" == "new" ]
run oras repo tags --plain-http --last "1" 127.0.0.1:${zot_port}/oras-tags
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 3 ]
[ "${lines[-3]}" == "1.20" ]
[ "${lines[-2]}" == "latest" ]
[ "${lines[-1]}" == "new" ]
}

@test "[new] push helm chart" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run helm package ${BATS_FILE_TMPDIR}/helm-charts/charts/zot -d ${BATS_FILE_TMPDIR}
[ "$status" -eq 0 ]
local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
run helm push ${BATS_FILE_TMPDIR}/zot-${chart_version}.tgz oci://localhost:${zot_port}/zot-chart
[ "$status" -eq 0 ]
}

@test "[new] pull helm chart" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
run helm pull oci://localhost:${zot_port}/zot-chart/zot --version ${chart_version} -d ${BATS_FILE_TMPDIR}
[ "$status" -eq 0 ]
}

@test "[new] push image with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl registry set localhost:${zot_port} --tls disabled
[ "$status" -eq 0 ]
run regctl image copy ocidir://${TEST_DATA_DIR}/golang:1.20 localhost:${zot_port}/test-regclient
[ "$status" -eq 0 ]
}

@test "[new] pull image with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl image copy localhost:${zot_port}/test-regclient ocidir://${TEST_DATA_DIR}/golang:1.20
[ "$status" -eq 0 ]
}

@test "[new] list repositories with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl repo ls localhost:${zot_port}
[ "$status" -eq 0 ]

found=0
for i in "${lines[@]}"
do

if [ "$i" = 'test-regclient' ]; then
found=1
fi
done
[ "$found" -eq 1 ]

run regctl repo ls --limit 2 localhost:${zot_port}
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 2 ]
[ "${lines[-2]}" == "busybox" ]
[ "${lines[-1]}" == "golang" ]

run regctl repo ls --last busybox --limit 1 localhost:${zot_port}
[ "$status" -eq 0 ]
echo "$output"
[ $(echo "$output" | wc -l) -eq 1 ]
[ "${lines[-1]}" == "golang" ]
}

@test "[new] list image tags with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl tag ls localhost:${zot_port}/test-regclient
[ "$status" -eq 0 ]

found=0
for i in "${lines[@]}"
do

if [ "$i" = 'latest' ]; then
found=1
fi
done
[ "$found" -eq 1 ]
}

@test "[new] push manifest with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
manifest=$(regctl manifest get localhost:${zot_port}/test-regclient --format=raw-body)
run regctl manifest put localhost:${zot_port}/test-regclient:1.0.0 --format oci --content-type application/vnd.oci.image.manifest.v1+json --format oci <<JSON
$manifest
JSON
[ "$status" -eq 0 ]
}

@test "[new] pull manifest with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl manifest get localhost:${zot_port}/test-regclient
[ "$status" -eq 0 ]
}

@test "[new] pull manifest with docker client" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run docker pull localhost:${zot_port}/test-regclient
[ "$status" -eq 0 ]
}

@test "[new] pull manifest with crictl" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run crictl pull localhost:${zot_port}/test-regclient
[ "$status" -eq 0 ]
}

@test "[new] push OCI artifact with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl artifact put localhost:${zot_port}/artifact:demo <<TXT
this is an artifact
TXT
[ "$status" -eq 0 ]
}

@test "[new] pull OCI artifact with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl manifest get localhost:${zot_port}/artifact:demo
[ "$status" -eq 0 ]
run regctl artifact get localhost:${zot_port}/artifact:demo
[ "$status" -eq 0 ]
[ "${lines[-1]}" == "this is an artifact" ]
}

@test "[new] push OCI artifact references with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl artifact put localhost:${zot_port}/manifest-ref:demo <<TXT
test artifact
TXT
[ "$status" -eq 0 ]
run regctl artifact list localhost:${zot_port}/manifest-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
run regctl artifact put --annotation demo=true --annotation format=oci --artifact-type "application/vnd.example.icecream.v1" --subject localhost:${zot_port}/manifest-ref:demo << TXT
test reference
TXT
[ "$status" -eq 0 ]
# with artifact media-type
run regctl artifact put localhost:${zot_port}/artifact-ref:demo <<TXT
test artifact
TXT
[ "$status" -eq 0 ]
run regctl artifact list localhost:${zot_port}/artifact-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
run regctl artifact put --annotation demo=true --annotation format=oci --artifact-type "application/vnd.example.icecream.v1" --subject localhost:${zot_port}/artifact-ref:demo << TXT
test reference
TXT
[ "$status" -eq 0 ]
}

@test "[new] pull OCI artifact references with regclient" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
run regctl artifact list localhost:${zot_port}/manifest-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
run regctl artifact list --filter-artifact-type "application/vnd.example.icecream.v1" localhost:${zot_port}/manifest-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
run regctl artifact list --filter-artifact-type "application/invalid" localhost:${zot_port}/manifest-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
# with artifact media-type
run regctl artifact list localhost:${zot_port}/artifact-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
run regctl artifact list --filter-artifact-type "application/vnd.example.icecream.v1" localhost:${zot_port}/artifact-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 1 ]
run regctl artifact list --filter-artifact-type "application/invalid" localhost:${zot_port}/artifact-ref:demo --format raw-body
[ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.manifests | length') -eq 0 ]
}

@test "[new] push docker image" {
zot_port=`cat ${BATS_FILE_TMPDIR}/zot.port`
cat > Dockerfile <<DOCKERFILE
FROM ghcr.io/project-zot/test-images/busybox-docker:1.37
RUN echo "hello world" > /testfile
DOCKERFILE
docker build -f Dockerfile . -t localhost:${zot_port}/test
run docker push localhost:${zot_port}/test
[ "$status" -eq 1 ]
run docker pull localhost:${zot_port}/test
[ "$status" -eq 1 ]
}
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file contains 719 lines that are almost entirely duplicated in upgrade_minimal.bats, with only two lines differing (lines 54 and 394). This creates a significant maintenance burden as any updates to test logic must be made in both files. Consider:

  1. Creating a shared test file with parameterized functions, or
  2. Using BATS parameterized tests to run the same tests with different configurations, or
  3. Extracting common test functions into a shared helper file that both files can source.

Copilot uses AI. Check for mistakes.
}
JSON
git -C ${BATS_FILE_TMPDIR} clone https://github.com/project-zot/helm-charts.git
zot_rel_serve ${ZOT_PATH} ${zot_config_file}
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The zot_rel_serve function is called with ${ZOT_PATH} as the first argument, but this function ignores the first argument when both arguments are provided (it only uses the first argument as config_file if there's no second argument). The function downloads its own binary from GitHub releases and doesn't use the passed ZOT_PATH. This call should be zot_rel_serve ${zot_config_file} to match the function's intended usage pattern.

Suggested change
zot_rel_serve ${ZOT_PATH} ${zot_config_file}
zot_rel_serve ${zot_config_file}

Copilot uses AI. Check for mistakes.
}

function teardown() {
# conditionally printing on failure is possible from teardown but not from from teardown_file
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment contains a duplicate word: "from from" should be "from".

Suggested change
# conditionally printing on failure is possible from teardown but not from from teardown_file
# conditionally printing on failure is possible from teardown but not from teardown_file

Copilot uses AI. Check for mistakes.
}

function teardown() {
# conditionally printing on failure is possible from teardown but not from from teardown_file
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment contains a duplicate word: "from from" should be "from".

Suggested change
# conditionally printing on failure is possible from teardown but not from from teardown_file
# conditionally printing on failure is possible from teardown but not from teardown_file

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +37
function zot_rel_serve() {
local config_file
if [ -n "${2}" ]; then
config_file=${2}
else
config_file=${1}
fi
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter handling in this function is confusing and inconsistent with the zot_serve function above it. When both parameters are provided, it uses ${2} as the config file and ignores ${1}. This creates an inconsistent API where calling zot_rel_serve ${binary} ${config} ignores the binary parameter. Consider either:

  1. Removing the first parameter entirely since the function always downloads its own binary, making it zot_rel_serve ${config_file}, or
  2. Using the first parameter as the binary path if provided, downloading only as a fallback.

Copilot uses AI. Check for mistakes.
local zot_path=${BATS_FILE_TMPDIR}/zot-rel-${OS}-${ARCH}

if [ ! -f "${zot_path}" ]; then
curl -L -o "${zot_path}" https://github.com/project-zot/zot/releases/latest/download/zot-${OS}-${ARCH}
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The binary is downloaded from GitHub without any integrity verification (e.g., checksum validation). While this is a test environment, consider adding checksum verification to ensure the downloaded binary hasn't been tampered with, especially since this binary will be executed with the downloaded file's permissions.

Suggested change
curl -L -o "${zot_path}" https://github.com/project-zot/zot/releases/latest/download/zot-${OS}-${ARCH}
curl -L -o "${zot_path}" https://github.com/project-zot/zot/releases/latest/download/zot-${OS}-${ARCH}
# Download checksum file and verify integrity
checksum_url="https://github.com/project-zot/zot/releases/latest/download/sha256sums.txt"
checksum_file="${BATS_FILE_TMPDIR}/zot-sha256sums.txt"
curl -L -o "${checksum_file}" "${checksum_url}"
expected_sum=$(grep "zot-${OS}-${ARCH}" "${checksum_file}" | awk '{print $1}')
if [ -z "${expected_sum}" ]; then
echo "ERROR: Could not find checksum for zot-${OS}-${ARCH} in sha256sums.txt"
exit 1
fi
actual_sum=$(sha256sum "${zot_path}" | awk '{print $1}')
if [ "${expected_sum}" != "${actual_sum}" ]; then
echo "ERROR: Checksum verification failed for zot-${OS}-${ARCH}"
exit 1
fi

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +63
curl -L -o "${zot_path}" https://github.com/project-zot/zot/releases/latest/download/zot-${OS}-${ARCH}-minimal
chmod +x "${zot_path}"
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The binary is downloaded from GitHub without any integrity verification (e.g., checksum validation). While this is a test environment, consider adding checksum verification to ensure the downloaded binary hasn't been tampered with, especially since this binary will be executed with the downloaded file's permissions.

Copilot uses AI. Check for mistakes.
Fixes project-zot#3601

Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat]: Add a upgrade test in our ci/cd pipeline

1 participant