mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-23 03:27:50 +09:00
test(kube-ovn): compare image copies against upstream's Dockerfile
packages/system/kubeovn/images/kubeovn/Dockerfile is a hand-kept copy of upstream's dist/images/Dockerfile, and `make update` rewrites only its ARG VERSION and ARG TAG. A binary upstream starts shipping therefore does not reach our image on its own, and nothing read the two files together: kube-ovn-bfdd-supervisor went missing that way with the whole suite green. hack/check-kubeovn-image-copies.sh normalizes the COPY instructions of both files and reports either side's surplus. It compares only the stages built FROM kubeovn/kube-ovn-base, because our builder stage clones and patches kube-ovn and its COPY lines have no upstream counterpart, and it treats an empty result as a broken check rather than agreement. `make update` now runs it against the tag it pulls, so the next bump stops on a mismatch, and hack/check-kubeovn-image-copies.bats runs it on every CI run against a verbatim copy of upstream's Dockerfile at the vendored appVersion. Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
This commit is contained in:
3 files changed
+280
No files matched your search
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env bats
|
||||
# -----------------------------------------------------------------------------
|
||||
# Unit tests for hack/check-kubeovn-image-copies.sh.
|
||||
#
|
||||
# packages/system/kubeovn/images/kubeovn/Dockerfile is a hand-kept copy of
|
||||
# upstream's dist/images/Dockerfile, and `make update` rewrites only ARG VERSION
|
||||
# and ARG TAG in it. When v1.15.26 added kube-ovn-bfdd-supervisor to the image,
|
||||
# our copy kept the v1.15.10 set and the binary went missing, which nothing in
|
||||
# the tree noticed. The checker compares what each file copies, and this suite
|
||||
# runs it over the real Dockerfile against a verbatim copy of upstream's for the
|
||||
# vendored appVersion -- a synthetic pair alone would only assert the shape we
|
||||
# already imagined.
|
||||
#
|
||||
# cozytest.sh's awk parser recognizes only @test blocks and a bare `}` on its
|
||||
# own line; there is no bats `run` or `$status`. Assertions are direct shell
|
||||
# tests, and an expected failure is written as `if CHECK ...; then ... exit 1`.
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BATS_TEST_FILENAME:-$0}")/.." && pwd)"
|
||||
CHECK="$REPO_ROOT/hack/check-kubeovn-image-copies.sh"
|
||||
OURS="$REPO_ROOT/packages/system/kubeovn/images/kubeovn/Dockerfile"
|
||||
CHART="$REPO_ROOT/packages/system/kubeovn/charts/kube-ovn/Chart.yaml"
|
||||
|
||||
# Upstream dist/images/Dockerfile at v1.15.26, copied verbatim. Bump it in the
|
||||
# same change that bumps the chart: `make update` re-runs the same comparison
|
||||
# against the tag it pulls, so the two cannot disagree for long.
|
||||
UPSTREAM_VERSION=1.15.26
|
||||
upstream_dockerfile() {
|
||||
cat <<'EOF'
|
||||
# syntax=docker/dockerfile:1
|
||||
ARG VERSION
|
||||
ARG BASE_TAG=$VERSION
|
||||
FROM kubeovn/kube-ovn-base:$BASE_TAG AS setcap
|
||||
|
||||
COPY *.sh /kube-ovn/
|
||||
COPY kubectl-ko /kube-ovn/kubectl-ko
|
||||
COPY 01-kube-ovn.conflist /kube-ovn/01-kube-ovn.conflist
|
||||
|
||||
COPY kube-ovn /kube-ovn/kube-ovn
|
||||
COPY kube-ovn-cmd /kube-ovn/kube-ovn-cmd
|
||||
COPY kube-ovn-bfdd-supervisor /kube-ovn/kube-ovn-bfdd-supervisor
|
||||
COPY kube-ovn-daemon /kube-ovn/kube-ovn-daemon
|
||||
COPY kube-ovn-controller /kube-ovn/kube-ovn-controller
|
||||
RUN ln -s /kube-ovn/kube-ovn-cmd /kube-ovn/kube-ovn-monitor && \
|
||||
ln -s /kube-ovn/kube-ovn-cmd /kube-ovn/kube-ovn-speaker && \
|
||||
ln -s /kube-ovn/kube-ovn-cmd /kube-ovn/kube-ovn-webhook && \
|
||||
ln -s /kube-ovn/kube-ovn-cmd /kube-ovn/kube-ovn-leader-checker && \
|
||||
ln -s /kube-ovn/kube-ovn-cmd /kube-ovn/kube-ovn-ic-controller && \
|
||||
ln -s /kube-ovn/kube-ovn-controller /kube-ovn/kube-ovn-pinger && \
|
||||
setcap CAP_NET_BIND_SERVICE+eip /kube-ovn/kube-ovn-cmd && \
|
||||
setcap CAP_NET_RAW,CAP_NET_BIND_SERVICE+eip /kube-ovn/kube-ovn-controller && \
|
||||
setcap CAP_NET_ADMIN,CAP_NET_RAW,CAP_NET_BIND_SERVICE,CAP_SYS_ADMIN+eip /kube-ovn/kube-ovn-daemon
|
||||
|
||||
FROM kubeovn/kube-ovn-base:$BASE_TAG
|
||||
|
||||
COPY --chmod=0644 logrotate/* /etc/logrotate.d/
|
||||
COPY grace_stop_ovn_controller /usr/share/ovn/scripts/grace_stop_ovn_controller
|
||||
|
||||
COPY --from=setcap /kube-ovn /kube-ovn
|
||||
RUN /kube-ovn/iptables-wrapper-installer.sh --no-sanity-check
|
||||
|
||||
WORKDIR /kube-ovn
|
||||
EOF
|
||||
}
|
||||
|
||||
@test "the fixture tracks the vendored chart version" {
|
||||
# Chart.yaml quotes the value; the package Makefile leans on the shell to
|
||||
# strip those, this runs the awk output through a comparison instead.
|
||||
appversion=$(awk '$1 == "appVersion:" {gsub(/"/, "", $2); print $2}' "$CHART")
|
||||
if [ "$appversion" != "$UPSTREAM_VERSION" ]; then
|
||||
echo "FAIL: chart appVersion is $appversion, fixture is v$UPSTREAM_VERSION" >&2
|
||||
echo "Refresh upstream_dockerfile() from dist/images/Dockerfile at v$appversion." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@test "our Dockerfile copies everything upstream's does" {
|
||||
upstream_dockerfile | "$CHECK" "$OURS" -
|
||||
}
|
||||
|
||||
@test "a binary upstream added and we did not is reported" {
|
||||
ours=$(mktemp)
|
||||
upstream=$(mktemp)
|
||||
upstream_dockerfile > "$upstream"
|
||||
grep -v 'kube-ovn-bfdd-supervisor' "$OURS" > "$ours"
|
||||
|
||||
if "$CHECK" "$ours" "$upstream" 2>/dev/null; then
|
||||
echo "FAIL: a missing binary passed the check" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! "$CHECK" "$ours" "$upstream" 2>&1 | grep -q 'kube-ovn-bfdd-supervisor'; then
|
||||
echo "FAIL: the report does not name the missing binary" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@test "a copy we make and upstream does not is reported" {
|
||||
ours=$(mktemp)
|
||||
upstream=$(mktemp)
|
||||
upstream_dockerfile > "$upstream"
|
||||
sed -e 's|^COPY --from=builder /source/dist/images/kube-ovn-cmd .*|&\nCOPY --from=builder /source/dist/images/kube-ovn-invented /kube-ovn/kube-ovn-invented|' "$OURS" > "$ours"
|
||||
|
||||
if "$CHECK" "$ours" "$upstream" 2>/dev/null; then
|
||||
echo "FAIL: an extra copy passed the check" >&2
|
||||
return 1
|
||||
fi
|
||||
if ! "$CHECK" "$ours" "$upstream" 2>&1 | grep -q 'kube-ovn-invented'; then
|
||||
echo "FAIL: the report does not name the extra copy" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@test "COPY lines in the builder stage are ours alone and not compared" {
|
||||
# `COPY patches /patches` and the regression test have no upstream
|
||||
# counterpart: they feed our clone-and-patch build, which upstream does not
|
||||
# have. Reading them would make the check fail on every run.
|
||||
ours=$(mktemp)
|
||||
upstream=$(mktemp)
|
||||
upstream_dockerfile > "$upstream"
|
||||
sed -e 's|^COPY patches /patches$|COPY patches /patches\nCOPY extra-build-input /some/where|' "$OURS" > "$ours"
|
||||
|
||||
"$CHECK" "$ours" "$upstream"
|
||||
}
|
||||
|
||||
@test "a destination that differs is reported even when the source matches" {
|
||||
ours=$(mktemp)
|
||||
upstream=$(mktemp)
|
||||
upstream_dockerfile > "$upstream"
|
||||
sed -e 's|/kube-ovn/kube-ovn-daemon$|/kube-ovn/kube-ovn-daemon-typo|' "$OURS" > "$ours"
|
||||
|
||||
if "$CHECK" "$ours" "$upstream" 2>/dev/null; then
|
||||
echo "FAIL: a retargeted copy passed the check" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@test "the checker reads the file it is given and not the tree" {
|
||||
empty=$(mktemp)
|
||||
upstream=$(mktemp)
|
||||
upstream_dockerfile > "$upstream"
|
||||
printf 'FROM scratch\n' > "$empty"
|
||||
|
||||
if "$CHECK" "$empty" "$upstream" 2>/dev/null; then
|
||||
echo "FAIL: a Dockerfile with no copies at all passed the check" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
#!/bin/sh
|
||||
# Compare what packages/system/kubeovn/images/kubeovn/Dockerfile copies into the
|
||||
# kube-ovn image against upstream's dist/images/Dockerfile, which it is a
|
||||
# hand-kept copy of.
|
||||
#
|
||||
# `make update` in that package rewrites only ARG VERSION and ARG TAG, so a
|
||||
# binary upstream starts shipping does not reach our image on its own: v1.15.26
|
||||
# added kube-ovn-bfdd-supervisor, our copy kept the v1.15.10 set, and a
|
||||
# VpcEgressGateway with spec.bfd got a container whose command did not exist.
|
||||
#
|
||||
# Usage: hack/check-kubeovn-image-copies.sh OURS UPSTREAM
|
||||
# Either path may be `-` to read that side from stdin.
|
||||
|
||||
set -eu
|
||||
|
||||
[ $# -eq 2 ] || {
|
||||
echo "Usage: $0 OURS UPSTREAM (either path may be '-' for stdin)" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
OURS=$1
|
||||
UPSTREAM=$2
|
||||
|
||||
# Normalize the COPY instructions of every stage built FROM kubeovn/kube-ovn-base
|
||||
# to `<source>... <destination>` lines.
|
||||
#
|
||||
# Only those stages are comparable. Our builder stage clones and patches
|
||||
# kube-ovn, which upstream does not do at all, so its COPY lines have no
|
||||
# counterpart and reading them would make every run fail.
|
||||
#
|
||||
# Flags are dropped so that `--from=builder /source/dist/images/kube-ovn` and a
|
||||
# bare `kube-ovn` read the same: upstream copies out of a build context that our
|
||||
# multi-stage build reproduces under /source/dist/images.
|
||||
copies() {
|
||||
if [ "$1" = "-" ]; then
|
||||
cat
|
||||
else
|
||||
cat -- "$1"
|
||||
fi | awk '
|
||||
function strip(path) {
|
||||
sub(/^\/source\/dist\/images\//, "", path)
|
||||
return path
|
||||
}
|
||||
# Fold a continued instruction into one logical line before reading it.
|
||||
{
|
||||
line = line $0
|
||||
if (line ~ /\\$/) {
|
||||
sub(/\\$/, " ", line)
|
||||
next
|
||||
}
|
||||
}
|
||||
line ~ /^[[:space:]]*FROM[[:space:]]/ {
|
||||
in_scope = (line ~ /kubeovn\/kube-ovn-base/)
|
||||
line = ""
|
||||
next
|
||||
}
|
||||
in_scope && line ~ /^[[:space:]]*COPY[[:space:]]/ {
|
||||
$0 = line
|
||||
out = ""
|
||||
for (i = 2; i <= NF; i++) {
|
||||
if ($i ~ /^--/) continue
|
||||
out = out (out == "" ? "" : " ") strip($i)
|
||||
}
|
||||
if (out != "") print out
|
||||
}
|
||||
{ line = "" }
|
||||
' | LC_ALL=C sort
|
||||
}
|
||||
|
||||
# Lines on stdin that do not appear in the newline-separated set $1.
|
||||
#
|
||||
# The set is fed through the same stream rather than `awk -v`: a multi-line -v
|
||||
# value is a GNU extension, and the awk on a maintainer's macOS rejects it.
|
||||
lines_not_in() {
|
||||
{
|
||||
printf '%s\n' "$1" | sed 's/^/S /'
|
||||
sed 's/^/C /'
|
||||
} | awk '
|
||||
$1 == "S" { sub(/^S /, ""); seen[$0] = 1; next }
|
||||
{ sub(/^C /, ""); if ($0 != "" && !($0 in seen)) print }
|
||||
'
|
||||
}
|
||||
|
||||
ours_copies=$(copies "$OURS")
|
||||
upstream_copies=$(copies "$UPSTREAM")
|
||||
|
||||
# A side with no copies at all means the stage markers stopped matching, not
|
||||
# that the two agree. Reporting that as a pass is how this check would quietly
|
||||
# stop working the next time the base image is renamed.
|
||||
if [ -z "$ours_copies" ]; then
|
||||
echo "ERROR: no COPY instructions found in a kubeovn/kube-ovn-base stage of $OURS" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$upstream_copies" ]; then
|
||||
echo "ERROR: no COPY instructions found in a kubeovn/kube-ovn-base stage of $UPSTREAM" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
missing=$(printf '%s\n' "$upstream_copies" | lines_not_in "$ours_copies")
|
||||
extra=$(printf '%s\n' "$ours_copies" | lines_not_in "$upstream_copies")
|
||||
|
||||
status=0
|
||||
|
||||
if [ -n "$missing" ]; then
|
||||
echo "ERROR: upstream copies these into the image and we do not:" >&2
|
||||
printf '%s\n' "$missing" | sed 's/^/ COPY /' >&2
|
||||
status=1
|
||||
fi
|
||||
|
||||
if [ -n "$extra" ]; then
|
||||
echo "ERROR: we copy these into the image and upstream does not:" >&2
|
||||
printf '%s\n' "$extra" | sed 's/^/ COPY /' >&2
|
||||
status=1
|
||||
fi
|
||||
|
||||
if [ "$status" -ne 0 ]; then
|
||||
echo >&2
|
||||
echo "packages/system/kubeovn/images/kubeovn/Dockerfile is a copy of upstream's" >&2
|
||||
echo "dist/images/Dockerfile. Bring the two back in step, keeping our" >&2
|
||||
echo "--from=builder /source/dist/images/ prefix." >&2
|
||||
fi
|
||||
|
||||
exit "$status"
|
||||
@@ -20,6 +20,16 @@ update:
|
||||
version=$$(awk '$$1 == "appVersion:" {print $$2}' charts/kube-ovn/Chart.yaml) && \
|
||||
$(SED_INPLACE) -e "s/ARG VERSION=.*/ARG VERSION=$${version}/" images/kubeovn/Dockerfile && \
|
||||
$(SED_INPLACE) -e "s/ARG TAG=.*/ARG TAG=v$${version}/" images/kubeovn/Dockerfile
|
||||
# images/kubeovn/Dockerfile is a copy of upstream's dist/images/Dockerfile
|
||||
# and only its ARG lines are rewritten above, so a binary upstream starts
|
||||
# shipping does not reach our image on its own. v1.15.26 added
|
||||
# kube-ovn-bfdd-supervisor that way and nothing noticed until the container
|
||||
# it backs could not exec. hack/check-kubeovn-image-copies.bats makes the
|
||||
# same comparison on every CI run, against a fixture this step tells you to
|
||||
# refresh.
|
||||
version=$$(awk '$$1 == "appVersion:" {gsub(/"/, "", $$2); print $$2}' charts/kube-ovn/Chart.yaml) && \
|
||||
curl -sSfL https://raw.githubusercontent.com/kubeovn/kube-ovn/v$${version}/dist/images/Dockerfile | \
|
||||
../../../hack/check-kubeovn-image-copies.sh images/kubeovn/Dockerfile -
|
||||
|
||||
image:
|
||||
docker buildx build images/kubeovn \
|
||||
|
||||
Reference in new issue
Block a user