-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
🌱 Add KAL linter for linting API conventions #11733
Conversation
Skipping CI for Draft Pull Request. |
/area api |
56611bc
to
ab11d8e
Compare
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.
Very nice!
Obviously have to enable them incrementally and have to decide linter-by-linter which ones we can also just apply to existing APIs (because they are non-breaking, e.g. commentstart, optionalorrequired) vs. only on new API fields / apiVersions.
.golangci-kal.yml
Outdated
settings: | ||
linters: | ||
enable: | ||
- "conditions" # Ensure conditions have the correct json tags and markers. |
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.
+1
Seems like the linter also enforces:
// +patchStrategy=merge
// +patchMergeKey=type
Does this make sense for CRDs?
Obviously we can't fix this finding for the old condition fields that use clusterv1.Conditions
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 believe those annotations still work for client-side implementations of patching. The patch strategy and key are part of the K8s extension of the OpenAPI v3 schema, so assuming controller-gen is capable of putting these in the right place , they can still be effective for clients like Kubectl patch
Looking at https://tech.aabouzaid.com/2022/11/set-openapi-patch-strategy-for-kubernetes-custom-resources-kustomize.html and other sources of generated CRDs, I don't think controller-gen currently populates these, but it looks like it would work if it did!
Perhaps dropping the patch keys for CRDs is appropriate, I'll check this with api-machinery folks, this could just be a bug in the linter
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.
(actually didn't initially remember that I clarified this already a while back :))

https://kubernetes.slack.com/archives/C0EG7JC6T/p1718717630176899
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.
Then that's a bug in KAL, and I will get that fixed! Thanks for highlighting
Edit: added an issue to fix this JoelSpeed/kal#35
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.
Thx!
I guess in general having both sets of markers would make sense only for structs that are supposed to be embedded in builtin types and CRDs.
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 JoelSpeed/kal#37, which I'll update to once merged.
This will allow us to forbid Proto and PatchStrategy markers from our types, which I think is appropriate as I don't think we will be importing any of our types into core types any time soon
.golangci-kal.yml
Outdated
- "commentstart" # Ensure comments start with the serialized version of the field name. | ||
- "integers" # Ensure only int32 and int64 are used for integers. | ||
- "jsontags" # Ensure every field has a json tag. | ||
- "maxlength" # Ensure all strings and arrays have maximum lengths/maximum items. |
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.
+1 I would be even okay with picking some sane max values for existing fields
.golangci-kal.yml
Outdated
- "integers" # Ensure only int32 and int64 are used for integers. | ||
- "jsontags" # Ensure every field has a json tag. | ||
- "maxlength" # Ensure all strings and arrays have maximum lengths/maximum items. | ||
- "nobools" # Bools do not evolve over time, should use enums instead. |
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.
Not sure about this one.
Mostly because I'm not sure if every bool can be intuitively expressed as an enum instead. I think we have a lot of fields that worked quite well as a bool and we have no need/plans to evolve them
Additionally, general edge case, in CABPK / KCP we ~ inherit a significant part of our API from kubeadm. I wouldn't like to diverge from these upstream APIs to avoid this finding.
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.
This is certainly one of the more controversial ones I agree. We should have a wider discussion about this, and how we might handle the inherited APIs from Kubeadm, if the linter is going to pick those up and we don't want to change them at all, then this could be awkward from a linter perspective
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.
besides the inconveniences it might cause, I'd like to have it enable to force us think twice. If enabled, what would be the process to allow exceptions?
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.
You have two options, either you // nolint:kal
which would disable all linter checks on this line, or you can use the golangci-lint configuration to exclude a specific instance using their more targeted/complex exclusions
If we go for the --new-from-rev
, we could also just override the check and then once it's in main, it's not going to come up again (on PRs at least)
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.
Another reason why I don't like using enums for this. It forces everyone to handle "unknown" values just in case the field ever evolves (which might never happen)
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.
On the other hand, if you do decide to expand later, you now have maybe two bools, where one of them is only allowed to be set when the other is set to a specific value, which can be awkward (we can probably bike shed on bools for a long time)
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.
Yeah. I don't remember a single bool in CAPI where we hit this issue over the last years :)
I guess I would make this a case-by-case decision
.golangci-kal.yml
Outdated
- "jsontags" # Ensure every field has a json tag. | ||
- "maxlength" # Ensure all strings and arrays have maximum lengths/maximum items. | ||
- "nobools" # Bools do not evolve over time, should use enums instead. | ||
- "nophase" # Phase fields are discouraged by the Kube API conventions, use conditions instead. |
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.
This one will be controversial. We initially wanted to drop phase, but then later realized that the phases are actually useful to users because they quickly give users an idea about the phase a Cluster/Machine is in without having to parse through a number of conditions
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.
Interesting, we should check the history of the API convention on this one. As the comment says, the idea would generally be something like a Ready condition to show the general state, I guess if we can represent printer columns well based on the condition, then the phase may become less fundamental?
Do you happen to have a link to prior discussion on this topic?
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'm not sure where that discussion happened. But I think @vincepri / @fabriziopandini should have context on it
EDIT: Found it: #10897 (comment) & #10897 (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.
Given that recent discussion about diverging from kube conventions, and the consensus there, I think it makes sense to capture this, and disable the nophase linter and comment why we are disabling it
this is great, thanks! |
Great work @JoelSpeed! Based on our discussion I would propose to work to merge this PR initially with all the linter disabled, and then send follow up PR enabling one linter at time by fixing findings / adding exceptions. For most of the linters seems we can probably fix stuff ~pretty fast directly in v1beta1 / without breaking changes (this will also avoid additional complexity when we will work on v1beta2)
We have to take a closer look at findings for following links, because they might be breaking
Instead most probably we should keep the "nophase" linter off. |
Sounds like a reasonable plan to me, so for now keep it focusing on the v1beta1 types, everything disabled and look to enable based on the above list. I'll get the PR updated to reflect that |
818fc3b
to
cec9549
Compare
cec9549
to
3ddf10c
Compare
@sbueringer @fabriziopandini PR is now updated so that:
If we get this merged, I can follow up with a handful of PRs to start getting various linters enabled, and go through the discussions required for each linter |
Makefile
Outdated
@@ -667,6 +672,15 @@ lint-dockerfiles: | |||
lint-fix: $(GOLANGCI_LINT) ## Lint the codebase and run auto-fixers if supported by the linter | |||
GOLANGCI_LINT_EXTRA_ARGS=--fix $(MAKE) lint | |||
|
|||
.PHONY: lint-api | |||
lint-api: GOLANGCI_LINT_EXTRA_ARGS?=--new-from-rev=main |
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.
What happens once this Makefile will end up on a release branch?
(I think we have to ensure this is the base branch of the PR and not always main)
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 it may be simpler to not use --new-from-rev
, but exclusions instead 🤔 We may have to build up that list then, but this seems to be the easier approach and always works on any branch or commit then.
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 there an env var that would expose the branch name of the base branch? I know on prow you have PULL_BASE_SHA
but I don't know GH actions well
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.
Okay with the decision to not use --new-from-rev for now this finding became irrelevant
@JoelSpeed Would be nice to open an umbrella issue |
Created #11834 to start tracking the enablement of each of the linters, we can record decisions there as well where we decide against a linter rule for any reason |
/lgtm /assign @fabriziopandini |
3ddf10c
to
49f8a9a
Compare
LGTM label has been added. Git tree hash: 0741ff7607218615b5d24ee478bd60cfbbfb788e
|
Or |
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.
/lgtm
LGTM label has been added. Git tree hash: 0741ff7607218615b5d24ee478bd60cfbbfb788e
|
/approve |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: sbueringer 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 |
What this PR does / why we need it:
This PR adds KAL to the linter workflows to lint API conventions.
The linter is a WIP project that I have been working on and the focus of the project is to try and enforce API conventions via a linter, to take off some of the cognitive load of API review.
At the moment, there are 10 sub-linters implemented, each of which I've listed out within the configuration with a description of what they mean.
We should discuss each one and agree upon them, and then, the intention is that we can use this linter for the new v1beta2 types.
The set up here runs KAL separately from the main golangci-lint run, so that the GitHub workflow for linting can continue to use the official GH action, which has some caching and speed improvements over running the binary manually.
The config for KAL is set to run only on files containing
api/v1beta2
in the name. Since we don't have any of those yet, I've temporarily addedapi/v1beta1
so that we can see what the output might look like. We will want to remove that before merging.I've also updated GolangCI-Lint to v1.63.4. v1.63 introduced the ability for custom linters to apply fixes to the code, so
lint-fix
will work with KAL as well provided we use a v1.63 or later version. I figured it best to update the main version of the linter at the same time.Which issue(s) this PR fixes (optional, in
fixes #<issue number>(, fixes #<issue_number>, ...)
format, will close the issue(s) when PR gets merged):Fixes #