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