Skip to content

Commit 2be5426

Browse files
committed
introduce crd-schema-checker
This is a script that uses openshift/crd-schema-checker to verify that we're not breaking any CRD API guarantees across releases. Signed-off-by: Daniel Grimm <[email protected]>
1 parent a8d66a8 commit 2be5426

File tree

2 files changed

+210
-1
lines changed

2 files changed

+210
-1
lines changed

Makefile.core.mk

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ OPM ?= $(LOCALBIN)/opm
549549
ISTIOCTL ?= $(LOCALBIN)/istioctl
550550
RUNME ?= $(LOCALBIN)/runme
551551
MISSPELL ?= $(LOCALBIN)/misspell
552+
CRD_SCHEMA_CHECKER ?= $(LOCALBIN)/crd-schema-checker
552553

553554
## Tool Versions
554555
OPERATOR_SDK_VERSION ?= v1.41.1
@@ -562,6 +563,7 @@ GITLEAKS_VERSION ?= v8.28.0
562563
ISTIOCTL_VERSION ?= 1.26.2
563564
RUNME_VERSION ?= 3.15.4
564565
MISSPELL_VERSION ?= v0.3.4
566+
CRD_SCHEMA_CHECKER_VERSION ?= release-4.22
565567

566568
.PHONY: helm $(HELM)
567569
helm: $(HELM) ## Download helm to bin directory. If wrong version is installed, it will be overwritten.
@@ -776,8 +778,17 @@ lint-spell: misspell
776778
misspell: $(LOCALBIN) ## Download misspell to bin directory.
777779
@test -s $(LOCALBIN)/misspell || GOBIN=$(LOCALBIN) go install github.com/client9/misspell/cmd/misspell@$(MISSPELL_VERSION)
778780

781+
.PHONY: crd-schema-checker
782+
crd-schema-checker: $(CRD_SCHEMA_CHECKER) ## Download crd-schema-checker to bin directory.
783+
$(CRD_SCHEMA_CHECKER): $(LOCALBIN)
784+
@test -x $(LOCALBIN)/crd-schema-checker || GOBIN=$(LOCALBIN) GO111MODULE=on go install github.com/openshift/crd-schema-checker/cmd/crd-schema-checker@$(CRD_SCHEMA_CHECKER_VERSION) > /dev/stderr
785+
786+
.PHONY: lint-crds
787+
lint-crds: crd-schema-checker ## Lint CRDs for backwards compatibility on release branches.
788+
@PREVIOUS_VERSION=$(PREVIOUS_VERSION) ./tools/crd-schema-checker.sh
789+
779790
.PHONY: lint
780-
lint: lint-scripts lint-licenses lint-copyright-banner lint-go lint-yaml lint-helm lint-bundle lint-watches lint-secrets lint-spell ## Run all linters.
791+
lint: lint-scripts lint-licenses lint-copyright-banner lint-go lint-yaml lint-helm lint-bundle lint-watches lint-secrets lint-spell lint-crds ## Run all linters.
781792

782793
.PHONY: format
783794
format: format-go tidy-go ## Auto-format all code. This should be run before sending a PR.

tools/crd-schema-checker.sh

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#!/bin/bash
2+
3+
# Copyright Istio Authors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
# CRD compatibility checker for release branches.
18+
# Compares CRDs between the current release branch and the previous one to catch breaking changes.
19+
# Uses OpenShift's crd-schema-checker to detect issues like removed fields or stricter validation.
20+
# Run 'make crd-schema-checker' first to install the dependency, then 'make lint-crds' to check.
21+
22+
set -euo pipefail
23+
24+
CHECKED_CRDS=0 ERRORS=0 STABLE_ERRORS=0 WARNINGS=0 INFOS=0
25+
26+
# TODO: fix the SSA tags on lists and enable the validator
27+
DISABLED_VALIDATORS="NoBools,NoMaps,ListsMustHaveSSATags"
28+
29+
# Get the latest version from a CRD
30+
getLatestCRDVersion() {
31+
command -v yq &>/dev/null || { echo "unknown"; return; }
32+
yq eval '.spec.versions[-1].name' "$1" 2>/dev/null || echo "unknown"
33+
}
34+
35+
# Check if version is stable (not alpha/beta)
36+
isStableVersion() {
37+
[[ "$1" =~ ^v[0-9]+(\.[0-9]+)*$ ]]
38+
}
39+
40+
# Output result with version info
41+
output_result() {
42+
local crd_name="$1" version="$2" output="$3"
43+
local errors=0 warnings=0 infos=0
44+
echo "$crd_name ($version)"
45+
if [ -n "${output}" ]; then
46+
while read -r line; do
47+
if echo "${line}" | grep -iq "ERROR:"; then
48+
errors=$((errors + 1))
49+
elif echo "${line}" | grep -iq "Warning:"; then
50+
warnings=$((warnings + 1))
51+
elif echo "${line}" | grep -iq "info:"; then
52+
infos=$((infos + 1))
53+
fi
54+
echo " - $line"
55+
done <<< "$output"
56+
fi
57+
echo "--> ${errors} errors, ${warnings} warnings, ${infos} infos"
58+
if isStableVersion "${version}"; then
59+
STABLE_ERRORS=$((STABLE_ERRORS + errors))
60+
fi
61+
ERRORS=$((ERRORS + errors))
62+
WARNINGS=$((WARNINGS + warnings))
63+
INFOS=$((INFOS + infos))
64+
}
65+
66+
if [[ -x "$(pwd)/bin/crd-schema-checker" ]]; then
67+
CRD_SCHEMA_CHECKER="$(pwd)/bin/crd-schema-checker"
68+
elif command -v crd-schema-checker &>/dev/null; then
69+
CRD_SCHEMA_CHECKER="crd-schema-checker"
70+
else
71+
echo "ERROR: crd-schema-checker not found. Run 'make crd-schema-checker'"
72+
exit 1
73+
fi
74+
75+
repo_url="https://github.com/istio-ecosystem/sail-operator.git"
76+
[[ -n "${PROW_JOB_ID:-}" && -n "${REPO_OWNER:-}" && -n "${REPO_NAME:-}" ]] &&
77+
repo_url="https://github.com/${REPO_OWNER}/${REPO_NAME}.git"
78+
79+
temp_dir=$(mktemp -d)
80+
trap 'rm -rf "$temp_dir"' EXIT
81+
local_repo_path=`pwd`
82+
git clone "$repo_url" "$temp_dir/repo"
83+
84+
pwd=$(pwd)
85+
86+
# Determine branches to compare
87+
current_branch=$(git rev-parse --abbrev-ref HEAD)
88+
89+
pushd "$temp_dir/repo" >/dev/null
90+
91+
git fetch origin '+refs/heads/release-*:refs/remotes/origin/release-*' || true
92+
93+
if [[ "$current_branch" =~ ^release-[0-9]+\.[0-9]+$ ]]; then
94+
# we're on a release branch. find the previous release branch
95+
previous_branch=$(git branch -r | grep -E 'origin/release-[0-9]+\.[0-9]+$' |
96+
sed 's|.*origin/||' | sort -V |
97+
awk -v target="$current_branch" '$0 == target { print prev; exit } { prev = $0 }')
98+
elif [[ -n "${PREVIOUS_VERSION:-}" ]]; then
99+
previous_branch="release-$(echo "${PREVIOUS_VERSION}" | cut -f1,2 -d'.')"
100+
else
101+
echo "Not on a release branch and PREVIOUS_VERSION not set. Skipping."
102+
exit 0
103+
fi
104+
105+
if [[ -z "$previous_branch" ]]; then
106+
echo "ERROR: No previous release branch found for $current_branch"
107+
exit 1
108+
fi
109+
110+
git checkout $previous_branch
111+
popd >/dev/null
112+
113+
echo "Checking CRD compatibility: $previous_branch -> $current_branch"
114+
115+
# Extract CRDs from repo_dir $1's, copy to output_dir $2
116+
extract_crds() {
117+
local repo_dir="$1" output_dir="$2"
118+
mkdir -p "$output_dir"
119+
pushd $repo_dir > /dev/null
120+
local files
121+
mapfile -t files < <(ls bundle/manifests | grep -E 'sailoperator.io_.*\.(yaml|yml)$')
122+
123+
for file in "${files[@]}"; do
124+
[[ -z "$file" ]] && continue
125+
content=$(cat bundle/manifests/$file)
126+
if [[ "$content" == *"kind: CustomResourceDefinition"* ]]; then
127+
echo "$content" > "$output_dir/${file}"
128+
echo "$file"
129+
fi
130+
done
131+
popd > /dev/null
132+
}
133+
mapfile -t previous_crds < <(extract_crds "$temp_dir/repo" "$temp_dir/prev")
134+
echo Found "${#previous_crds[@]}" CRDs in $previous_branch: ${previous_crds[@]}
135+
mapfile -t current_crds < <(extract_crds "$pwd" "$temp_dir/curr")
136+
echo Found "${#current_crds[@]}" CRDs in $current_branch: ${current_crds[@]}
137+
138+
# Create lookup maps
139+
declare -A current_crd_map previous_crd_map
140+
for crd in "${current_crds[@]}"; do
141+
current_crd_map["$crd"]="$temp_dir/curr/${crd}"
142+
done
143+
for crd in "${previous_crds[@]}"; do
144+
previous_crd_map["$crd"]="$temp_dir/prev/${crd}"
145+
done
146+
147+
echo "Comparing CRDs..."
148+
149+
# Check existing CRDs for breaking changes
150+
for crd in "${previous_crds[@]}"; do
151+
if [[ -n "${current_crd_map[$crd]:-}" ]]; then
152+
set +e
153+
output=$($CRD_SCHEMA_CHECKER check-manifests \
154+
--disabled-validators=${DISABLED_VALIDATORS} \
155+
--existing-crd-filename="${previous_crd_map[$crd]}" \
156+
--new-crd-filename="${current_crd_map[$crd]}" 2>&1)
157+
set -e
158+
159+
version=$(getLatestCRDVersion "${current_crd_map[$crd]}")
160+
output_result "${crd}" "${version}" "${output}"
161+
CHECKED_CRDS=$((CHECKED_CRDS + 1))
162+
else
163+
# CRD was removed
164+
version=$(getLatestCRDVersion "${previous_crd_map[$crd]}")
165+
if ! isStableVersion "$version"; then
166+
echo "WARNING: CRD $crd was removed ($version)"
167+
WARNINGS=$((WARNINGS + 1))
168+
else
169+
echo "ERROR: CRD $crd was removed (${version})"
170+
ERRORS=$((ERRORS + 1))
171+
fi
172+
fi
173+
done
174+
175+
# Check for new CRDs
176+
for crd in "${current_crds[@]}"; do
177+
[[ -n "${previous_crd_map[$crd]:-}" ]] && continue
178+
echo "INFO: New CRD added: $crd"
179+
set +e
180+
output=$($CRD_SCHEMA_CHECKER check-manifests \
181+
--disabled-validators=${DISABLED_VALIDATORS} \
182+
--new-crd-filename="${current_crd_map[$crd]}" 2>&1)
183+
set -e
184+
version=$(getLatestCRDVersion "${current_crd_map[$crd]}")
185+
output_result "${crd}" "${version}" "${output}"
186+
((CHECKED_CRDS++))
187+
done
188+
189+
echo
190+
echo "=== Results ==="
191+
echo "Checked $CHECKED_CRDS CRDs: $ERRORS errors ($STABLE_ERRORS errors in stable APIs), $WARNINGS warnings, $INFOS infos"
192+
193+
if [[ $STABLE_ERRORS -gt 0 ]]; then
194+
echo "FAILED: Breaking changes detected"
195+
exit 1
196+
else
197+
echo "PASSED: No breaking changes"
198+
fi

0 commit comments

Comments
 (0)