Skip to content

Commit

Permalink
Refactor image tests with new docker_lib.sh
Browse files Browse the repository at this point in the history
Signed-off-by: James Blair <mail@jamesblair.net>
  • Loading branch information
jmhbnz committed Apr 2, 2023
1 parent 7abf342 commit 500f60f
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 89 deletions.
180 changes: 180 additions & 0 deletions scripts/docker_lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/env bash

# Source of truth, these should not be modified elsewhere
readonly IMAGE_ARCH_LIST="amd64 arm64 ppc64le s390x"
readonly CONTAINER_REGISTRY_LIST="quay.io/coreos gcr.io/etcd-development"

function compareSemanticVersions() {

v1=$(echo "$1" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+')
v2=$(echo "$2" | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+')

if [ -z "$v1" ] || [ -z "$v2" ]; then
log_error "ERROR: Invalid version string(s) provided ${v1} and ${v2}."
exit 1
fi

read -ra ver1 <<< "${v1//./ }"
read -ra ver2 <<< "${v2//./ }"

len=${#ver1[@]}
if ((len < ${#ver2[@]})); then
len=${#ver2[@]}
fi

# compare each component of the versions
for ((i=0; i<len; i++)); do

# set missing components to 0
num1=${ver1[i]:-0}
num2=${ver2[i]:-0}

# compare the components
if ((num1 < num2)); then
log_info "INFO: ${v2} is greater than ${v1}."
return 0
elif ((num1 > num2)); then
log_info "INFO: ${v1} is greater than ${v2}."
return 0
fi
done

# if we reach this point, the versions are equal
log_info "INFO: ${v1} and ${v2} are equal."
return 0
}

function checkContainerEngine() {

# Validate args
if [ "${#}" -ne 1 ]; then
log_error "ERROR: checkContainerEngine requires exactly one parameter."
exit 1
fi

ENGINE="${1}"

# Etcd releases require a container engine like docker
if ! command -v "${ENGINE}" >/dev/null; then
log_error "ERROR: Cannot find container engine ${ENGINE}."
exit 1
fi

# Etcd releases require a modern version of docker
# TODO Consider adding support for a recent version of podman
MINIMUM_DOCKER_VERSION="20.10.0"
CURRENT_VERSION=$("${ENGINE}" version --format '{{.Server.Version}}' | cut -d '+' -f1)

if compareSemanticVersions "${MINIMUM_DOCKER_VERSION}" "${CURRENT_VERSION}"; then
log_success "SUCCESS: ${ENGINE} version ${CURRENT_VERSION} is >= minimum version ${MINIMUM_DOCKER_VERSION}."
else
log_error "ERROR: ${ENGINE} version ${CURRENT_VERSION} is less than the minimum version ${MINIMUM_DOCKER_VERSION}."
exit 1
fi

# Etcd releases require a linux host
if [[ $(go env GOOS) == "darwin" ]]; then
log_error "ERROR: Please use linux machine for release builds."
exit 1
fi
}

function checkImageExists() {

# Validate args
if [ "${#}" -ne 1 ]; then
log_error "ERROR: checkImageExists requires exactly one parameter."
exit 1
fi

IMAGE="${1}"
log_info "INFO: Checking image ${IMAGE} is present locally."
if [[ "$(docker images --quiet "${IMAGE}" 2> /dev/null)" == "" ]]; then
log_error "ERROR: Image ${IMAGE} not present locally."
exit 1
fi

log_success "SUCCESS: Image ${IMAGE} exists locally."
}


function checkImageHasArch() {

# Validate args
if [ "${#}" -ne 2 ]; then
log_error "ERROR: checkImageHasArch requires exactly two parameters."
exit 1
fi

IMAGE="${1}"
EXPECTED_ARCH="${2}"

# Validate the image exists
checkImageExists "${IMAGE}"

# Validate that manifest exists
MANIFEST=$(docker manifest inspect "${IMAGE}")
if [ -z "${MANIFEST}" ]; then
log_error "ERROR: Manifest not found for image ${IMAGE}."
exit 1
fi

# Check that expected architecture is included
PLATFORMS=$(echo "${MANIFEST}" | sed -n 's/.*"platform":{\(.*\)},/\1/p' | tr -d ' ')
if [[ "${PLATFORMS}" != *"${EXPECTED_ARCH}"* ]]; then
log_error "ERROR: Image ${IMAGE} does not contain the expected architecture ${EXPECTED_ARCH}."
exit 1
fi

log_success "SUCCESS: Image ${IMAGE} contains the expected architecture ${EXPECTED_ARCH}."
return 0
}

function buildMultiArchImage() {

# Validate args
if [ "${#}" -ne 1 ]; then
log_error "ERROR: buildMultiArchImage requires exactly one parameter."
exit 1
fi

RELEASE_VERSION="${1}"
IFS=' ' read -ra image_arch_array <<< "${IMAGE_ARCH_LIST}"
IFS=' ' read -ra registries_array <<< "${CONTAINER_REGISTRY_LIST}"

for TARGET_ARCH in "${image_arch_array[@]}"; do
for REGISTRY in "${registries_array[@]}"; do

# Validate required images exist
checkImageExists "${REGISTRY}/etcd:${RELEASE_VERSION}"
checkImageExists "${REGISTRY}/etcd:${RELEASE_VERSION}-${TARGET_ARCH}"

# Update the manifest
log_info "INFO: Updating manifest ${REGISTRY}/etcd:${RELEASE_VERSION} with ${REGISTRY}/etcd:${RELEASE_VERSION}-${TARGET_ARCH}."
docker manifest create --amend "${REGISTRY}/etcd:${RELEASE_VERSION}" "${REGISTRY}/etcd:${RELEASE_VERSION}-${TARGET_ARCH}"
docker manifest annotate "${REGISTRY}/etcd:${RELEASE_VERSION}" "${REGISTRY}/etcd:${RELEASE_VERSION}-${TARGET_ARCH}" --arch "${TARGET_ARCH}"
done
done
}

function startContainer {

# Validate args
if [ "${#}" -ne 2 ]; then
log_error "ERROR: startContainer requires exactly two parameters."
exit 1
fi

CONTAINER_NAME="${1}"
IMAGE="${2}"

# Run docker in the background
log_info "INFO: Starting container named: ${CONTAINER_NAME} with image: ${IMAGE}."
docker run --detach --rm --name "${CONTAINER_NAME}" "${IMAGE}"

# Wait for etcd daemon to bootstrap
sleep 5

# Cleanup in case of failures
trap 'docker stop "${CONTAINER_NAME}"' EXIT
}
28 changes: 13 additions & 15 deletions scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set -o nounset
set -o pipefail

source ./scripts/test_lib.sh
source ./scripts/docker_lib.sh
source ./scripts/release_mod.sh

DRY_RUN=${DRY_RUN:-true}
Expand Down Expand Up @@ -72,13 +73,8 @@ main() {
log_callout "REPOSITORY=${REPOSITORY}"
log_callout ""

# Required to enable 'docker manifest ...'
export DOCKER_CLI_EXPERIMENTAL=enabled

if ! command -v docker >/dev/null; then
log_error "cannot find docker"
exit 1
fi
# Releases can't proceed without docker
checkContainerEngine "docker"

# Expected umask for etcd release artifacts
umask 022
Expand Down Expand Up @@ -258,16 +254,18 @@ main() {
log_warning "login failed, retrying"
done

log_callout "Creating manifest-list (multi-image)..."

for TARGET_ARCH in "amd64" "arm64" "ppc64le" "s390x"; do
maybe_run docker manifest create --amend "quay.io/coreos/etcd:${RELEASE_VERSION}" "quay.io/coreos/etcd:${RELEASE_VERSION}-${TARGET_ARCH}"
maybe_run docker manifest annotate "quay.io/coreos/etcd:${RELEASE_VERSION}" "quay.io/coreos/etcd:${RELEASE_VERSION}-${TARGET_ARCH}" --arch "${TARGET_ARCH}"

maybe_run docker manifest create --amend "gcr.io/etcd-development/etcd:${RELEASE_VERSION}" "gcr.io/etcd-development/etcd:${RELEASE_VERSION}-${TARGET_ARCH}"
maybe_run docker manifest annotate "gcr.io/etcd-development/etcd:${RELEASE_VERSION}" "gcr.io/etcd-development/etcd:${RELEASE_VERSION}-${TARGET_ARCH}" --arch "${TARGET_ARCH}"
IFS=' ' read -ra image_arch_array <<< "${IMAGE_ARCH_LIST}"
IFS=' ' read -ra registries_array <<< "${CONTAINER_REGISTRY_LIST}"
for TARGET_ARCH in "${image_arch_array[@]}"; do
for REGISTRY in "${registries_array[@]}"; do
log_callout "Pushing container images to ${REGISTRY} ${RELEASE_VERSION}-${TARGET_ARCH}"
maybe_run docker push "${REGISTRY}/etcd:${RELEASE_VERSION}-${TARGET_ARCH}"
done
done

log_callout "Creating manifest-list (multi-image)..."
maybe_run buildMultiArchImage "${RELEASE_VERSION}"

log_callout "Pushing container manifest list to quay.io ${RELEASE_VERSION}"
maybe_run docker manifest push "quay.io/coreos/etcd:${RELEASE_VERSION}"

Expand Down
109 changes: 35 additions & 74 deletions scripts/test_images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,108 +6,69 @@ IFS=$'\n\t'

source ./scripts/test_lib.sh
source ./scripts/build_lib.sh
source ./scripts/docker_lib.sh

function startContainer {
# run docker in the background
docker run -d --rm --name "${RUN_NAME}" "${IMAGE}"

# wait for etcd daemon to bootstrap
sleep 5
}

function runVersionCheck {
Out=$(docker run --rm "${IMAGE}" "${@}")
foundVersion=$(echo "$Out" | head -1 | rev | cut -d" " -f 1 | rev )
function runVersionCheck() {
out=$(docker exec "${1}" "${@:2}")
foundVersion=$(echo "${out}" | head -1 | rev | cut -d" " -f 1 | rev )
if [[ "${foundVersion}" != "${VERSION}" ]]; then
echo "error: Invalid Version. Got $foundVersion, expected $VERSION. Error: $Out"
log_error "ERROR: Invalid version. Got ${foundVersion}, expected ${VERSION}. Error: ${out}"
exit 1
fi
}

function checkImageHasArch() {
IMAGE="${1}"
EXPECTED_ARCH="${2}"

MANIFEST=$(docker manifest inspect "${IMAGE}")

# Check that manifest exists
if [ -z "${MANIFEST}" ]; then
echo "ERROR: Manifest not found for image ${IMAGE}"
exit 1
fi

PLATFORMS=$(echo "${MANIFEST}" | sed -n 's/.*"platform":{\(.*\)},/\1/p' | tr -d ' ')

# Check that expected architecture is included
if [[ "${PLATFORMS}" != *"${EXPECTED_ARCH}"* ]]; then
echo "ERROR: Image ${IMAGE} does not contain the expected architecture ${EXPECTED_ARCH}"
exit 1
fi

echo "SUCCESS: Image ${IMAGE} contains the expected architecture ${EXPECTED_ARCH}"
return 0
}

# Can't proceed without docker
if ! command -v docker >/dev/null; then
log_error "cannot find docker"
exit 1
fi

# You can't run darwin binaries in linux containers
if [[ $(go env GOOS) == "darwin" ]]; then
echo "Please use linux machine for release builds."
exit 1
fi
# Tests can't proceed without docker
checkContainerEngine "docker"

# Pick defaults based on release workflow
ARCH=$(go env GOARCH)
REPOSITORY=${REPOSITORY:-"gcr.io/etcd-development/etcd"}
if [ -n "$VERSION" ]; then
if [ -n "${VERSION}" ]; then
# Expected Format: v3.6.99-amd64
TAG=v"${VERSION}"-"${ARCH}"
else
echo "Terminating test, VERSION not supplied"
log_error "ERROR: Terminating test, VERSION not supplied"
exit 1
fi
IMAGE=${IMAGE:-"${REPOSITORY}:${TAG}"}

# ETCD related values
RUN_NAME="test_etcd"
# Etcd related values
CONTAINER_NAME="test_etcd"
KEY="foo"
VALUE="bar"

if [[ "$(docker images -q "${IMAGE}" 2> /dev/null)" == "" ]]; then
echo "${IMAGE} not present locally"
exit 1
fi
# Start an etcd container
startContainer "${CONTAINER_NAME}" "${IMAGE}"

# Version check
runVersionCheck "/usr/local/bin/etcd" "--version"
runVersionCheck "/usr/local/bin/etcdctl" "version"
runVersionCheck "/usr/local/bin/etcdutl" "version"
# Test versions in container
runVersionCheck "${CONTAINER_NAME}" "etcd" "--version"
runVersionCheck "${CONTAINER_NAME}" "etcdctl" "version"
runVersionCheck "${CONTAINER_NAME}" "etcdutl" "version"

startContainer
# stop container
trap 'docker stop "${RUN_NAME}"' EXIT


# Put/Get check
PUT=$(docker exec "${RUN_NAME}" /usr/local/bin/etcdctl put "${KEY}" "${VALUE}")
# Test etcd kv working
PUT=$(docker exec "${CONTAINER_NAME}" etcdctl put "${KEY}" "${VALUE}")
if [ "${PUT}" != "OK" ]; then
echo "Problem with Putting in etcd"
log_error "ERROR: Etcd PUT test failed in container ${CONTAINER_NAME}. Got ${PUT}"
exit 1
fi

GET=$(docker exec "${RUN_NAME}" /usr/local/bin/etcdctl get "$KEY" --print-value-only)
GET=$(docker exec "${CONTAINER_NAME}" etcdctl get "${KEY}" --print-value-only)
if [ "${GET}" != "${VALUE}" ]; then
echo "Problem with getting foo bar in etcd. Got ${GET}"
log_error "ERROR: Etcd GET test failed in container ${CONTAINER_NAME}. Got ${GET}"
exit 1
fi

echo "Succesfully tested etcd local image ${TAG}"
# Test we can assemble the multi arch etcd image
buildMultiArchImage "v${VERSION}"

# Test local image manifest contains all expected architectures
if [ -n "${IMAGE_ARCH_LIST}" ]; then
for TARGET_ARCH in ${IMAGE_ARCH_LIST}; do
checkImageHasArch "${REPOSITORY}:${VERSION}" "${TARGET_ARCH}"
done
else
log_error "ERROR: IMAGE_ARCH_LIST not populated."
exit 1
fi

# Test image manifest contains all expected architectures
for TARGET_ARCH in "amd64" "arm64" "ppc64le" "s390x"; do
checkImageHasArch "${REPOSITORY}:${VERSION}" "${TARGET_ARCH}"
done
log_success "SUCCESS: Etcd local image tests for ${IMAGE} passed."

0 comments on commit 500f60f

Please sign in to comment.