Skip to content

Commit b4e4ede

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 749d908 commit b4e4ede

File tree

2 files changed

+206
-1
lines changed

2 files changed

+206
-1
lines changed

Makefile.core.mk

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

554555
## Tool Versions
555556
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.1
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: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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+
git clone "$repo_url" "$temp_dir/repo" && cd "$temp_dir/repo"
82+
83+
# Determine branches to compare
84+
current_branch=$(git rev-parse --abbrev-ref HEAD)
85+
git fetch origin '+refs/heads/release-*:refs/remotes/origin/release-*' || true
86+
87+
if [[ "$current_branch" =~ ^release-[0-9]+\.[0-9]+$ ]]; then
88+
target_branch="$current_branch"
89+
elif [[ -n "${PREVIOUS_VERSION:-}" ]]; then
90+
target_branch="release-$(echo "${PREVIOUS_VERSION}" | cut -f1,2 -d'.')"
91+
current_branch="$target_branch"
92+
else
93+
echo "Not on a release branch and PREVIOUS_VERSION not set. Skipping."
94+
exit 0
95+
fi
96+
97+
# Find previous release branch
98+
previous_branch=$(git branch -r | grep -E 'origin/release-[0-9]+\.[0-9]+$' |
99+
sed 's|.*origin/||' | sort -V |
100+
awk -v target="$target_branch" '$0 == target { print prev; exit } { prev = $0 }')
101+
102+
if [[ -z "$previous_branch" ]]; then
103+
echo "ERROR: No previous release branch found for $target_branch"
104+
exit 1
105+
fi
106+
107+
echo "Checking CRD compatibility: $previous_branch -> $current_branch"
108+
109+
# Extract CRDs from both branches
110+
extract_crds() {
111+
local branch="$1" output_dir="$2"
112+
mkdir -p "$output_dir"
113+
git fetch origin "$branch" || true
114+
115+
local files
116+
mapfile -t files < <(git ls-tree --name-only -r "origin/$branch:bundle/manifests" 2>/dev/null | grep -E '\.(yaml|yml)$')
117+
118+
for file in "${files[@]}"; do
119+
[[ -z "$file" ]] && continue
120+
content=$(git show "origin/$branch:bundle/manifests/$file" 2>/dev/null)
121+
if [[ "$content" == *"CustomResourceDefinition"* && "$content" == *"sailoperator.io"* ]]; then
122+
crd_name=$(echo "$content" | grep "name:" | head -1 | sed 's/.*name:[[:space:]]*//' | tr -d '"'"'"' ')
123+
if [[ -n "$crd_name" ]]; then
124+
echo "$content" > "$output_dir/${crd_name}.yaml"
125+
echo "$crd_name"
126+
fi
127+
fi
128+
done
129+
}
130+
131+
mapfile -t previous_crds < <(extract_crds "$previous_branch" "$temp_dir/prev")
132+
mapfile -t current_crds < <(extract_crds "$current_branch" "$temp_dir/curr")
133+
134+
# Create lookup maps
135+
declare -A current_crd_map previous_crd_map
136+
for crd in "${current_crds[@]}"; do
137+
current_crd_map["$crd"]="$temp_dir/curr/${crd}.yaml"
138+
done
139+
for crd in "${previous_crds[@]}"; do
140+
previous_crd_map["$crd"]="$temp_dir/prev/${crd}.yaml"
141+
done
142+
143+
echo "Comparing CRDs..."
144+
145+
# Check existing CRDs for breaking changes
146+
for crd in "${previous_crds[@]}"; do
147+
if [[ -n "${current_crd_map[$crd]:-}" ]]; then
148+
set +e
149+
output=$($CRD_SCHEMA_CHECKER check-manifests \
150+
--disabled-validators=${DISABLED_VALIDATORS} \
151+
--existing-crd-filename="${previous_crd_map[$crd]}" \
152+
--new-crd-filename="${current_crd_map[$crd]}" 2>&1)
153+
set -e
154+
155+
version=$(getLatestCRDVersion "${current_crd_map[$crd]}")
156+
output_result "${crd}" "${version}" "${output}"
157+
CHECKED_CRDS=$((CHECKED_CRDS + 1))
158+
else
159+
# CRD was removed
160+
version=$(getLatestCRDVersion "${previous_crd_map[$crd]}")
161+
if ! isStableVersion "$version"; then
162+
echo "WARNING: CRD $crd was removed ($version)"
163+
WARNINGS=$((WARNINGS + 1))
164+
else
165+
echo "ERROR: CRD $crd was removed (${version})"
166+
ERRORS=$((ERRORS + 1))
167+
fi
168+
fi
169+
done
170+
171+
# Check for new CRDs
172+
for crd in "${current_crds[@]}"; do
173+
[[ -n "${previous_crd_map[$crd]:-}" ]] && continue
174+
echo "INFO: New CRD added: $crd"
175+
set +e
176+
output=$($CRD_SCHEMA_CHECKER check-manifests \
177+
--disabled-validators=${DISABLED_VALIDATORS} \
178+
--new-crd-filename="${current_crd_map[$crd]}" 2>&1)
179+
set -e
180+
version=$(getLatestCRDVersion "${current_crd_map[$crd]}")
181+
output_result "${crd}" "${version}" "${output}"
182+
((CHECKED_CRDS++))
183+
done
184+
185+
echo
186+
echo "=== Results ==="
187+
echo "Checked $CHECKED_CRDS CRDs: $ERRORS errors ($STABLE_ERRORS errors in stable APIs), $WARNINGS warnings, $INFOS infos"
188+
189+
if [[ $STABLE_ERRORS -gt 0 ]]; then
190+
echo "FAILED: Breaking changes detected"
191+
exit 1
192+
else
193+
echo "PASSED: No breaking changes"
194+
fi

0 commit comments

Comments
 (0)