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
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .custom-gcl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: v1.64.8
name: golangci-kube-api-linter
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.

21 changes: 21 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ linters:
- unparam
- unused
- whitespace
- kubeapilinter

linters-settings:
gci:
Expand Down Expand Up @@ -69,7 +70,27 @@ linters-settings:
alias: bsemver
- pkg: "^github.com/operator-framework/operator-controller/internal/util/([^/]+)$"
alias: "${1}util"

custom:
kubeapilinter:
type: "module"
description: Kube API Linter lints Kube like APIs based on API conventions and best practices.
settings:
linters: {}
lintersConfig:
optionalFields:
pointers:
preference: WhenRequired
policy: Warn
omitempty:
policy: Warn

output:
formats:
- format: tab

issues:
exclude-rules:
- path-except: "api/*"
linters:
- kubeapilinter
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ help-extended: #HELP Display extended help.

.PHONY: lint
lint: lint-custom $(GOLANGCI_LINT) #HELP Run golangci linter.
$(GOLANGCI_LINT) run --build-tags $(GO_BUILD_TAGS) $(GOLANGCI_LINT_ARGS)
$(GOLANGCI_LINT) custom
./bin/golangci-kube-api-linter run --build-tags $(GO_BUILD_TAGS) $(GOLANGCI_LINT_ARGS)

.PHONY: custom-linter-build
custom-linter-build: #EXHELP Build custom linter
Expand Down
31 changes: 18 additions & 13 deletions api/v1/clustercatalog_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,27 @@
// ClusterCatalog enables users to make File-Based Catalog (FBC) catalog data available to the cluster.
// For more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs
type ClusterCatalog struct {
// +optional
metav1.TypeMeta `json:",inline"`

// metadata is the standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata"`

// spec is the desired state of the ClusterCatalog.
// spec is required.
// The controller will work to ensure that the desired
// catalog is unpacked and served over the catalog content HTTP server.
// +kubebuilder:validation:Required
// +required
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.

}

//+kubebuilder:object:root=true
Expand All @@ -87,7 +89,7 @@

// items is a list of ClusterCatalogs.
// items is required.
// +kubebuilder:validation:Required
// +required
Items []ClusterCatalog `json:"items"`
}

Expand All @@ -110,7 +112,7 @@
// image:
// ref: quay.io/operatorhubio/catalog:latest
//
// +kubebuilder:validation:Required
// +required
Source CatalogSource `json:"source"`

// priority allows the user to define a priority for a ClusterCatalog.
Expand All @@ -131,10 +133,10 @@
// The highest possible value is 2147483647.
//
// +kubebuilder:default:=0
// +kubebuilder:validation:minimum:=-2147483648
// +kubebuilder:validation:maximum:=2147483647
// +kubebuilder:validation:Minimum:=-2147483648
// +kubebuilder:validation:Maximum:=2147483647
Comment on lines -134 to +137
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.

// +optional
Priority int32 `json:"priority"`

Check failure on line 139 in api/v1/clustercatalog_types.go

View workflow job for this annotation

GitHub Actions / lint

optionalfields: field Priority is optional and should have the omitempty tag (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.

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.


// availabilityMode allows users to define how the ClusterCatalog is made available to clients on the cluster.
// availabilityMode is optional.
Expand Down Expand Up @@ -179,6 +181,8 @@
// contents. This could occur when we've initially fetched the latest contents from the source for this catalog and when polling for changes
// to the contents we identify that there are updates to the contents.
//
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
// +optional
Expand Down Expand Up @@ -214,7 +218,7 @@
//
// As the needs of users and clients of the evolve, new endpoints may be added.
//
// +kubebuilder:validation:Required
// +required
// +kubebuilder:validation:MaxLength:=525
// +kubebuilder:validation:XValidation:rule="isURL(self)",message="must be a valid URL"
// +kubebuilder:validation:XValidation:rule="isURL(self) ? (url(self).getScheme() == \"http\" || url(self).getScheme() == \"https\") : true",message="scheme must be either http or https"
Expand All @@ -236,7 +240,7 @@
//
// +unionDiscriminator
// +kubebuilder:validation:Enum:="Image"
// +kubebuilder:validation:Required
// +required
Type SourceType `json:"type"`
// image is used to configure how catalog contents are sourced from an OCI image.
// This field is required when type is Image, and forbidden otherwise.
Expand All @@ -258,19 +262,20 @@
//
// +unionDiscriminator
// +kubebuilder:validation:Enum:="Image"
// +kubebuilder:validation:Required
// +required
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.

}

// ResolvedImageSource provides information about the resolved source of a Catalog sourced from an image.
type ResolvedImageSource struct {
// ref contains the resolved image digest-based reference.
// The digest format is used so users can use other tooling to fetch the exact
// OCI manifests that were used to extract the catalog contents.
// +kubebuilder:validation:Required
// +required
// +kubebuilder:validation:MaxLength:=1000
// +kubebuilder:validation:XValidation:rule="self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\\\b')",message="must start with a valid domain. valid domains must be alphanumeric characters (lowercase and uppercase) separated by the \".\" character."
// +kubebuilder:validation:XValidation:rule="self.find('(\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') != \"\"",message="a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters."
Expand Down Expand Up @@ -325,7 +330,7 @@
// An example of a valid digest-based image reference is "quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05"
// An example of a valid tag-based image reference is "quay.io/operatorhubio/catalog:latest"
//
// +kubebuilder:validation:Required
// +required
// +kubebuilder:validation:MaxLength:=1000
// +kubebuilder:validation:XValidation:rule="self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\\\b')",message="must start with a valid domain. valid domains must be alphanumeric characters (lowercase and uppercase) separated by the \".\" character."
// +kubebuilder:validation:XValidation:rule="self.find('(\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') != \"\"",message="a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters."
Expand All @@ -344,7 +349,7 @@
// 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?

}

func init() {
Expand Down
42 changes: 24 additions & 18 deletions api/v1/clusterextension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type ClusterExtensionSpec struct {
// +kubebuilder:validation:MaxLength:=63
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="namespace is immutable"
// +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\")",message="namespace must be a valid DNS1123 label"
// +kubebuilder:validation:Required
// +required
Namespace string `json:"namespace"`

// serviceAccount is a reference to a ServiceAccount used to perform all interactions
Expand All @@ -68,7 +68,7 @@ type ClusterExtensionSpec struct {
// The ServiceAccount must exist in the namespace referenced in the spec.
// serviceAccount is required.
//
// +kubebuilder:validation:Required
// +required
ServiceAccount ServiceAccountReference `json:"serviceAccount"`

// source is a required field which selects the installation source of content
Expand All @@ -84,7 +84,7 @@ type ClusterExtensionSpec struct {
// catalog:
// packageName: example-package
//
// +kubebuilder:validation:Required
// +required
Source SourceConfig `json:"source"`

// install is an optional field used to configure the installation options
Expand Down Expand Up @@ -112,7 +112,7 @@ type SourceConfig struct {
//
// +unionDiscriminator
// +kubebuilder:validation:Enum:="Catalog"
// +kubebuilder:validation:Required
// +required
SourceType string `json:"sourceType"`

// catalog is used to configure how information is sourced from a catalog.
Expand Down Expand Up @@ -162,11 +162,10 @@ type CatalogFilter struct {
//
// [RFC 1123]: https://tools.ietf.org/html/rfc1123
//
// +kubebuilder:validation.Required
// +required
// +kubebuilder:validation:MaxLength:=253
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="packageName is immutable"
// +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="packageName must be a valid DNS1123 subdomain. It must contain only lowercase alphanumeric characters, hyphens (-) or periods (.), start and end with an alphanumeric character, and be no longer than 253 characters"
// +kubebuilder:validation:Required
PackageName string `json:"packageName"`

// version is an optional semver constraint (a specific version or range of versions). When unspecified, the latest version available will be installed.
Expand Down Expand Up @@ -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.

// +kubebuilder:validation:XValidation:rule="self.matches(\"^(\\\\s*(=||!=|>|<|>=|=>|<=|=<|~|~>|\\\\^)\\\\s*(v?(0|[1-9]\\\\d*|[x|X|\\\\*])(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*]))?(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*))?(-([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?(\\\\+([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?)\\\\s*)((?:\\\\s+|,\\\\s*|\\\\s*\\\\|\\\\|\\\\s*)(=||!=|>|<|>=|=>|<=|=<|~|~>|\\\\^)\\\\s*(v?(0|[1-9]\\\\d*|x|X|\\\\*])(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*))?(\\\\.(0|[1-9]\\\\d*|x|X|\\\\*]))?(-([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?(\\\\+([0-9A-Za-z\\\\-]+(\\\\.[0-9A-Za-z\\\\-]+)*))?)\\\\s*)*$\")",message="invalid version expression"
// +optional
Version string `json:"version,omitempty"`
Expand Down Expand Up @@ -356,7 +356,7 @@ type ServiceAccountReference struct {
// +kubebuilder:validation:MaxLength:=253
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="name is immutable"
// +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="name must be a valid DNS1123 subdomain. It must contain only lowercase alphanumeric characters, hyphens (-) or periods (.), start and end with an alphanumeric character, and be no longer than 253 characters"
// +kubebuilder:validation:Required
// +required
Name string `json:"name"`
}

Expand All @@ -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?

}

// CRDUpgradeSafetyPreflightConfig is the configuration for CRD upgrade safety preflight check.
Expand All @@ -386,7 +387,7 @@ type CRDUpgradeSafetyPreflightConfig struct {
// performing an upgrade operation.
//
// +kubebuilder:validation:Enum:="None";"Strict"
// +kubebuilder:validation:Required
// +required
Enforcement CRDUpgradeSafetyEnforcement `json:"enforcement"`
}

Expand All @@ -411,20 +412,21 @@ type BundleMetadata struct {
// hyphens (-) or periods (.), start and end with an alphanumeric character,
// and be no longer than 253 characters.
//
// +kubebuilder:validation:Required
// +required
// +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$\")",message="packageName must be a valid DNS1123 subdomain. It must contain only lowercase alphanumeric characters, hyphens (-) or periods (.), start and end with an alphanumeric character, and be no longer than 253 characters"
Name string `json:"name"`

// version is a required field and is a reference to the version that this bundle represents
// version follows the semantic versioning standard as defined in https://semver.org/.
//
// +kubebuilder:validation:Required
// +required
// +kubebuilder:validation:XValidation:rule="self.matches(\"^([0-9]+)(\\\\.[0-9]+)?(\\\\.[0-9]+)?(-([-0-9A-Za-z]+(\\\\.[-0-9A-Za-z]+)*))?(\\\\+([-0-9A-Za-z]+(-\\\\.[-0-9A-Za-z]+)*))?\")",message="version must be well-formed semver"
Version string `json:"version"`
}

// ClusterExtensionStatus defines the observed state of a ClusterExtension.
type ClusterExtensionStatus struct {
// conditions represents the observed state of the cluster extension.
// The set of condition types which apply to all spec.source variations are Installed and Progressing.
//
// The Installed condition represents whether or not the bundle has been installed for this ClusterExtension.
Expand Down Expand Up @@ -463,7 +465,7 @@ type ClusterExtensionInstallStatus struct {
// A "bundle" is a versioned set of content that represents the resources that
// need to be applied to a cluster to install a package.
//
// +kubebuilder:validation:Required
// +required
Bundle BundleMetadata `json:"bundle"`
}

Expand All @@ -478,16 +480,20 @@ type ClusterExtensionInstallStatus struct {

// ClusterExtension is the Schema for the clusterextensions API
type ClusterExtension struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// +optional
metav1.TypeMeta `json:",inline"`

// spec is an optional field that defines the desired state of the ClusterExtension.
// metadata is the object metadata
// +optional
Spec ClusterExtensionSpec `json:"spec,omitempty"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// spec is a required field that defines the desired state of the ClusterExtension.
// +required
Spec ClusterExtensionSpec `json:"spec"`

// status is an optional field that defines the observed state of the ClusterExtension.
// +optional
Status ClusterExtensionStatus `json:"status,omitempty"`
Status ClusterExtensionStatus `json:"status,omitempty"` //nolint: kubeapilinter
}

// +kubebuilder:object:root=true
Expand All @@ -501,7 +507,7 @@ type ClusterExtensionList struct {

// items is a required list of ClusterExtension objects.
//
// +kubebuilder:validation:Required
// +required
Items []ClusterExtension `json:"items"`
}

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golangci/plugin-module-register v0.1.1 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.25.0 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
Expand Down Expand Up @@ -251,6 +252,7 @@ require (
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect
sigs.k8s.io/gateway-api v1.1.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kube-api-linter v0.0.0-20250626111229-e719da12d840 // indirect
sigs.k8s.io/kustomize/api v0.19.0 // indirect
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c=
github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY=
Expand Down Expand Up @@ -792,6 +794,8 @@ sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM=
sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kube-api-linter v0.0.0-20250626111229-e719da12d840 h1:zYPk3+59kzZB2HurKhyNnqYQ2ZhskB5blDJEb/TNA9E=
sigs.k8s.io/kube-api-linter v0.0.0-20250626111229-e719da12d840/go.mod h1:eLCPJVcvhVcNkLOGu2IFzkF5ZpdNjrm+azKaxS+x4IQ=
sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
Expand Down
Loading