mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
* fix(docker): parameterize image with CCS_IMAGE env so smoke-test exercises new digest
docker/compose.yaml hardcoded image: ghcr.io/kaitranntt/ccs:latest, causing
network-contract.sh to always use the old published image regardless of what
IMAGE_OVERRIDE was passed. Switching to \${CCS_IMAGE:-ghcr.io/kaitranntt/ccs:latest}
lets CI pass CCS_IMAGE=<digest> so the smoke-test actually exercises the
just-built image. Default end-user behaviour is unchanged.
Resolves REV4 (reviewer loop 2, PR #1261).
* fix(test): image-size.sh fails loudly on manifest inspection failure
The --platform branch previously exited 0 when imagetools inspect returned
empty or zero bytes, silently passing the budget check. A broken image
could reach production undetected if buildx had any inspection issue.
Changed to exit 1 with a clear diagnostic message explaining possible causes
(old buildx, manifest format mismatch, image not yet pushed). No --allow-inspect-failure
escape hatch is provided; CI must fix the root cause.
Resolves REV5 (reviewer loop 2, PR #1261).
* test(docker): cover --platform branch in image-size-logic tests
Prior tests only exercised the local docker image inspect path. The
--platform branch (imagetools inspect) had zero coverage, meaning the
REV5 silent-pass regression would not have been caught by CI.
Added 5 mock-based test cases for the --platform branch:
- pass when platform-scoped compressed size < budget
- fail when platform-scoped compressed size > budget
- fail (exit 1) when imagetools inspect errors (REV5 guard)
- fail (exit 1) when reported size is "0" (REV5 guard)
- fail (exit 1) when size output is empty (REV5 guard)
Mocks follow the existing pattern (override docker in PATH with a temp
wrapper), extended to handle buildx imagetools inspect subcommand.
Resolves REV6 (reviewer loop 2, PR #1261).
* fix(test): compose-parity image_name() handles \${VAR:-default} syntax
After parameterizing compose.yaml's image field with \${CCS_IMAGE:-...},
the image_name() sed pipeline cut at the first colon in the shell variable
syntax (:-) instead of the tag separator, returning '\${CCS_IMAGE' and
failing the kaitranntt/ccs grep.
Added a sed step to unwrap \${VAR:-default} by extracting just the default
value before stripping the tag. Existing plain image: name:tag references
are unaffected.
118 lines
4.2 KiB
Bash
Executable File
118 lines
4.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Asserts that a Docker image does not exceed a given byte budget.
|
|
#
|
|
# Usage: image-size.sh <image:tag> <max-bytes> [--platform <platform>]
|
|
# Exit: 0 on pass, 1 on fail
|
|
#
|
|
# When --platform is given the manifest for that specific platform is inspected
|
|
# via `docker buildx imagetools inspect`, summing compressed layer sizes from
|
|
# the registry manifest. This avoids pulling the image locally for each arch
|
|
# and works on a multi-arch manifest list.
|
|
#
|
|
# Without --platform the locally cached image is inspected via
|
|
# `docker image inspect`, which only reports the host-native architecture.
|
|
#
|
|
# Examples:
|
|
# image-size.sh ghcr.io/kaitranntt/ccs:latest 367001600
|
|
# image-size.sh ghcr.io/kaitranntt/ccs:latest 367001600 --platform linux/amd64
|
|
# image-size.sh ghcr.io/kaitranntt/ccs:latest 367001600 --platform linux/arm64
|
|
set -euo pipefail
|
|
|
|
if [[ $# -lt 2 ]]; then
|
|
echo "[X] Usage: $0 <image:tag> <max-bytes> [--platform <platform>]" >&2
|
|
exit 1
|
|
fi
|
|
|
|
IMAGE="$1"
|
|
MAX_BYTES="$2"
|
|
PLATFORM=""
|
|
|
|
# Parse optional --platform flag
|
|
shift 2
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--platform)
|
|
PLATFORM="$2"
|
|
shift 2
|
|
;;
|
|
*)
|
|
echo "[X] Unknown argument: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validate that max-bytes is a positive integer
|
|
if ! [[ "$MAX_BYTES" =~ ^[0-9]+$ ]]; then
|
|
echo "[X] max-bytes must be a positive integer, got: ${MAX_BYTES}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
MAX_MB=$(( MAX_BYTES / 1048576 ))
|
|
|
|
if [[ -n "$PLATFORM" ]]; then
|
|
# Multi-arch path: sum compressed layer sizes from the registry manifest.
|
|
# `docker buildx imagetools inspect --format` returns the manifest JSON for
|
|
# the given platform. We sum the `size` field of each layer entry.
|
|
echo "[i] Inspecting ${IMAGE} for platform ${PLATFORM} via registry manifest..." >&2
|
|
ACTUAL_BYTES=$(
|
|
docker buildx imagetools inspect "${IMAGE}" \
|
|
--format "{{ range .Manifest.Layers }}{{ .Size }} {{ end }}" \
|
|
--raw 2>/dev/null \
|
|
| tr ' ' '\n' \
|
|
| awk 'NF && /^[0-9]+$/ { sum += $1 } END { print sum+0 }' \
|
|
2>/dev/null || echo ""
|
|
)
|
|
|
|
if [[ -z "$ACTUAL_BYTES" || "$ACTUAL_BYTES" == "0" ]]; then
|
|
# Fallback: try the platform-specific sub-manifest
|
|
echo "[i] Falling back to platform-scoped imagetools inspect..." >&2
|
|
ACTUAL_BYTES=$(
|
|
docker buildx imagetools inspect "${IMAGE}@$(
|
|
docker buildx imagetools inspect "${IMAGE}" \
|
|
--format "{{ range .Manifest.Manifests }}{{ if eq .Platform.OS \"$(echo "${PLATFORM}" | cut -d/ -f1)\" }}{{ if eq .Platform.Architecture \"$(echo "${PLATFORM}" | cut -d/ -f2)\" }}{{ .Digest }}{{ end }}{{ end }}{{ end }}" \
|
|
2>/dev/null | head -1
|
|
)" --format "{{ range .Manifest.Layers }}{{ .Size }} {{ end }}" 2>/dev/null \
|
|
| tr ' ' '\n' \
|
|
| awk 'NF && /^[0-9]+$/ { sum += $1 } END { print sum+0 }' \
|
|
2>/dev/null || echo ""
|
|
)
|
|
fi
|
|
|
|
if [[ -z "$ACTUAL_BYTES" || "$ACTUAL_BYTES" == "0" ]]; then
|
|
echo "[X] Could not determine size for ${IMAGE} platform=${PLATFORM}" >&2
|
|
echo " Possible causes: buildx version too old, manifest format unsupported, image not pushed yet" >&2
|
|
echo " Refusing to silently pass — fix the inspection or push the image first" >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
# Local-image path: use docker image inspect (host architecture only)
|
|
if ! docker image inspect "$IMAGE" > /dev/null 2>&1; then
|
|
echo "[i] Pulling ${IMAGE}..." >&2
|
|
docker pull "$IMAGE" >&2
|
|
fi
|
|
|
|
ACTUAL_BYTES=$(docker image inspect "$IMAGE" --format='{{.Size}}' 2>/dev/null)
|
|
|
|
if [[ -z "$ACTUAL_BYTES" ]]; then
|
|
echo "[X] Could not inspect image: ${IMAGE}" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
ACTUAL_MB=$(( ACTUAL_BYTES / 1048576 ))
|
|
LABEL="${IMAGE}${PLATFORM:+ (${PLATFORM})}"
|
|
|
|
if (( ACTUAL_BYTES > MAX_BYTES )); then
|
|
echo "[X] Image size check FAILED: ${LABEL}" >&2
|
|
echo " Actual: ${ACTUAL_BYTES} bytes (${ACTUAL_MB} MB)" >&2
|
|
echo " Budget: ${MAX_BYTES} bytes (${MAX_MB} MB)" >&2
|
|
echo " Excess: $(( ACTUAL_BYTES - MAX_BYTES )) bytes ($(( ACTUAL_MB - MAX_MB )) MB over budget)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "[OK] Image size check PASSED: ${LABEL}"
|
|
echo " Actual: ${ACTUAL_BYTES} bytes (${ACTUAL_MB} MB)"
|
|
echo " Budget: ${MAX_BYTES} bytes (${MAX_MB} MB)"
|
|
echo " Margin: $(( MAX_BYTES - ACTUAL_BYTES )) bytes ($(( MAX_MB - ACTUAL_MB )) MB remaining)"
|