|
| 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