Skip to content

Enable the RequiredFields Kube-API-Linter rule #131884

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

JoelSpeed
Copy link
Contributor

What type of PR is this?

/kind api-change

What this PR does / why we need it:

This PR enables the requiredfield linter rule of KAL.

In short:

requiredFields is a linter to check that fields that are marked as required are not pointers, and do not have the omitempty tag.
The linter will check for fields that are marked as required using the +required marker, or the +kubebuilder:validation:Required marker.

The linter will suggest to remove the omitempty tag from fields that are marked as required, but have the omitempty tag.
The linter will suggest to remove the pointer type from fields that are marked as required.

Enabling this check on K/K flagged 19 existing issues where we have examples of fields marked +required but that also had omitempty, or are pointers.

ERROR: staging/src/k8s.io/api/networking/v1alpha1/types.go:52:2: requiredfields: field ParentRef is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1alpha1/types.go:62:2: requiredfields: field Resource is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1alpha1/types.go:68:2: requiredfields: field Name is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1beta1/types.go:454:2: requiredfields: field ParentRef is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1beta1/types.go:464:2: requiredfields: field Resource is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1beta1/types.go:470:2: requiredfields: field Name is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/apiserverinternal/v1alpha1/types.go:120:2: requiredfields: field Message is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/coordination/v1alpha2/types.go:77:2: requiredfields: field Strategy is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1beta1/types.go:192:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1beta1/types.go:359:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/coordination/v1beta1/types.go:150:2: requiredfields: field Strategy is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1alpha3/types.go:197:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1alpha3/types.go:364:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1alpha3/types.go:1602:2: requiredfields: field Taint is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1/types.go:668:2: requiredfields: field ParentRef is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1/types.go:678:2: requiredfields: field Resource is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1/types.go:684:2: requiredfields: field Name is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1beta2/types.go:192:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1beta2/types.go:347:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)

4 of these cases refer to approximately the same type, {Ingress}PortStatus which has fields marked as +optional, but also +kubebuilder:validation:Required. This makes them flag as required for the purpose of this linter, but, they are considered optional within Kube core types. In this case I've added an exception.

For the remaining cases:

ERROR: staging/src/k8s.io/api/apiserverinternal/v1alpha1/types.go:120:2: requiredfields: field Message is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/coordination/v1beta1/types.go:150:2: requiredfields: field Strategy is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/coordination/v1alpha2/types.go:77:2: requiredfields: field Strategy is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1beta2/types.go:192:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1beta2/types.go:347:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1beta1/types.go:192:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1beta1/types.go:359:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1alpha3/types.go:197:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/resource/v1alpha3/types.go:364:2: requiredfields: field Counters is marked as required, but has the omitempty tag (kubeapilinter)

For each of the above errors, the field validation that prevents the field length from being 0. So whether the JSON serialization omits the value, or marshals the zero value ("counters": {}, "strategy": "") doesn't appear to matter, both would be invalid. These are also all in alpha and beta API types. I believe the omitempty can be removed in these cases.

ERROR: staging/src/k8s.io/api/networking/v1alpha1/types.go:52:2: requiredfields: field ParentRef is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1/types.go:668:2: requiredfields: field ParentRef is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1beta1/types.go:454:2: requiredfields: field ParentRef is marked as required, but has the omitempty tag (kubeapilinter)

ERROR: staging/src/k8s.io/api/networking/v1/types.go:678:2: requiredfields: field Resource is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1/types.go:684:2: requiredfields: field Name is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1beta1/types.go:464:2: requiredfields: field Resource is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1beta1/types.go:470:2: requiredfields: field Name is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1alpha1/types.go:62:2: requiredfields: field Resource is marked as required, but has the omitempty tag (kubeapilinter)
ERROR: staging/src/k8s.io/api/networking/v1alpha1/types.go:68:2: requiredfields: field Name is marked as required, but has the omitempty tag (kubeapilinter)

The ParentRef, Resource and Name are all validated in this validation which checks the ParentRef itself is not nil, and that the Resource and Name are not empty.

Fixing Name and Resource is like the above, the difference between omitted and empty wouldn't make a difference to the validation.

For the ParentRef, if we removed omitempty, the JSON representation would then be null, since the field is a pointer. I think this one should not be fixed.

ERROR: staging/src/k8s.io/api/resource/v1alpha3/types.go:1602:2: requiredfields: field Taint is marked as required, but has the omitempty tag (kubeapilinter)

The Taint field uses a type DeviceTaint, which is a struct. The Taint field does not use a pointer, and so, since the Go JSON library cannot omit a struct reference, the omitempty on this field is pointless and can definitely be removed, as it doesn't change the serialization at all.

Which issue(s) this PR fixes:

Fixes #

Special notes for your reviewer:

@thockin This will be the first actual rule enabling checks from KAL. Starting with this one as it seems fairly straightforward and it hasn't got too many existing issues to look at fixing. Would appreciate if you could validate my logic on the acceptable omitempty removals above.

Does this PR introduce a user-facing change?

NONE

Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:


@k8s-ci-robot k8s-ci-robot added release-note-none Denotes a PR that doesn't merit a release note. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API size/M Denotes a PR that changes 30-99 lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. labels May 21, 2025
@k8s-triage-robot
Copy link

This PR may require API review.

If so, when the changes are ready, complete the pre-review checklist and request an API review.

Status of requested reviews is tracked in the API Review project.

# All four of these files contain a variant of {Ingress}PortStatus, which has an Error field marked as optional and kubebuilder:validation:Required.
# This means the behaviour of the field is inconsistent for core types vs CRDs, and should not be fixed.
# It also means the requiredfield linter complains about the fields being pointers, which they must be for the core types.
- path: "staging/src/k8s.io/api/core/v1/types.go|staging/src/k8s.io/api/extensions/v1beta1/types.go|staging/src/k8s.io/api/networking/v1beta1/types.go|staging/src/k8s.io/api/networking/v1/types.go"
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand what this is saying. The field appears to be annotated incorrectly; it claims kubebuilder:validation:Required, but the actual API validation does not require it. Why shouldn't we just remove the annotation?

(Also, why do you mention CRDs here?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Anyone who is creating a CRD that imports these types somewhere into their API, would fall into this switch case in the CRD schema generator and would hit kubebuilder:validation:Required rather than optional.

This means that the CRD schema that is generated would say that the Error field for their API is required.

If we removed the kubebuilder:validation:Required marker, than the next time they generate their CRD, that field would change from required to optional, which is technically a breaking change, which we probably don't want to just blanketly force upon people.

Yes, I appreciate that this is a core type, but we do know that folks import core types and generate schemas based on the markers on those core types in the wild

Copy link
Contributor

Choose a reason for hiding this comment

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

creating a CRD that imports these types somewhere into their API

ah, can you clarify that a little better in the comment?

and maybe the API docs for that type should warn about this too? (Actually, why does the linter exception end up in an unrelated file somewhere else rather than being a //nolint-style comment at the point where it's needed?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah, can you clarify that a little better in the comment?

I can certainly try!

and maybe the API docs for that type should warn about this too?

I can also give that a go

//nolint-style comment at the point where it's needed?

Using an exceptions file is not new to K/K.

At present, the only way to ignore one of the linter rules is //nolint:kubeapilinter which would ignore every rule on that line/type, which is undesirable.

It would probably be beneficial to have a no lint for the individual rules but we don't have that yet. If the exceptions are not natural like this, and folks think it's important, I can look at prioritising a way to ignore the individual rules.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@danwinship If you can check the latest updates, is the documentation clearer now?

Copy link
Member

Choose a reason for hiding this comment

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

what's the history behind // +kubebuilder:validation:Required getting added to an optional field ... that seems like a bug at first glance

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems it was added in c970a46#diff-5ffbebb40a29f8e491f4ff1e07c618eb3edcc0d5f00a1a71445272d6767d7883, which added the field as +optional, but also +kubebuilder:validation:Required

Scanning through the comments on the PR that added it, I cannot see anything that suggests it was talked about

@JoelSpeed JoelSpeed force-pushed the enable-linter-requiredfields branch from 4847c6a to d4e7ed6 Compare May 22, 2025 14:11
@k8s-ci-robot k8s-ci-robot added area/code-generation sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. and removed do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels May 22, 2025
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: JoelSpeed
Once this PR has been reviewed and has the lgtm label, please assign smarterclayton 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

@JoelSpeed JoelSpeed force-pushed the enable-linter-requiredfields branch from d4e7ed6 to b0c4efd Compare May 22, 2025 16:10
@k8s-ci-robot
Copy link
Contributor

k8s-ci-robot commented May 22, 2025

@JoelSpeed: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
pull-kubernetes-e2e-kind-ipv6 b0c4efd link true /test pull-kubernetes-e2e-kind-ipv6

Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR.

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. I understand the commands that are listed here.

Copy link
Contributor

@danwinship danwinship left a comment

Choose a reason for hiding this comment

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

lgtm

@seans3
Copy link
Contributor

seans3 commented May 27, 2025

/triage accepted

@k8s-ci-robot k8s-ci-robot added triage/accepted Indicates an issue or PR is ready to be actively worked on. and removed needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels May 27, 2025
@@ -229,7 +244,7 @@ linters:
# - "nomaps" # Ensure maps are not used, unless they are `map[string]string` (for labels/annotations/etc).
# - "nophase" # Ensure field names do not have the word "phase" in them.
# - "optionalorrequired" # Every field should be marked as `+optional` xor `+required`.
# - "requiredfields" # Required fields should not be pointers, and should not have `omitempty`.
- "requiredfields" # Required fields should not be pointers, and should not have `omitempty`.
Copy link
Member

Choose a reason for hiding this comment

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

remind me why a required field should not be a pointer? that seems orthogonal to 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.

If you had a field that was a pointer, but not omitempty, assuming a structured client hasn't set it, so, something like

type A struct {
	Foo *string `json:"foo"`
	Bar *string `json:"bar,omitempty"`
}

a := A{}

aData, err := json.Marshal(a)
if err != nil {
	panic(err)
}

fmt.Printf("A: %s", string(aData))

The output would be A: {"foo":null}.

Now, I know fields can be +nullable, but my understanding was that this is the rarer case, and generally fields would not be nullable. I've not personally come across a use case for this before.

In the case that the structured client hasn't expressed an opinion on this required, the likelihood is that null would be rejected by the API server as an invalid value, but not for the right reason?

For example, a required string field, I would expect to have some sort of validation that asserts that when specified, the length is greater than zero. This certainly seems to be the case for all the field validations I did check as part of working up this PR.

I also believe that the more pointers we have in our APIs, the more likely folks are to create accidental dereferencing bugs, so we should encourage not to use pointers where it makes sense.

To quote the API conventions:

Required fields have the opposite properties, namely:

* They do not have an +optional comment tag.
* They do not have an omitempty struct tag.
* They are not a pointer type in the Go definition (e.g. AnotherFlag SomeFlag).
* The API server should not allow POSTing or PUTing a resource with this field unset.

So far I've only discovered 1 example where the field is a pointer and also required, and when I reviewed the validation for that field I couldn't see any reason why changing that field to remove the pointer would have made a difference to the validation from either structured or unstructured client perspectives.

Since not every field is marked up as either +optional or +required yet, there may be more exceptions out there to discover still.

Are there examples you're aware of where the fields are required and should absolutely be pointers?

Copy link
Member

Choose a reason for hiding this comment

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

Any required scalar field where the zero-value is a valid value (e.g. 0 for int fields, false for bool fields) must be a pointer to avoid implicitly defaulting to the zero-value client-side via a typed client.

Once it's a pointer, unless you want to make it nullable as well (which you probably don't), it would also need to be omitempty to keep spurious nulls out of the serialization.

So something like this seems fairly legitimate / necessary if you want a required scalar field where the zero-value of the scalar is valid:

// +required
// +k8s:minimum=0
Foo *int32 `json:"foo,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.

So that example breaks two of the four points from the API conventions. There are always exceptions I guess.

Does this example also mean that we shouldn't be enforcing no omitempty on required fields?

For the reverse of this linter, the optionalfields, we implemented bounds checking, and the linter recommends the best approach it can based on the bounds it has available. I'm wondering if something similar could be implemented for cases like this integer one you mention.

Until we have declarative validation on fields, being smart in the linter is harder for built in types though. I could certainly go ahead and implement it, but we would have to find exceptions and mark them with the declarative validation syntax as we find them I think? As mentioned, so far there are no fields marked explicitly as required that have had this issue so far

Would you agree with:

  • Required fields should not have omitempty, and should not be pointers UNLESS their zero value is a valid and accepted value, and the distinction between unset and the zero value matters

This would mean then that a string with a +k8s:minLength of 0 would also need to be pointer and omitempty

Copy link
Member

Choose a reason for hiding this comment

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

So that example breaks two of the four points from the API conventions. There are always exceptions I guess.

Hmm... I didn't remember the conventions talking about required fields not being pointers and not using omitempty. That's worth an update there... those two conventions in that direction are less hard and fast.

Something like:

Required fields have the following properties:

- They do not have an `+optional` comment tag.
- They mark themselves as required explicitly with a `+required` comment tag, or implicitly by not setting an `omitempty` struct tag.
- The API server should not allow POSTing or PUTing a resource with this field unset.
- They _typically_ do not use pointer types in the Go definition (e.g. `AnotherFlag SomeFlag`), though required fields where the zero value is a valid value may use pointer types, likely paired with an `omitempty` struct tag to avoid spurious null serializations.

Using the `+optional` comment tag, or the `omitempty` struct tag without a `+required` comment tag, causes OpenAPI documentation to reflect that the field is optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah that makes sense to me. I can raise a PR.

So I think in terms of moving this forward, I can look at adding zero value detection based on declarative markers to this linter, and then we would have the following:

  • If a field is marked required, and has incomplete +k8s markers, it should not be a pointer, and should not have omitempty
  • If a field is marked required, and has +k8s markers that suggest the zero value is valid, then it should be a pointer, and should have omitempty or +nullable

This would mean that as we start marking more fields as +required during the validation gen project, we would have a way to express to this linter "it's ok for this field to be a pointer" without adding exceptions everywhere, and would help towards the generated validations in the long term

Does that sound like a reasonable plan forward to make this a useful linter rule?

Copy link
Member

@liggitt liggitt Jun 11, 2025

Choose a reason for hiding this comment

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

  • If a field is marked required, and has incomplete +k8s markers, it should not be a pointer, and should not have omitempty
  • If a field is marked required, and has +k8s markers that suggest the zero value is valid, then it should be a pointer, and should have omitempty or +nullable

I'm not quite sure what "incomplete +k8s markers" means... with 0 +k8s markers, the zero value would be considered valid, right?

I think I'd phrase it something like the following:

If a field is marked required, and the zero value appears to be valid (either because of missing +k8s validation markers or validation that allows the zero value), then it should (or maybe could?) be a pointer, and if so, should either have omitempty or +nullable

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'm not quite sure what "incomplete +k8s markers" means... with 0 +k8s markers, the zero value would be considered valid, right?

Yes, sorry, I meant "doesn't have enough markers to know if the zero value is valid or not", so yes, if the markers are incomplete, the 0 value could be valid. In which case yes, the safest thing would be to pointer+omitempty it.

I think I'm coming to the realisation that while I thought this rule was low hanging fruit, it actually will rely on having complete validation on every required field across all of our types before we could really leverage it safely if we implement it correctly.

I'll work on getting the changes on the linter side and see how many exceptions it pulls up here.

And will need to catch up with the validation-gen folks to see what progress there is on markers that would allow this to be useful (minLength, minimum, maximum etc)

If a field is marked required, and the zero value appears to be valid (either because of missing +k8s validation markers or validation that allows the zero value), then it should (or maybe could?) be a pointer, and if so, should either have omitempty or +nullable

I think "should be a pointer is correct" based on the earlier comments in this thread. If the zero value is a valid choice, and the field is required, then without it being a pointer, it cannot have omitempty, and an empty marshalled object would implicitly marshal a value, removing the value of the field being required

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

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
area/code-generation cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API needs-priority Indicates a PR lacks a `priority/foo` label and requires one. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. release-note-none Denotes a PR that doesn't merit a release note. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. triage/accepted Indicates an issue or PR is ready to be actively worked on.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants