Skip to content

Commit 9992248

Browse files
authored
Merge pull request #164 from krishagarwal278/defaultOrRequired
Added defaultorRequired linter
2 parents 38ad5ff + e938076 commit 9992248

File tree

7 files changed

+246
-0
lines changed

7 files changed

+246
-0
lines changed

docs/linters.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- [Conditions](#conditions) - Checks that `Conditions` fields are correctly formatted
55
- [CommentStart](#commentstart) - Ensures comments start with the serialized form of the type
66
- [ConflictingMarkers](#conflictingmarkers) - Detects mutually exclusive markers on the same field
7+
- [DefaultOrRequired](#defaultorrequired) - Ensures fields marked as required do not have default values
78
- [DuplicateMarkers](#duplicatemarkers) - Checks for exact duplicates of markers
89
- [ForbiddenMarkers](#forbiddenmarkers) - Checks that no forbidden markers are present on types/fields.
910
- [Integers](#integers) - Validates usage of supported integer types
@@ -139,6 +140,37 @@ lintersConfig:
139140

140141
The linter does not provide automatic fixes as it cannot determine which conflicting marker should be removed.
141142

143+
## DefaultOrRequired
144+
145+
The `defaultorrequired` linter checks that fields marked as required do not have default values applied.
146+
147+
A field cannot be both required and have a default value, as these are conflicting concepts:
148+
- A **required** field must be provided by the user and cannot be omitted
149+
- A **default** value is used when a field is not provided
150+
151+
This linter helps prevent common configuration errors where a field is marked as required but also has a default value, which creates ambiguity about whether the field truly needs to be provided by the user.
152+
153+
### Example
154+
155+
The following will be flagged by the linter:
156+
157+
```go
158+
type MyStruct struct {
159+
// +required
160+
// +default:=value
161+
ConflictedField string `json:"conflictedField"` // Error: field cannot have both a default value and be marked as required
162+
}
163+
```
164+
165+
The linter also detects conflicts with:
166+
- `+required`
167+
- `+default`
168+
- `+k8s:required`
169+
- `+kubebuilder:validation:Required`
170+
- `+kubebuilder:default`
171+
172+
This linter is enabled by default and helps ensure that API designs are consistent and unambiguous about whether fields are truly required or have default values.
173+
142174
## DuplicateMarkers
143175

144176
The duplicatemarkers linter checks for exact duplicates of markers for types and fields.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package defaultorrequired
17+
18+
import (
19+
"errors"
20+
"fmt"
21+
22+
"golang.org/x/tools/go/analysis"
23+
"k8s.io/apimachinery/pkg/util/validation/field"
24+
"sigs.k8s.io/kube-api-linter/pkg/analysis/conflictingmarkers"
25+
"sigs.k8s.io/kube-api-linter/pkg/analysis/initializer"
26+
"sigs.k8s.io/kube-api-linter/pkg/markers"
27+
)
28+
29+
const (
30+
name = "defaultorrequired"
31+
doc = "Checks that fields marked as required do not have default values applied"
32+
)
33+
34+
var errUnexpectedInitializerType = errors.New("expected conflictingmarkers.Initializer() to be of type initializer.ConfigurableAnalyzerInitializer, but was not")
35+
36+
// newAnalyzer creates a new analyzer that wraps conflictingmarkers with a predefined configuration
37+
// for checking default and required marker conflicts.
38+
func newAnalyzer() *analysis.Analyzer {
39+
cfg := &conflictingmarkers.ConflictingMarkersConfig{
40+
Conflicts: []conflictingmarkers.ConflictSet{
41+
{
42+
Name: "default_or_required",
43+
Sets: [][]string{
44+
{markers.DefaultMarker, markers.KubebuilderDefaultMarker},
45+
{markers.RequiredMarker, markers.KubebuilderRequiredMarker, markers.K8sRequiredMarker},
46+
},
47+
Description: "A field with a default value cannot be required. A required field must be provided by the user, so a default value is not meaningful.",
48+
},
49+
},
50+
}
51+
52+
configInit, ok := conflictingmarkers.Initializer().(initializer.ConfigurableAnalyzerInitializer)
53+
if !ok {
54+
panic(fmt.Errorf("getting initializer: %w", errUnexpectedInitializerType))
55+
}
56+
57+
errs := configInit.ValidateConfig(cfg, field.NewPath("defaultorrequired"))
58+
if err := errs.ToAggregate(); err != nil {
59+
panic(fmt.Errorf("defaultorrequired linter has an invalid conflictingmarkers configuration: %w", err))
60+
}
61+
62+
analyzer, err := configInit.Init(cfg)
63+
if err != nil {
64+
panic(fmt.Errorf("defaultorrequired linter encountered an error initializing wrapped conflictingmarkers analyzer: %w", err))
65+
}
66+
67+
analyzer.Name = name
68+
analyzer.Doc = doc
69+
70+
return analyzer
71+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package defaultorrequired_test
17+
18+
import (
19+
"testing"
20+
21+
"golang.org/x/tools/go/analysis/analysistest"
22+
"sigs.k8s.io/kube-api-linter/pkg/analysis/defaultorrequired"
23+
)
24+
25+
func Test(t *testing.T) {
26+
testdata := analysistest.TestData()
27+
28+
analyzer, err := defaultorrequired.Initializer().Init(nil)
29+
if err != nil {
30+
t.Fatalf("initializing defaultorrequired linter: %v", err)
31+
}
32+
33+
analysistest.Run(t, testdata, analyzer, "a")
34+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/*
18+
Package defaultorrequired provides the defaultorrequired analyzer.
19+
20+
The defaultorrequired analyzer checks that fields marked as required do not have default values applied.
21+
22+
A field cannot be both required and have a default value, as these are conflicting concepts:
23+
- A required field must be provided by the user and cannot be omitted
24+
- A default value is used when a field is not provided
25+
26+
For example, the following would be flagged:
27+
28+
// +kubebuilder:validation:Required
29+
// +kubebuilder:default:=value
30+
Field string `json:"field"`
31+
*/
32+
package defaultorrequired
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package defaultorrequired
17+
18+
import (
19+
"sigs.k8s.io/kube-api-linter/pkg/analysis/initializer"
20+
"sigs.k8s.io/kube-api-linter/pkg/analysis/registry"
21+
)
22+
23+
func init() {
24+
registry.DefaultRegistry().RegisterLinter(Initializer())
25+
}
26+
27+
// Initializer returns the AnalyzerInitializer for this
28+
// Analyzer so that it can be added to the registry.
29+
func Initializer() initializer.AnalyzerInitializer {
30+
return initializer.NewInitializer(
31+
name,
32+
newAnalyzer(),
33+
true,
34+
)
35+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package a
2+
3+
type Test struct {
4+
// This field has both required and default markers - should be flagged
5+
// +kubebuilder:validation:Required
6+
// +kubebuilder:default:=testValue
7+
ConflictedField string `json:"conflictedField"` // want `field Test.ConflictedField has conflicting markers: default_or_required: \{\[kubebuilder:default\], \[kubebuilder:validation:Required\]\}. A field with a default value cannot be required. A required field must be provided by the user, so a default value is not meaningful.`
8+
9+
// This field has only required marker - should not be flagged
10+
// +kubebuilder:validation:Required
11+
RequiredOnlyField string `json:"requiredOnlyField"`
12+
13+
// This field has only default marker - should not be flagged
14+
// +kubebuilder:default:=defaultValue
15+
DefaultOnlyField string `json:"defaultOnlyField"`
16+
17+
// This field uses KAL required marker with default - should be flagged
18+
// +required
19+
// +default:=anotherValue
20+
KALConflictedField string `json:"kalConflictedField"` // want `field Test.KALConflictedField has conflicting markers: default_or_required: \{\[default\], \[required\]\}. A field with a default value cannot be required. A required field must be provided by the user, so a default value is not meaningful.`
21+
22+
// This field uses k8s required marker with kubebuilder default - should be flagged
23+
// +k8s:required
24+
// +kubebuilder:default:=yetanotherValue
25+
K8sConflictedField string `json:"k8sConflictedField"` // want `field Test.K8sConflictedField has conflicting markers: default_or_required: \{\[kubebuilder:default\], \[k8s:required\]\}. A field with a default value cannot be required. A required field must be provided by the user, so a default value is not meaningful.`
26+
27+
// This field has neither marker - should not be flagged
28+
NormalField string `json:"normalField"`
29+
30+
// Multiple fields to test various combinations
31+
ValidField1 string `json:"validField1"` // no markers
32+
33+
// +required
34+
ValidField2 string `json:"validField2"` // only required
35+
36+
// +kubebuilder:default:=value
37+
ValidField3 string `json:"validField3"` // only default
38+
39+
// +kubebuilder:validation:Required
40+
ValidField4 string `json:"validField4"` // only required (kubebuilder format)
41+
}

pkg/registration/doc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
_ "sigs.k8s.io/kube-api-linter/pkg/analysis/commentstart"
2828
_ "sigs.k8s.io/kube-api-linter/pkg/analysis/conditions"
2929
_ "sigs.k8s.io/kube-api-linter/pkg/analysis/conflictingmarkers"
30+
_ "sigs.k8s.io/kube-api-linter/pkg/analysis/defaultorrequired"
3031
_ "sigs.k8s.io/kube-api-linter/pkg/analysis/duplicatemarkers"
3132
_ "sigs.k8s.io/kube-api-linter/pkg/analysis/forbiddenmarkers"
3233
_ "sigs.k8s.io/kube-api-linter/pkg/analysis/integers"

0 commit comments

Comments
 (0)