Skip to content

Add statusoptional linter #73

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

Merged
merged 1 commit into from
May 14, 2025

Conversation

yongruilin
Copy link
Contributor

This PR introduces a new linter, statusoptional, which checks that all first-level children fields within a status struct are marked as optional. It adds functionality to automatically suggest fixes for fields that are missing the appropriate markers.

Fix #31

@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Apr 29, 2025
@yongruilin yongruilin force-pushed the statusoptional branch 3 times, most recently from 357ccbf to 22348c8 Compare April 29, 2025 22:48
Copy link
Contributor

@JoelSpeed JoelSpeed left a comment

Choose a reason for hiding this comment

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

In the optionalorrequired linter, we give the user a choice of which optional tag they prefer, we probably want to do the same here so that the suggested fix uses their preferred marker, right?

@yongruilin yongruilin force-pushed the statusoptional branch 4 times, most recently from 2f69191 to 10f15c4 Compare April 30, 2025 22:42
// +kubebuilder:validation:Optional
Reason string `json:"reason"`

State `json:",inline"` // want "status field \"State\" must be marked as optional"
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't correct. You can't mark an embedded field as optional, it would have no effect on the openapi schema.

The child of this embedded field however should be marked as optional, which I think you have already

Copy link

@jpbetz jpbetz May 1, 2025

Choose a reason for hiding this comment

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

A go embed withjson:,inline cannot be optional, but a non-inlined go embed can (embed must be a pointer). Today we embed ObjectMeta but don't inline it and tag it with json:metadata,omitempty. ObjectMeta is not a great example because it cannot be empty, but a pointer embed struct can.

if you want full test coverage it's quite a few cases: Embed inlined, embed non-inlined (omit empty is allowed but doesn't do anything), pointer embed non-inlined, pointer embed non-inlined omitempty. On and we've got omitzero support coming so: embed non-inlined omitzero and pointer embed non-inlined omitzero too..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added more test cases.

}

// MyResourceStatus is a sample status with a mix of optional and non-optional fields.
type MyResourceStatus struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add an optional struct field to this type that then has required members within it. The linter shouldn't report anything for the members of the new struct as they would be second level

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a test case for it.

@JoelSpeed
Copy link
Contributor

@yongruilin just wanted to check if you saw #73 (review)

}

// MyResourceStatus is a sample status with a mix of optional and non-optional fields.
type MyResourceStatus struct {
Copy link
Member

Choose a reason for hiding this comment

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

When the following field is added to struct

// +required
RequiredField string `json:"requiredField"`

The diagnostic works fine, but the suggesting results in followng.

//+required
//+optional
RequiredField string `json"requiredField"`

Fixed code can compile, but the schema doesn't make sense. So we should handle it if the filed has already required field.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, I updated to handle this case.

@yongruilin yongruilin force-pushed the statusoptional branch 2 times, most recently from 14cf805 to 025fc57 Compare May 6, 2025 22:10
@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels May 6, 2025
@yongruilin yongruilin force-pushed the statusoptional branch 2 times, most recently from 3db7fa8 to 511c165 Compare May 6, 2025 22:16
@yongruilin
Copy link
Contributor Author

@yongruilin just wanted to check if you saw #73 (review)

Apologize, I was a bit tied up last week.
I should've address the comments. PTAL.

@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 7, 2025
Comment on lines 35 to 45
// OptionalMarker is the marker that indicates that a field is optional.
OptionalMarker = "optional"

// KubebuilderOptionalMarker is the marker that indicates that a field is optional in kubebuilder.
KubebuilderOptionalMarker = "kubebuilder:validation:Optional"

// RequiredMarker is the marker that indicates that a field is required.
RequiredMarker = "required"

// KubebuilderRequiredMarker is the marker that indicates that a field is required in kubebuilder.
KubebuilderRequiredMarker = "kubebuilder:validation:Required"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// OptionalMarker is the marker that indicates that a field is optional.
OptionalMarker = "optional"
// KubebuilderOptionalMarker is the marker that indicates that a field is optional in kubebuilder.
KubebuilderOptionalMarker = "kubebuilder:validation:Optional"
// RequiredMarker is the marker that indicates that a field is required.
RequiredMarker = "required"
// KubebuilderRequiredMarker is the marker that indicates that a field is required in kubebuilder.
KubebuilderRequiredMarker = "kubebuilder:validation:Required"

You can delete these entirely, then suggest to use markers constant instead.

https://github.com/kubernetes-sigs/kube-api-linter/blob/main/pkg/markers/markers.go

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated, is it recently added?


func Test(t *testing.T) {
testdata := analysistest.TestData()
analysistest.RunWithSuggestedFixes(t, testdata, newAnalyzer("optional"), "a")
Copy link
Contributor

Choose a reason for hiding this comment

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

Should add a test case for the alternative optional marker too, should be fairly simple to copy the existing cases across

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added the test cases for kubebuilder optional and k8s:optional.

Comment on lines 182 to 192
// Handle standard required markers
if fieldMarkers.Has(RequiredMarker) {
for _, marker := range fieldMarkers[RequiredMarker] {
textEdits = append(textEdits, analysis.TextEdit{
Pos: marker.Pos,
End: marker.End,
// Remove the marker completely (will add preferred one separately)
NewText: []byte(""),
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

In other packages we have helpers for removing the markers, perhaps we can copy that pattern and extract this to a helper to reduce the duplication between these lines and the block below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Left a TODO on it. I prefer to have a separate PR to consolidate the usage.

@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 8, 2025
type StatusOptionalConfig struct {
// preferredOptionalMarker is the preferred marker to use for optional fields.
// If this field is not set, the default value is "optional".
// Valid values are "optional" and "kubebuilder:validation:Optional".
Copy link
Contributor

Choose a reason for hiding this comment

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

And the K8s version now right? Or do you not want to include that in the documentation until the declarative validation project is further along?

fieldErrors := field.ErrorList{}

switch soc.PreferredOptionalMarker {
case "", markers.OptionalMarker, markers.KubebuilderOptionalMarker:
Copy link
Contributor

Choose a reason for hiding this comment

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

As above, should this include the K8s version?

switch soc.PreferredOptionalMarker {
case "", markers.OptionalMarker, markers.KubebuilderOptionalMarker, markers.K8sOptionalMarker:
default:
fieldErrors = append(fieldErrors, field.Invalid(fldPath.Child("preferredOptionalMarker"), soc.PreferredOptionalMarker, fmt.Sprintf("invalid value, must be one of %q, %q or omitted", markers.OptionalMarker, markers.KubebuilderOptionalMarker)))
Copy link
Contributor

Choose a reason for hiding this comment

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

This line needs updating to include the K8s version of the optional

This commit introduces a new linter, `statusoptional`, which checks that all first-level children fields within a status struct are marked as optional. It adds functionality to automatically suggest fixes for fields that are missing the appropriate markers.
@JoelSpeed
Copy link
Contributor

I think this is looking all good and correct now, @yongruilin are you happy with it?

@yongruilin
Copy link
Contributor Author

Yea, it looks good to me as well now.

@yongruilin
Copy link
Contributor Author

@JoelSpeed anything else in your mind for this PR?

@JoelSpeed
Copy link
Contributor

/lgtm
/approve

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label May 14, 2025
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: JoelSpeed, yongruilin

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

The pull request process is described 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

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label May 14, 2025
@k8s-ci-robot k8s-ci-robot merged commit 25b7231 into kubernetes-sigs:main May 14, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

New Linter: statusoptional
5 participants