-
Notifications
You must be signed in to change notification settings - Fork 114
Add new configuration APIs/mechanisms for starting to introduce new MR API version policy #585
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
ulucinar
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @sergenyalcin, leaving some comments for us to discuss.
pkg/pipeline/crd.go
Outdated
| // buildEnhancedDeprecationWarning creates an enhanced deprecation warning message | ||
| // by appending release information to the base warning message if available. | ||
| func buildEnhancedDeprecationWarning(deprecation config.VersionDeprecation) string { | ||
| warning := deprecation.Warning |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| warning := deprecation.Warning | |
| warning := strings.TrimSpace(deprecation.Warning) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
pkg/config/resource.go
Outdated
| // is planned to be removed. According to the versioning policy, a version must | ||
| // remain deprecated for at least one release before removal. | ||
| // Format: "v1.3.0" | ||
| RemovalPlannedRelease string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we've a preposition in the name DeprecatedInRelease, for consistency:
| RemovalPlannedRelease string | |
| RemovalPlannedForRelease string |
Or something like DeprecationRelease & PlannedRemovalRelease (or just RemovalRelease concisely). I think these forms are more suitable for a declarative API btw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
pkg/config/resource.go
Outdated
| DeprecatedInRelease string | ||
|
|
||
| // RemovalPlannedRelease indicates the provider release in which this version | ||
| // is planned to be removed. According to the versioning policy, a version must | ||
| // remain deprecated for at least one release before removal. | ||
| // Format: "v1.3.0" | ||
| RemovalPlannedRelease string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are coupling the specific release versions with the resource configurations I believe for the first time, optionally. I think this should be fine because:
- It's optional. Provider authors can opt out if they don't want such coupling.
- We are planning to drive the deprecation policy enforcement as autonomously as possible, i.e., there will be automatic configurators which utilize the information from
VersionDeprecationto drive the policy as we make new releases. - If the provider code lives in multiple repositories (has forks), most probably they will be following the same versioning policy and have the same versions. But if there are forks, this may also not be true and if the provider author does not want to maintain this configuration (even via some sort of automatic configuration), she can still opt out using this optional configuration.
So, I think this is perfectly fine. But because we are introducing a coupling with the resource configurations and the release versions of the providers, I believe it's worth surfacing this.
We could hold this information outside of the resource configuration and specific for a provider's implementation. The current approach makes it universally available for all upjet-based providers rather than being part of a repo specific API versioning policy driver. So I believe it's worth the coupling.
pkg/config/resource.go
Outdated
|
|
||
| // DeprecatedInRelease indicates which provider release deprecated this version. | ||
| // Format: "v1.2.3" | ||
| DeprecatedInRelease string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| DeprecatedInRelease string | |
| DeprecationRelease string |
Please see the comment below also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
pkg/config/resource.go
Outdated
| // Version: "v1beta2" | ||
| // PreviousVersions: []string{} // v1beta1 removed | ||
| // ServedVersions: []string{"v1beta2"} // Only v1beta2 served | ||
| ServedVersions []string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We must ensure that ServedVersions contains Version. It may not be a superset of the set PreviousVersions, i.e., there might be some previous versions that are no longer served. Another constraint could be if a version is not the Version or is not in PreviousVersions, then it cannot be in ServedVersions, i.e., a removed version should not exist in ServedVersions. We will also likely be serving some deprecated versions but not all of them.
We could enforce these rules with a validation, or maybe unexporting ServedVersions and implement the enforcement in accessors. That would also enforce the use of {Set,Get}ServedVersions at the API level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ServedVersions unexported, added accessors and some validations.
pkg/config/resource.go
Outdated
| // Deprecated versions should still be listed in ServedVersions until they are | ||
| // completely removed from the API. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this comment correct? I think we can have some deprecated versions that are no longer served, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching this. Yes this comment isn't accurate. Changed it.
| // - If ServedVersions is explicitly set, it's used as-is | ||
| // - If ServedVersions is empty/nil, all versions are served by default | ||
| // - The returned slice is a new slice to prevent external modifications | ||
| func (r *Resource) GetServedVersions() []string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default implementations looks good for backward compatibility so that if a provider author does not start using the new APIs, we assume all previously configured API versions via the existing Version and PreviousVersions APIs are considered as being served.
One case we need to be careful for backward compatibility is what would happen if a provider author had multiple versions but did not bother to set the PreviousVersions? Is this possible? How do we use PreviousVersions before these changes? Can you check how we previously make use of the PreviousVersions API and leave a comment here for documentation purposes?
If some scenario like this is actually possible, i.e., the PreviousVersions API was optional even with multi-version CRDs, then we may also consider not using ServedVersions if it's not explicitly configured or making this visible in the release notes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About PreviousVersions usage, I added comments to a few places. Also, I will add some statements to the release notes.
In the current situation, the PreviousVersions API is optional and managed by providers. However, if the provider wants to work everything in place (I mean, without the new configuration), they must be set. Because in the current code generation pipeline, we rely on this field being filled.
So at the API level, we don't enforce this, even if the provider has multiversion CRDs. However, in the current code-generation pipeline, we assume this is properly configured.
pkg/pipeline/crd.go
Outdated
| if deprecation.RemovalPlannedRelease != "" { | ||
| notice.WriteString(fmt.Sprintf(" and is planned for removal in release %s.", deprecation.RemovalPlannedRelease)) | ||
| } else { | ||
| notice.WriteString(".") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: If both deprecation and removal releases are empty strings, then the comment is not terminated with a ".".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
pkg/pipeline/crd.go
Outdated
| } | ||
|
|
||
| // Add deprecation information if this version is deprecated | ||
| if deprecation, isDeprecated := cfg.DeprecatedVersions[cfg.Version]; isDeprecated { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't expect the actively being generated API version to be deprecated, right? This code path still generates the types of only the current API version, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One use case must be this for deprecating the last (generated) API version:
The underlying TF provider deprecates some resources in minor versions and removes them in the next major version.
Until today, because we didn't have the resource-level deprecation configuration, we couldn't deprecate them on our side and directly remove resources when we bump the providers in major versions.
As you pointed, in the context of this PR, I mean in the context of multiversion lifecyle of CRDs of resources, this is not expected. However, deprecating a generated version (the latest version) is a legitimate scenario for this type of situation. So I preferred to add this.
pkg/pipeline/crd.go
Outdated
| // Check if this version should be marked as unserved | ||
| // Only mark as unserved if ServedVersions is explicitly configured | ||
| // and the current version is NOT in that list | ||
| if len(cfg.ServedVersions) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Misconfiguration like forgetting to put the current API version into the served versions would result in the current version not being served. Please also see the discussions above. I believe we had better prevent such invalid configuration cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added validation.
pkg/pipeline/hooks.go
Outdated
| }) | ||
| } | ||
|
|
||
| type storageVersionMarkerUpdater struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's break the implementations of these two hooks into their own files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
pkg/pipeline/hooks.go
Outdated
| }) | ||
| } | ||
|
|
||
| type markerUpdater struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's break the implementations of these two hooks into their own files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
pkg/pipeline/hooks.go
Outdated
| }) | ||
| } | ||
|
|
||
| type markerUpdater struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we already have a struct type declaration (markerUpdater), the type can directly implement the PostGenerationHook interface. Or, you can convert updateMarkers into a PostGenerationHookFn but I think having the type is better for breaking its implementation into multiple methods (we can make both work I think).
Please also consider doing this for storageVersionMarkerUpdater.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
pkg/pipeline/hooks.go
Outdated
| }) | ||
| } | ||
|
|
||
| type markerUpdater struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to use higher-level string functions (search/replace/regex, etc.) to simplify this processor instead of using go/{ast,parser,token} maybe with some assumptions on the shape of the generated Go files? Can you give this a try?
We can also consider this for storageVersionMarkerUpdater.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now we don't use the ast, parser, etc. libraries. Because we don't change any Go code, even if these libraries are just used to ensure more robustness, as you pointed out, these usages are a bit complex.
Now I just did some string manipulations by using the strings library. So, the current one is also working on the same logic.
For removal, I used the Replace function directly. However, for additions, some processing is necessary and applied.
pkg/pipeline/run.go
Outdated
| Scope: tjtypes.CRDScopeCluster, | ||
| // Register built-in post-generation hooks | ||
| PostGenerationHooks: []PostGenerationHook{ | ||
| NewStorageVersionMarkerUpdateHook(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A high-level comment: (That we also discussed offline) We would like to break the current monolithic pipeline into smaller, modular steps and be able to selectively generate a single version of a single resource, for example. Once we achieve this, it will become much more feasible for us to generate the selected older API versions of selected resources when we need to do so, like there's an API change in the "preferred" version and we need to update the old versions.
This is currently not practical and we are relying on complex post-processors to work around this. And this also means we are building technical debt when we need updates to the older versions (or simply we rely on manual code updates on previously generated code).
I'm a little bit hesitant with the hooks API we are introducing here, although hooks can be utilized for other purposes than what we are doing here with them (updating the already generated versions).
Furthermore, we also setup a higher level code generation pipeline using the //go:generate markers on Go source, like here. So, the hook API here is a lower-level alternative to that with some convenience of itself being able to programmatically access the upjet resource configuration API.
Because of these reasons, I feel like it's better for us at least not to export this API, even if you'd like to utilize the convenience of accessing the resource configuration (which the new API-related configuration becomes part of).
We could also choose to move the new configuration (ServedVersions, etc.) out of upjet's resource configuration so that the implementations of these two post processors can completely be moved outside of upjet's code generation pipeline, and possibly added to the higher-level go:generate-based pipeline above.
But I still think the current resource configuration is better because:
- Resource configuration determines/affects the shapes of the CRDs generated. Other aspects already live in upjet resource configurations. Why would we then move served version or deprecated version configurations out of upjet's resource configuration?
- Once we achieve a modular upjet code generation pipeline, we will need the resource configuration when re-generating the old versions according to the new version. In my opinion, this is a much cleaner, easier to maintain approach, then trying to post-process the already generated (but no longer auto-generated) code.
Another approach could be the move the new configuration and the post processors both out of upjet's code generation pipeline. But I also think why should we not enjoy the convenience as long as long we don't make this part of the API contract, till we do the refactoring on the code generation pipelines?
TLDR; how about unexporting the hooks APIs? If some unrelated use case pops up (some other use case than modifying the auto-generated code), then we can reconsider exporting them. We already know the drawbacks of updating auto-generated code in post processors so maybe we would like to prioritize the pipeline refactoring over adding new features that will require us to update the older versions. So we may have a change to block further technical debt.
When we export this, with a small probability, other provider maintainers may start relying on these APIs to update the older API versions like we do here. And according to our discussions, I believe we've consensus on preventing this as we this as technical debt. So let's be proactive and block the way to technical debt. Maybe some other use case pops up and then we reconsider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we talked about offline, I agree on the unexporting them. Unexported this hook mechanism.
…em, unexport post generation hook mechanism, in hook implementation remove ast usage and just do with string manipulation, break hook implementations to their own files, apply some nit comments Signed-off-by: Sergen Yalçın <yalcinsergen97@gmail.com>
📝 WalkthroughWalkthroughThis PR adds API version deprecation and served versions support to the Kubernetes API code generator. It introduces configuration types and methods for managing deprecation metadata, implements post-generation hooks to update kubebuilder markers in generated files, and extends CRD templates to conditionally include deprecation and unserved markers. Changes
Sequence DiagramsequenceDiagram
participant Config as Provider Config
participant Resource as Resource API
participant CRDGen as CRD Generator
participant PostHook as Post-Gen Hooks
participant FileSystem as File System
Config->>Resource: SetServedVersions(versions)
Config->>Resource: SetDeprecatedVersion(version, metadata)
Resource->>Resource: Validate & Store
CRDGen->>Resource: GetServedVersions()
CRDGen->>Resource: IsVersionDeprecated(version)
Resource-->>CRDGen: Served list & deprecation metadata
CRDGen->>CRDGen: Build CRD with markers
CRDGen-->>PostHook: Generated files
PostHook->>PostHook: lifecycleMarkerUpdateHook
PostHook->>FileSystem: Read type files
FileSystem-->>PostHook: File content
PostHook->>PostHook: Update unservedversion markers
PostHook->>PostHook: Update deprecatedversion markers
PostHook->>FileSystem: Write updated files
PostHook->>PostHook: storageVersionMarkerUpdateHook
PostHook->>FileSystem: Read previous version files
FileSystem-->>PostHook: File content
PostHook->>PostHook: Add/remove storageversion markers
PostHook->>FileSystem: Write updated files
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@pkg/config/resource.go`:
- Around line 1021-1028: The code currently uses lexicographic string comparison
to validate that deprecation.PlannedRemovalRelease is after
deprecation.DeprecationRelease, which fails for multi-digit semver components;
replace the string comparison with a proper semantic version comparison using
golang.org/x/mod/semver (import it) and call
semver.Compare(deprecation.PlannedRemovalRelease,
deprecation.DeprecationRelease) to ensure the planned removal is strictly
greater; keep the existing error message when the comparison indicates the
planned removal is not after the deprecation.
In `@pkg/pipeline/hooks_deprecated_version.go`:
- Around line 251-281: The current findDescriptionBlock function uses a fixed
look-back limit (typeLineIndex-10) which can skip doc lines when many
kubebuilder markers exist; remove the hard cap and iterate i from
typeLineIndex-1 down to 0, stopping only when you hit the first
non-comment/non-kubebuilder line (but still treating "//" as description lines
and ignoring "// +kubebuilder:" markers), and update
descriptionStartIndex/descriptionEndIndex accordingly; apply the identical
change to the analogous function around lines 337-349 so both scanning routines
no longer use the 10/20-line limits.
🧹 Nitpick comments (4)
pkg/pipeline/crd.go (1)
109-119: Consider simplifying the served version check withslices.Contains.The manual loop to check if the current version is served could be simplified using Go's
slices.Contains(available since Go 1.21). This improves readability and reduces boilerplate.♻️ Suggested refactor
+import "slices" + // Check if this version should be marked as unserved // With SetServedVersions() validation, the current version MUST be included // if served versions are explicitly configured. This check is defensive but should // not trigger under normal circumstances with validated configuration. servedVersions := cfg.GetServedVersions() -isServed := false -for _, v := range servedVersions { - if v == cfg.Version { - isServed = true - break - } -} -if !isServed { +if !slices.Contains(servedVersions, cfg.Version) { crdMap["MarkUnserved"] = "true" }pkg/pipeline/hooks_test.go (1)
245-269: Consider adding test for warning messages with special characters.The test cases use clean warning strings. Consider adding a test case with a warning containing quotes or other special characters to ensure proper escaping in the generated marker.
Example test case to add:
"DeprecatedWithSpecialChars": { reason: "Should handle warning with special characters.", args: args{ content: baseContent, typeName: "Member", version: "v1beta1", isServed: true, isDeprecated: true, deprecation: config.VersionDeprecation{ Warning: `Use "v1beta2" instead`, }, }, want: want{ // Verify escaping behavior }, },pkg/pipeline/hooks_deprecated_version.go (1)
7-15: Usegithub.com/pkg/errorsfor wrapping.The import uses
github.com/crossplane/crossplane-runtime/v2/pkg/errors, but the guidelines requiregithub.com/pkg/errorsfor wrapping/context. Please switch to align with the repo standard.🔧 Suggested import change
- "github.com/crossplane/crossplane-runtime/v2/pkg/errors" + "github.com/pkg/errors"As per coding guidelines, please use
github.com/pkg/errors.pkg/pipeline/hooks_storage_version.go (1)
7-16: Usegithub.com/pkg/errorsfor wrapping.This file also imports
github.com/crossplane/crossplane-runtime/v2/pkg/errors. Guidelines requiregithub.com/pkg/errorsfor wrapping/context.🔧 Suggested import change
- "github.com/crossplane/crossplane-runtime/v2/pkg/errors" + "github.com/pkg/errors"As per coding guidelines, please use
github.com/pkg/errors.
| // Validate release ordering if both are provided | ||
| if deprecation.DeprecationRelease != "" && deprecation.PlannedRemovalRelease != "" { | ||
| // Simple string comparison works for semver format (v1.5.0 < v1.7.0) | ||
| if deprecation.PlannedRemovalRelease <= deprecation.DeprecationRelease { | ||
| return errors.Errorf("PlannedRemovalRelease %q must be after DeprecationRelease %q", | ||
| deprecation.PlannedRemovalRelease, deprecation.DeprecationRelease) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the specific lines mentioned
sed -n '1015,1035p' pkg/config/resource.goRepository: crossplane/upjet
Length of output: 857
🏁 Script executed:
# Check what version patterns are actually used in the codebase
rg -i 'v[0-9]+\.[0-9]+\.[0-9]+' --max-count=20Repository: crossplane/upjet
Length of output: 5749
🏁 Script executed:
# Search for any existing semver libraries imported
rg 'import.*semver|Masterminds|coreos/go-semver' pkg/Repository: crossplane/upjet
Length of output: 42
🌐 Web query:
Go semver version comparison library recommended best practices 2024 2025
💡 Result:
Short summary — recommended Go semver libs (2024–2025) and best practices
Recommended libraries
- golang.org/x/mod/semver — official x/mod package for simple/accurate comparisons (requires leading "v", returns canonical form). [1]
- github.com/Masterminds/semver — feature-rich (parsing, ranges/constraints, v3+ for Go modules). [2]
- github.com/blang/semver (v4) — fast, spec‑compliant, zero‑stdlib approach (value types). [3]
- github.com/gomodules/semver — modern, constraint and sort support, optional "v" handling. [4]
- github.com/aquasecurity/go-version — parses semver-like versions, supports extra components and constraints (useful if you need non‑standard variants). [5]
Practical best practices
- Prefer a well‑maintained library rather than writing custom parsing/comparison. Use golang.org/x/mod/semver for straightforward precedence checks; use Masterminds or blang/gomodules when you need constraints, sorting, or richer parsing. [1][2][3][4]
- Always use Go modules (go.mod) and pin exact versions (avoid
@latest/main) for reproducible builds; vendor if you require hermetic CI. Use go mod tidy and run dependency audits. [6] - Normalize/canonicalize versions before comparing (use library canonical functions) and be explicit about accepting or requiring a leading "v". [1][2]
- Handle pre-release and build metadata according to SemVer 2.0.0 rules (pre-release < release; build metadata ignored for precedence). Use libraries that implement the spec rather than ad hoc rules. [1][2][3]
- Use semantic‑aware constraint checking (e.g., range expressions) from a library when deciding allowed upgrades; rely on MVS behavior but assert constraints in CI. [2][4]
- Keep dependencies updated and monitored (Dependabot or CI checks), add tests for version logic, and document version expectations in your project README. [6]
If you want, I can: (a) show short code examples for Compare/IsValid using x/mod or Masterminds, or (b) recommend one library tailored to your exact needs (requirement: simple compare vs. range/constraint support).
Use a proper semver comparison library instead of string comparison.
String comparison fails for semantic versions with multi-digit components. For example, "v1.10.0" < "v1.9.0" evaluates to true lexicographically, but semantically v1.10.0 comes after v1.9.0. Use golang.org/x/mod/semver (standard library) or another dedicated semver library for correct ordering validation.
Example fix
import "golang.org/x/mod/semver"
// Replace the string comparison with semver comparison
if semver.Compare(deprecation.PlannedRemovalRelease, deprecation.DeprecationRelease) <= 0 {
return errors.Errorf("PlannedRemovalRelease %q must be after DeprecationRelease %q",
deprecation.PlannedRemovalRelease, deprecation.DeprecationRelease)
}🤖 Prompt for AI Agents
In `@pkg/config/resource.go` around lines 1021 - 1028, The code currently uses
lexicographic string comparison to validate that
deprecation.PlannedRemovalRelease is after deprecation.DeprecationRelease, which
fails for multi-digit semver components; replace the string comparison with a
proper semantic version comparison using golang.org/x/mod/semver (import it) and
call semver.Compare(deprecation.PlannedRemovalRelease,
deprecation.DeprecationRelease) to ensure the planned removal is strictly
greater; keep the existing error message when the comparison indicates the
planned removal is not after the deprecation.
| // findDescriptionBlock finds the description comment block before the type | ||
| func findDescriptionBlock(lines []string, typeLineIndex int) (int, int) { | ||
| descriptionStartIndex := -1 | ||
| descriptionEndIndex := -1 | ||
|
|
||
| for i := typeLineIndex - 1; i >= 0 && i >= typeLineIndex-10; i-- { | ||
| line := strings.TrimSpace(lines[i]) | ||
| if line == "" { | ||
| if descriptionEndIndex != -1 { | ||
| break | ||
| } | ||
| continue | ||
| } | ||
| if strings.HasPrefix(line, "//") && !strings.HasPrefix(line, "// +kubebuilder:") { | ||
| if descriptionEndIndex == -1 { | ||
| descriptionEndIndex = i | ||
| } | ||
| descriptionStartIndex = i | ||
| } else { | ||
| if descriptionEndIndex != -1 { | ||
| break | ||
| } | ||
| if strings.HasPrefix(line, "// +kubebuilder:") { | ||
| continue | ||
| } | ||
| break | ||
| } | ||
| } | ||
|
|
||
| return descriptionStartIndex, descriptionEndIndex | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove fixed look‑back limits to avoid missing doc blocks.
The 10/20-line caps can skip the description line when many kubebuilder markers exist, leaving stale or missing deprecation notices. Scanning back to the first non-comment line is safer.
🛠️ Proposed fix
- for i := typeLineIndex - 1; i >= 0 && i >= typeLineIndex-10; i-- {
+ for i := typeLineIndex - 1; i >= 0; i-- {
@@
- for i := typeLineIndex - 1; i >= 0 && i >= typeLineIndex-20; i-- {
+ for i := typeLineIndex - 1; i >= 0; i-- {Also applies to: 337-349
🤖 Prompt for AI Agents
In `@pkg/pipeline/hooks_deprecated_version.go` around lines 251 - 281, The current
findDescriptionBlock function uses a fixed look-back limit (typeLineIndex-10)
which can skip doc lines when many kubebuilder markers exist; remove the hard
cap and iterate i from typeLineIndex-1 down to 0, stopping only when you hit the
first non-comment/non-kubebuilder line (but still treating "//" as description
lines and ignoring "// +kubebuilder:" markers), and update
descriptionStartIndex/descriptionEndIndex accordingly; apply the identical
change to the analogous function around lines 337-349 so both scanning routines
no longer use the 10/20-line limits.
Description of your changes
This PR adds infrastructure for managing the lifecycle of Managed Resource API versions, in accordance with Kubernetes deprecation policies.
ServedVersions: Controls which API versions the API server exposesDeprecatedVersions: Maps versions to deprecation metadata (warning messages, release info)GetServedVersions(): Provides safe access with proper defaulting behavior+kubebuilder:deprecatedversion:warningmarkers for deprecated versions+kubebuilder:unservedversionmarkers for removed versionsNewVersionMarkerUpdateHook(): Updates lifecycle markers in previous version filesNewStorageVersionMarkerUpdateHook(): Ensures only the configured storage version has the markerI have:
make reviewableto ensure this PR is ready for review.backport release-x.ylabels to auto-backport this PR if necessary.How has this code been tested
Tested in the provider-upjet azuread and provider-upjet-gcp. Observed successful generations. Deprecation and serving version markers were observed. There are no unintended changes.
Please see the provider branches that consume this change:
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.