Skip to content

🌱 wip: add kube-api-linter, fix linter issues #2058

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

everettraven
Copy link
Contributor

Description

Reviewer Checklist

  • API Go Documentation
  • Tests: Unit Tests (and E2E Tests, if appropriate)
  • Comprehensive Commit Messages
  • Links to related GitHub Issue(s)

Signed-off-by: Bryce Palmer <bpalmer@redhat.com>
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 26, 2025
Copy link

openshift-ci bot commented Jun 26, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign tmshort for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copy link

netlify bot commented Jun 26, 2025

Deploy Preview for olmv1 ready!

Name Link
🔨 Latest commit c5710f6
🔍 Latest deploy log https://app.netlify.com/projects/olmv1/deploys/685d9e6244a1c00008d8dbd4
😎 Deploy Preview https://deploy-preview-2058--olmv1.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Spec ClusterCatalogSpec `json:"spec"`

// status contains information about the state of the ClusterCatalog such as:
// - Whether or not the catalog contents are being served via the catalog content HTTP server
// - Whether or not the ClusterCatalog is progressing to a new state
// - A reference to the source from which the catalog contents were retrieved
// +optional
Status ClusterCatalogStatus `json:"status,omitempty"`
Status ClusterCatalogStatus `json:"status,omitempty"` //nolint: kubeapilinter
Copy link
Contributor Author

@everettraven everettraven Jun 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This nolint is because of a bug (I think) in kubeapilinter suggesting that status fields in top-level Kind objects should be pointers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the linter is raising it right here.

It should be a pointer because:

  1. .status is optional and only set by the controller.
  2. Value types serialize as status: {} even when empty.
    Pointer types allow the field to be omitted (cleaner JSON).
  3. Follows Kubernetes API conventions — e.g. Deployment, CronJob use *Status.
  4. The user does not set that just the controllers

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter is correct, optional structs, unless they have omitzero, must be pointers.

Otherwise the structured client will marshal {} and that may have a semantic difference (e.g. CEL rules are triggered here where they may not have been before)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like there were recent discussions with Kubernetes API reviewers that this makes sense as-is that I was unaware of and thought there were discussions going the other way that i was referencing.

This should indeed be a pointer.

Comment on lines -134 to +137
// +kubebuilder:validation:minimum:=-2147483648
// +kubebuilder:validation:maximum:=2147483647
// +kubebuilder:validation:Minimum:=-2147483648
// +kubebuilder:validation:Maximum:=2147483647
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a typo that meant this was not properly being enforced. This is now a breaking change as these APIs have already been released as v1.

I'll leave it up to the maintainers as to how this should be handled. Some options I can quickly think of:

  • make the change, expect ratcheting validations to take over where used in production. Seems unlikely that users could have reasonably specified something out of these bounds.
  • don't make the change. Leave unvalidated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IHMO, we should make this change.
It is a bug, not a breaking change.
We are not validating something that we should validate.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, given that this is the entire range of a valid int32... The odds of someone hitting an issue because we fixed this typo is very, very, very low

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because it is a bug does not make it a non-breaking change. This can be both, but it is most certainly a "breaking change" in the sense that you are adding restrictions to something that previously had no restrictions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether or not this is a reasonably safe change is an entirely separate question.

// +kubebuilder:validation:minimum:=-2147483648
// +kubebuilder:validation:maximum:=2147483647
// +kubebuilder:validation:Minimum:=-2147483648
// +kubebuilder:validation:Maximum:=2147483647
// +optional
Priority int32 `json:"priority"`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a pointer with omitempty as the 0 value is a valid value, but that would be a breaking change. Also leaving up to maintainers how they might want to handle this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have a bug in the linter here.

I am applying Kubebuilder to all scenarios, so we can discuss each one to shape not only Kubebuilder scaffolds and samples, but also the linter.

But IMO, this should not be a pointer because:

  • Zero (0) is a meaningful default, not absence.
  • The spec comment already treats 0 as the intended default.
  • We also have +kubebuilder:default:=0, which works perfectly with a value type.
  • There's no need to distinguish between unset and zero.

So, IHMO it should be fine as:

// +optional
+kubebuilder:default:=0
Priority int32 json:"priority"

pinging @JoelSpeed he might can help us to know more about

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very special case, and while I agree with what @camilamacedo86 is saying, there are some nuances.

The field is optional, so in general, we would want to be able to tell between unset and the zero value.

The field has a default, this means that for any client, should they omit the value, it would be set by the API server to 0.

If the value were non-zero, it's obvious that this needs to be a pointer with omitempty (I hope that's obvious? Challenge me otherwise and I'll explain).

The nuance that is missed here is who is doing the defaulting.

If we leave this field as a non-pointer, non-omitempty, this means that the structured client happens to marshal the default value when non other opinion is set.

If this were a pointer+omitempty, then the structured client would not marshal anything, and the API server would default the value for them, and on future round trips, the value would be set, because 0 is not a nil pointer.

So, for this case, yes, what is there currently works, but it's not "correct". The structured client has no way to specify "I have no opinion, please apply a default value".

IMO, this should be pointer and omitempty and have the API server apply the default, which would be consistent with any other field that is optional and has a valid zero value.

Copy link
Contributor

@camilamacedo86 camilamacedo86 Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for the detailed and very comprehensive info

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is priority truly optional here then? If it is truly optional then it should be a pointer to distinguish between unset and the valid zero value.

Based on what you've said, it sounds to me like this field is actually required but allows the zero value.

Type SourceType `json:"type"`
// image is a field containing resolution information for a catalog sourced from an image.
// This field must be set when type is Image, and forbidden otherwise.
Image *ResolvedImageSource `json:"image"`
// +optional
Image *ResolvedImageSource `json:"image,omitempty"`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe adding the omitempty would technically be a breaking change here as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, but in this case we have a check already see;

// +union
// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type == 'Image' ? has(self.image) : !has(self.image)",message="image is required when source type is Image, and forbidden otherwise"

Adding omitempty ensures that when Image is nil, it’s not serialized at all, which is aligned with the above validation rule and union semantics as far I get it. So, I think it’s fair to call this a small bug fix in the CRD/spec — we’re not changing the API behavior, just ensuring that invalid/unnecessary output doesn't get emitted.

c/c @joelanford wdyt?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since, without omitempty, this would render to null, and the field does not have +nullable, any request from a structured client that did not set this would have been rejected.

You've also added the optional tag which takes this from required to optional, which is correct from the DU perspective, and as @camilamacedo86 points out, doesn't matter anyway because of the CEL.

As far as I can tell, requests from structured clients before and after this change behave in the same way (reject or accept, error message may vary), +1 to go ahead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@camilamacedo86 That sounds right to me. If there is no way for this to be set to image: null today, then adding the omitempty would not change serialization.

@@ -344,7 +349,7 @@ type ImageSource struct {
// When omitted, the image will not be polled for new content.
// +kubebuilder:validation:Minimum:=1
// +optional
PollIntervalMinutes *int `json:"pollIntervalMinutes,omitempty"`
PollIntervalMinutes *int `json:"pollIntervalMinutes,omitempty"` //nolint: kubeapilinter
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nolint here because it wants this to be either int32 or int64 and not a pointer which would be a breaking change. Although I should probably remove the nolint

Copy link
Contributor

@camilamacedo86 camilamacedo86 Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, in your POV, should we consider it a bug fix and move forward?
I think it might be acceptable; it will not change the expected value for end users, but we will need to change our internal code.

@joelanford WDYT?
Could we do the change?

@@ -244,6 +243,7 @@ type CatalogFilter struct {
// For more information on semver, please see https://semver.org/
//
// +kubebuilder:validation:MaxLength:=64
// +kubebuilder:validation:MinLength:=1
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking change because explicitly setting version: "" is no longer valid, but IIRC the original intention was to actually have it so you either don't provide this or explicitly provide a range so this would be more appropriate.

Ratcheting should take effect here, but again - up to maintainer discretion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we set "version: """ today?
If that is not allowed already, then we might be able to move forward here as a bug fix.
We are just trying to catch the invalid scenario before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I understand, this would be accepted as a valid input.

@@ -369,7 +369,8 @@ type PreflightConfig struct {
//
// The CRD Upgrade Safety pre-flight check safeguards from unintended
// consequences of upgrading a CRD, such as data loss.
CRDUpgradeSafety *CRDUpgradeSafetyPreflightConfig `json:"crdUpgradeSafety"`
// +optional
CRDUpgradeSafety *CRDUpgradeSafetyPreflightConfig `json:"crdUpgradeSafety,omitempty"`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that adding omitempty here is also a breaking change because it changes serialization (i.e crdUpgradeSafety: null as opposed to it jusst being totally missing.

That being said, the CEL validation and the fact that the parent field is omitempty might make it so that this is never actually serialized with a null value, meaning this might be OK.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right.
@joelanford is the right person here to 🔨 .
WDYT?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field isn't nullable so an empty value would have marshalled to null and would have been rejected, right?

destination: ./bin
plugins:
- module: 'sigs.k8s.io/kube-api-linter'
version: 'v0.0.0-20250626111229-e719da12d840' # Replace with the latest version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not using version: v1.64.8 any more
We need config it with 2x

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't seem to be true based on what I gathered from the bingo files that set golangci-lint to 1.64.8.

If this recently changed, I can look into updating this. I'll hold off on this until other items that I think are more important are addressed in the PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you opened the PR, it was using Go 1.64.8 :-)
But that’s no longer the case now.

The issue is that we can’t use .custom-gcl.yml with GolangCI-Lint v2.x.
You can see how I was testing it in Kubebuilder here:
https://github.com/kubernetes-sigs/kubebuilder/pull/4888/files

I don’t think it’s the best way to install it, but it might help as a reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Golangci-lint version still shows as 1.64.8 on main to me:

require github.com/golangci/golangci-lint v1.64.8 // cmd/golangci-lint

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OH, we did not merged : https://github.com/operator-framework/operator-controller/pull/2045/files
^ can you help me out, get this one merged so we do it right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I no longer consider myself a maintainer of this project so I'll defer that to current maintainers to get it in. I'll update the configuration in the PR accordingly once that lands.

@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 27, 2025
@openshift-merge-robot
Copy link

PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants