Skip to content

Commit a5334e7

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 38a39d0 commit a5334e7

File tree

2 files changed

+207
-1
lines changed

2 files changed

+207
-1
lines changed

Makefile.core.mk

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ OPM ?= $(LOCALBIN)/opm
521521
ISTIOCTL ?= $(LOCALBIN)/istioctl
522522
RUNME ?= $(LOCALBIN)/runme
523523
MISSPELL ?= $(LOCALBIN)/misspell
524+
CRD_SCHEMA_CHECKER ?= $(LOCALBIN)/crd-schema-checker
524525

525526
## Tool Versions
526527
OPERATOR_SDK_VERSION ?= v1.41.1
@@ -533,6 +534,7 @@ GITLEAKS_VERSION ?= v8.28.0
533534
ISTIOCTL_VERSION ?= 1.26.0
534535
RUNME_VERSION ?= 3.15.0
535536
MISSPELL_VERSION ?= v0.3.4
537+
CRD_SCHEMA_CHECKER_VERSION ?= master
536538

537539
# GENERATE_RELATED_IMAGES defines whether `spec.relatedImages` is going to be generated or not
538540
# To disable set flag to false
@@ -747,8 +749,17 @@ lint-spell: misspell
747749
misspell: $(LOCALBIN) ## Download misspell to bin directory.
748750
@test -s $(LOCALBIN)/misspell || GOBIN=$(LOCALBIN) go install github.com/client9/misspell/cmd/misspell@$(MISSPELL_VERSION)
749751

752+
.PHONY: crd-schema-checker
753+
crd-schema-checker: $(CRD_SCHEMA_CHECKER) ## Download crd-schema-checker to bin directory.
754+
$(CRD_SCHEMA_CHECKER): $(LOCALBIN)
755+
@test -s $(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
756+
757+
.PHONY: lint-crds
758+
lint-crds: crd-schema-checker ## Lint CRDs for backwards compatibility on release branches.
759+
@PREVIOUS_VERSION=$(PREVIOUS_VERSION) ./tools/crd-schema-checker.sh
760+
750761
.PHONY: lint
751-
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.
762+
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.
752763

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

tools/crd-schema-checker.sh

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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
25+
ERRORS=0
26+
WARNINGS=0
27+
28+
# Function to check if a CRD has any alpha/beta versions
29+
is_alpha_beta_crd() {
30+
local crd_file="$1"
31+
# Look for version names that contain alpha or beta (e.g., "name: v1alpha1", "name: v1beta1")
32+
grep -q -E "name:[[:space:]]*v[0-9]+(alpha|beta)" "$crd_file"
33+
}
34+
35+
[[ $# -gt 0 && ("$1" == "--help" || "$1" == "-h") ]] && {
36+
echo "Usage: $0 [--help|-h]"
37+
echo "Checks CRD backwards compatibility on release branches."
38+
exit 0
39+
}
40+
41+
# Check for crd-schema-checker
42+
if [[ -x "./bin/crd-schema-checker" ]]; then
43+
CRD_SCHEMA_CHECKER="./bin/crd-schema-checker"
44+
elif command -v crd-schema-checker &> /dev/null; then
45+
CRD_SCHEMA_CHECKER="crd-schema-checker"
46+
else
47+
echo "ERROR: crd-schema-checker not found. Run 'make crd-schema-checker'"
48+
exit 1
49+
fi
50+
51+
current_branch=$(git rev-parse --abbrev-ref HEAD)
52+
53+
# Only run on release branches
54+
[[ "$current_branch" =~ ^release-[0-9]+\.[0-9]+$ ]] || {
55+
echo "Not on a release branch (current: $current_branch). Attempting PREVIOUS_VERSION env var."
56+
VERSION=$(echo "${PREVIOUS_VERSION}" | cut -f1,2 -d'.')
57+
current_branch="release-${VERSION}"
58+
echo "Detected PREVIOUS_VERSION=${PREVIOUS_VERSION}. Using ${current_branch} as latest release branch."
59+
}
60+
61+
# Extract version and find previous release
62+
read -r current_major current_minor <<< "$(echo "$current_branch" | sed 's/release-//' | tr '.' ' ')"
63+
64+
previous_branch=$(git branch -a | grep -E 'release-[0-9]+\.[0-9]+$' | \
65+
sed 's/.*release-/release-/' | sort -V | uniq | \
66+
awk -v target="release-${current_major}.${current_minor}" '
67+
$0 == target { print prev; exit }
68+
{ prev = $0 }
69+
')
70+
71+
[[ -n "$previous_branch" ]] || {
72+
echo "ERROR: No previous release branch found"
73+
exit 1
74+
}
75+
76+
echo "Checking CRD compatibility: $previous_branch -> $current_branch"
77+
78+
# Setup temp dirs
79+
temp_dir=$(mktemp -d)
80+
trap 'rm -rf $temp_dir' EXIT
81+
previous_dir="$temp_dir/previous"
82+
current_dir="$temp_dir/current"
83+
mkdir -p "$previous_dir" "$current_dir"
84+
85+
extract_crds() {
86+
local branch="$1" output_dir="$2"
87+
while IFS= read -r file; do
88+
[[ -z "$file" ]] && continue
89+
content=$(git show "$branch:bundle/manifests/$file" 2>/dev/null)
90+
[[ -z "$content" ]] && continue
91+
if [[ "$content" == *"CustomResourceDefinition"* ]]; then
92+
crd_name=$(echo "$content" | grep "name:" | head -1 | sed 's/.*name:[[:space:]]*//' | tr -d '"'"'"' ')
93+
if [[ -n "$crd_name" && "$crd_name" == *"sailoperator.io" ]]; then
94+
echo "$content" > "$output_dir/${crd_name}.yaml"
95+
echo "$output_dir/${crd_name}.yaml"
96+
fi
97+
fi
98+
done < <(git ls-tree --name-only -r "$branch:bundle/manifests" 2>/dev/null | grep -E '\.(yaml|yml)$')
99+
}
100+
101+
echo "Extracting CRDs..."
102+
mapfile -t previous_crds < <(extract_crds "$previous_branch" "$previous_dir")
103+
mapfile -t current_crds < <(extract_crds "$current_branch" "$current_dir")
104+
105+
# Create lookup map for current CRDs
106+
declare -A current_crd_map
107+
for crd_file in "${current_crds[@]}"; do
108+
[[ -n "$crd_file" ]] && {
109+
crd_name=$(basename "$crd_file" .yaml)
110+
current_crd_map["$crd_name"]="$crd_file"
111+
}
112+
done
113+
114+
echo "Comparing CRDs..."
115+
116+
# Compare existing CRDs
117+
for previous_crd in "${previous_crds[@]}"; do
118+
[[ -z "$previous_crd" ]] && continue
119+
120+
crd_name=$(basename "$previous_crd" .yaml)
121+
122+
if [[ -n "${current_crd_map[$crd_name]:-}" ]]; then
123+
set +e
124+
output=$($CRD_SCHEMA_CHECKER check-manifests \
125+
--disabled-validators=NoBools,NoMaps \
126+
--existing-crd-filename="$previous_crd" \
127+
--new-crd-filename="${current_crd_map[$crd_name]}" 2>&1)
128+
exit_code=$?
129+
set -e
130+
131+
if [[ $exit_code -eq 0 ]]; then
132+
echo "OK: $crd_name"
133+
else
134+
# Check if this is an alpha/beta CRD
135+
if is_alpha_beta_crd "$previous_crd"; then
136+
echo "WARNING: $crd_name (alpha/beta CRD)"
137+
if echo "$output" | grep -q "ERROR:"; then
138+
echo "$output" | grep "ERROR:" | sed 's/ERROR:[[:space:]]*/ /'
139+
else
140+
echo " $output"
141+
fi
142+
WARNINGS=$((WARNINGS + 1))
143+
else
144+
echo "ERROR: $crd_name"
145+
if echo "$output" | grep -q "ERROR:"; then
146+
echo "$output" | grep "ERROR:" | sed 's/ERROR:[[:space:]]*/ /'
147+
else
148+
echo " $output"
149+
fi
150+
ERRORS=$((ERRORS + 1))
151+
fi
152+
fi
153+
CHECKED_CRDS=$((CHECKED_CRDS + 1))
154+
else
155+
# Check if the removed CRD was alpha/beta
156+
if is_alpha_beta_crd "$previous_crd"; then
157+
echo "WARNING: CRD $crd_name was removed (alpha/beta CRD)"
158+
WARNINGS=$((WARNINGS + 1))
159+
else
160+
echo "ERROR: CRD $crd_name was removed"
161+
ERRORS=$((ERRORS + 1))
162+
fi
163+
fi
164+
done
165+
166+
# Check for new CRDs
167+
for current_crd in "${current_crds[@]}"; do
168+
[[ -z "$current_crd" ]] && continue
169+
170+
crd_name=$(basename "$current_crd" .yaml)
171+
found=false
172+
173+
for previous_crd in "${previous_crds[@]}"; do
174+
[[ -n "$previous_crd" ]] && [[ "$crd_name" == "$(basename "$previous_crd" .yaml)" ]] && {
175+
found=true
176+
break
177+
}
178+
done
179+
180+
[[ "$found" == false ]] && {
181+
echo "INFO: New CRD added: $crd_name"
182+
WARNINGS=$((WARNINGS + 1))
183+
}
184+
done
185+
186+
echo
187+
echo "=== Results ==="
188+
echo "Checked $CHECKED_CRDS CRDs: $ERRORS errors, $WARNINGS warnings"
189+
190+
if [[ $ERRORS -gt 0 ]]; then
191+
echo "FAILED: Breaking changes detected"
192+
exit 1
193+
else
194+
echo "PASSED: No breaking changes"
195+
fi

0 commit comments

Comments
 (0)