|
| 1 | +--- |
| 2 | +description: Validate API issues using kube-api-linter with diff-aware analysis |
| 3 | +--- |
| 4 | + |
| 5 | +# API Lint Diff |
| 6 | + |
| 7 | +Validates API issues in `api/` directory using kube-api-linter with diff-aware analysis that distinguishes between NEW and PRE-EXISTING issues. |
| 8 | + |
| 9 | +## Instructions for Claude AI |
| 10 | + |
| 11 | +When this command is invoked, you MUST: |
| 12 | + |
| 13 | +**CRITICAL:** The final output MUST be a comprehensive analysis report displayed directly in the conversation for the user to read. Do NOT just create temp files - output the full report as your response. |
| 14 | + |
| 15 | +1. **Execute the shell script** |
| 16 | + ```bash |
| 17 | + bash hack/api-lint-diff/run.sh |
| 18 | + ``` |
| 19 | + |
| 20 | +2. **Understand the shell script's output**: |
| 21 | + - **False positives (IGNORED)**: Standard CRD scaffolding patterns that kube-api-linter incorrectly flags |
| 22 | + - **NEW issues (ERRORS)**: Introduced in current branch → MUST fix |
| 23 | + - **PRE-EXISTING issues (WARNINGS)**: Existed before changes → Can fix separately |
| 24 | + |
| 25 | +3. **Filter false positives** - Operator projects scaffold standard Kubernetes CRD patterns that kube-api-linter incorrectly flags as errors, even with WhenRequired configuration. |
| 26 | + |
| 27 | + **Scenario 1: optionalfields on Status field** |
| 28 | + ```go |
| 29 | + Status MyResourceStatus `json:"status,omitzero"` |
| 30 | + ``` |
| 31 | + **Error reported:** |
| 32 | + ``` |
| 33 | + optionalfields: field Status has a valid zero value ({}), but the validation |
| 34 | + is not complete (e.g. min properties/adding required fields). The field should |
| 35 | + be a pointer to allow the zero value to be set. If the zero value is not a |
| 36 | + valid use case, complete the validation and remove the pointer. |
| 37 | + ``` |
| 38 | + **Why it's a FALSE POSITIVE:** |
| 39 | + - Status is NEVER a pointer in any Kubernetes API |
| 40 | + - Works correctly with `omitzero` tag |
| 41 | + - Validation incompleteness is expected - Status is controller-managed, not user-provided |
| 42 | + - **ACTION: IGNORE this error** |
| 43 | + |
| 44 | + **Scenario 2: requiredfields on Spec field** |
| 45 | + ```go |
| 46 | + Spec MyResourceSpec `json:"spec"` |
| 47 | + ``` |
| 48 | + **Error reported:** |
| 49 | + ``` |
| 50 | + requiredfields: field Spec has a valid zero value ({}), but the validation is |
| 51 | + not complete (e.g. min properties/adding required fields). The field should be |
| 52 | + a pointer to allow the zero value to be set. If the zero value is not a valid |
| 53 | + use case, complete the validation and remove the pointer. |
| 54 | + ``` |
| 55 | + **Why it's a FALSE POSITIVE:** |
| 56 | + - Spec is NEVER a pointer in Kubernetes APIs |
| 57 | + - Scaffolds are starting points - users add validation when they implement their business logic |
| 58 | + - **ACTION: IGNORE this error** |
| 59 | + |
| 60 | + **Scenario 3: conditions markers on metav1.Condition** |
| 61 | + ```go |
| 62 | + Conditions []metav1.Condition `json:"conditions,omitempty"` |
| 63 | + ``` |
| 64 | + **Error reported:** |
| 65 | + ``` |
| 66 | + conditions: Conditions field is missing the following markers: |
| 67 | + patchStrategy=merge, patchMergeKey=type |
| 68 | + ``` |
| 69 | + **Why it's a FALSE POSITIVE:** |
| 70 | + - `metav1.Condition` already handles patches correctly |
| 71 | + - Adding these markers is redundant for this standard Kubernetes type |
| 72 | + - **ACTION: IGNORE this error** |
| 73 | + |
| 74 | +4. **For reported issues, provide intelligent analysis**: |
| 75 | + |
| 76 | + a. **Categorize by fix complexity**: |
| 77 | + - NON-BREAKING: Marker replacements, adding listType, adding +required/+optional |
| 78 | + - BREAKING: Pointer conversions, type changes (int→int32) |
| 79 | + |
| 80 | + b. **Search for actual usage** (REQUIRED FOR ALL ISSUES - NOT OPTIONAL): |
| 81 | + - **CRITICAL:** Do NOT just look at JSON tags - analyze actual code usage patterns |
| 82 | + - **Exception:** Deprecated marker replacements (`+kubebuilder:validation:Required` → `+required`) are straightforward - no usage analysis needed |
| 83 | + - **For all other issues:** MUST analyze actual usage before making recommendations |
| 84 | + - Use Grep to find ALL occurrences where each field is: |
| 85 | + * **Read/accessed**: `obj.Spec.FieldName`, `cat.Spec.Priority` |
| 86 | + * **Written/set**: `obj.Spec.FieldName = value` |
| 87 | + * **Checked for zero/nil**: `if obj.Spec.FieldName == ""`, `if ptr != nil` |
| 88 | + * **Used in conditionals**: Understand semantic meaning of zero values |
| 89 | + - Search in: controllers, reconcilers, internal packages, tests, examples |
| 90 | + - **Smart assessment based on usage patterns**: |
| 91 | + * Field ALWAYS set in code? → Should be **required**, no omitempty |
| 92 | + * Field SOMETIMES set? → Should be **optional** with omitempty |
| 93 | + * Code checks `if field == zero`? → May need **pointer** to distinguish zero vs unset |
| 94 | + * Zero value semantically valid? → Keep as value type with omitempty |
| 95 | + * Zero value semantically invalid? → Use pointer OR mark required |
| 96 | + * Field never read but only set by controller? → Likely Status field |
| 97 | + - **Example analysis workflow for a field**: |
| 98 | + ``` |
| 99 | + 1. Grep for field usage: `CatalogFilter.Version` |
| 100 | + 2. Found 5 occurrences: |
| 101 | + - controllers/extension.go:123: if filter.Version != "" { ... } |
| 102 | + - controllers/extension.go:456: result.Version = bundle.Version |
| 103 | + - tests/filter_test.go:89: Version: "1.2.3" |
| 104 | + 3. Analysis: Version is checked for empty, sometimes set, sometimes omitted |
| 105 | + 4. Recommendation: Optional with omitempty (current usage supports this) |
| 106 | + ``` |
| 107 | +
|
| 108 | + c. **Generate EXACT code fixes** grouped by file: |
| 109 | + - Show current code |
| 110 | + - Show replacement code (copy-pasteable) |
| 111 | + - **Explain why based on actual usage analysis** (not just JSON tags): |
| 112 | + * Include usage summary: "Found N occurrences" |
| 113 | + * Cite specific examples: "Used in resolve/catalog.go:163 as direct int32" |
| 114 | + * Explain semantic meaning: "Field distinguishes priority 0 vs unset" |
| 115 | + * Justify recommendation: "Since code checks for empty, should be optional" |
| 116 | + - Note breaking change impact with reasoning |
| 117 | + - **Each fix MUST include evidence from code usage** |
| 118 | +
|
| 119 | + d. **Prioritize recommendations**: |
| 120 | + - NEW issues first (must fix) |
| 121 | + - Group PRE-EXISTING by NON-BREAKING vs BREAKING |
| 122 | +
|
| 123 | +5. **Present actionable report directly to user**: |
| 124 | + - **IMPORTANT:** Output the full comprehensive analysis in the conversation (not just to a temp file) |
| 125 | + - Summary: False positives filtered, NEW count, PRE-EXISTING count |
| 126 | + - Group issues by file and fix type |
| 127 | + - Provide code snippets ready to apply (current code → fixed code) |
| 128 | + - **DO NOT include "Next Steps" or "Conclusion" sections** - just present the analysis |
| 129 | +
|
| 130 | + **Report Structure:** |
| 131 | + ``` |
| 132 | + # API Lint Diff Analysis Report |
| 133 | + |
| 134 | + **Generated:** [date] |
| 135 | + **Baseline:** main branch |
| 136 | + **Current:** [branch name] |
| 137 | + **Status:** [status icon and message based on logic below] |
| 138 | + |
| 139 | + **Status Logic:** |
| 140 | + - ✅ PASSED: 0 real issues (after filtering false positives) |
| 141 | + - ⚠️ WARN: 0 new issues but has pre-existing issues |
| 142 | + - ❌ FAIL: Has new issues that must be fixed |
| 143 | + |
| 144 | + ## Executive Summary |
| 145 | + - Total issues: X |
| 146 | + - False positives (IGNORED): Y |
| 147 | + - Real issues (NEED FIXING): Z |
| 148 | + - NEW issues: N |
| 149 | + - PRE-EXISTING issues: P |
| 150 | + |
| 151 | + ## REAL ISSUES - FIXES NEEDED (Z issues) |
| 152 | + |
| 153 | + ### Category 1: [Issue Type] (N issues) - [BREAKING/NON-BREAKING] |
| 154 | + |
| 155 | + #### File: [filename] |
| 156 | + |
| 157 | + **[Issue #]. Line X - [Field Name]** |
| 158 | + ```go |
| 159 | + // CURRENT: |
| 160 | + [current code] |
| 161 | + |
| 162 | + // FIX: |
| 163 | + [fixed code] |
| 164 | + ``` |
| 165 | + **Usage Analysis:** |
| 166 | + - Found N occurrences in codebase |
| 167 | + - [Specific usage example 1]: path/file.go:123 |
| 168 | + - [Specific usage example 2]: path/file.go:456 |
| 169 | + - Pattern: [always set / sometimes set / checked for zero / etc.] |
| 170 | + |
| 171 | + **Why:** [Recommendation based on usage analysis with evidence] |
| 172 | + **Breaking:** [YES/NO] ([detailed reason with impact]) |
| 173 | + |
| 174 | + [Repeat for all issues] |
| 175 | + |
| 176 | + ## Summary of Breaking Changes |
| 177 | + [Table of breaking changes if any] |
| 178 | + ``` |
| 179 | +
|
| 180 | +## What This Command Does |
| 181 | +
|
| 182 | +1. **Scopes to API directory only**: Analyzes only Go files under `api/` |
| 183 | +2. **Diff-aware analysis**: Compares current branch against `main` to identify: |
| 184 | + - **NEW issues**: Appear only in current branch in changed files (FAIL) |
| 185 | + - **PRE-EXISTING issues**: Already exist on main or in unchanged files (WARN) |
| 186 | +3. **False positive filtering**: Ignores known CRD scaffold patterns: |
| 187 | + - `optionalfields` on Status field (Status is never a pointer in K8s) |
| 188 | + - `requiredfields` on Spec field (Spec is never a pointer in K8s) |
| 189 | + - `conditions` on `[]metav1.Condition` (patch semantics already correct) |
| 190 | +4. **Breaking change analysis**: For each issue, assesses if the fix would be breaking |
| 191 | +5. **Temporary configuration**: Never modifies tracked files |
| 192 | +
|
| 193 | +## Usage |
| 194 | +
|
| 195 | +```bash |
| 196 | +/api-lint-diff |
| 197 | +``` |
| 198 | + |
| 199 | +## Output Format |
| 200 | + |
| 201 | +The command provides: |
| 202 | + |
| 203 | +1. **Summary** |
| 204 | + - Count of NEW issues |
| 205 | + - Count of PRE-EXISTING issues |
| 206 | + - List of changed files under `api/` |
| 207 | + |
| 208 | +2. **NEW Issues** (cause failure) |
| 209 | + - File + line number |
| 210 | + - Linter name and message |
| 211 | + - Explanation of the issue |
| 212 | + - How to fix it |
| 213 | + - Breaking-change assessment |
| 214 | + |
| 215 | +3. **PRE-EXISTING Issues** (warnings only) |
| 216 | + - Same detailed structure as NEW issues |
| 217 | + - Clearly labeled as pre-existing with `[WARNING]` prefix |
| 218 | + - Includes context, analysis, recommendations, and breaking-change assessment |
| 219 | + |
| 220 | +## Exit Codes |
| 221 | + |
| 222 | +- `0`: Success (no NEW issues, or only PRE-EXISTING/ignored issues) |
| 223 | +- `1`: Failure (NEW issues found that need fixing) |
| 224 | + |
| 225 | +## Implementation Steps |
| 226 | + |
| 227 | +When executing this command, the script automatically: |
| 228 | + |
| 229 | +1. **Builds custom golangci-lint with kube-api-linter plugin** |
| 230 | + - Finds base golangci-lint from bingo or bin/ |
| 231 | + - Extracts version from `.bingo/Variables.mk` |
| 232 | + - Creates temporary `.custom-gcl.yml` config with version placeholder |
| 233 | + - Builds custom binary with kube-api-linter plugin using `golangci-lint custom` |
| 234 | + - All files created in temporary directory |
| 235 | + |
| 236 | +2. **Configures linter** |
| 237 | + - Generates temporary `.golangci.yaml` config inline (no templates needed) |
| 238 | + - Configures kube-api-linter with WhenRequired pointer preference |
| 239 | + - Focuses only on `api/` directory |
| 240 | + - Excludes generated files (zz_generated) |
| 241 | + |
| 242 | +3. **Analyzes current and baseline** |
| 243 | + - Runs custom linter on current branch |
| 244 | + - Creates temporary git worktree for `main` branch |
| 245 | + - Runs linter on baseline branch |
| 246 | + - Parses and categorizes results comparing file + linter + message (ignores line number changes) |
| 247 | + |
| 248 | +4. **Filters false positives** |
| 249 | + - Silently ignores `optionalfields` on Status fields |
| 250 | + - Silently ignores `requiredfields` on Spec fields |
| 251 | + - Silently ignores `conditions` markers on `[]metav1.Condition` |
| 252 | + - No false positives shown in output |
| 253 | + |
| 254 | +5. **Presents results** |
| 255 | + - Summary: NEW count, PRE-EXISTING count, changed files |
| 256 | + - NEW issues: Full analysis with `[ERROR]` prefix in red (causes exit code 1) |
| 257 | + - PRE-EXISTING issues: Full analysis with `[WARNING]` prefix in yellow (no failure) |
| 258 | + - Each issue includes: file location, code context, analysis, recommendations, breaking-change assessment |
| 259 | + |
| 260 | +6. **Cleanup** |
| 261 | + - Removes all temporary files and directories |
| 262 | + - Removes git worktree |
| 263 | + - Leaves repository in clean state |
| 264 | + |
| 265 | +## Requirements |
| 266 | + |
| 267 | +- golangci-lint v2.7.2+ (installed via bingo or available in bin/) |
| 268 | +- Git repository with `main` branch as baseline |
| 269 | +- Go toolchain (for building custom golangci-lint) |
| 270 | +- Internet connection (to download kube-api-linter module during custom build) |
| 271 | + |
| 272 | +## Related Documentation |
| 273 | + |
| 274 | +- [Kubernetes API Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md) |
| 275 | +- [kube-api-linter](https://github.com/kubernetes/kubernetes/tree/master/staging/src/k8s.io/code-generator/cmd/kube-api-linter) |
| 276 | +- AGENTS.md in this repository for understanding operator patterns |
| 277 | + |
| 278 | +## Notes |
| 279 | + |
| 280 | +- This command is safe to run at any time - it makes no persistent changes |
| 281 | +- False positives are based on standard Kubernetes CRD scaffolding patterns |
| 282 | +- Analysis considers operator-specific patterns (see AGENTS.md) |
| 283 | +- Breaking change assessments follow Kubernetes API compatibility rules |
0 commit comments