Skip to content

Commit b6233b0

Browse files
committed
unify marker definitions to markers package
Signed-off-by: sivchari <shibuuuu5@gmail.com>
1 parent 2e78eb0 commit b6233b0

File tree

8 files changed

+113
-79
lines changed

8 files changed

+113
-79
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 markers
17+
18+
const (
19+
// OptionalMarker is the marker that indicates that a field is optional.
20+
OptionalMarker = "optional"
21+
22+
// RequiredMarker is the marker that indicates that a field is required.
23+
RequiredMarker = "required"
24+
)
25+
26+
const (
27+
// KubebuilderRootMarker is the marker that indicates that a struct is the object root for code and CRD generation.
28+
KubebuilderRootMarker = "kubebuilder:object:root"
29+
30+
// KubebuilderStatusSubresourceMarker is the marker that indicates that the CRD generated for a struct should include the /status subresource.
31+
KubebuilderStatusSubresourceMarker = "kubebuilder:subresource:status"
32+
33+
// KubebuilderEnumMarker is the marker that indicates that a field has an enum in kubebuilder.
34+
KubebuilderEnumMarker = "kubebuilder:validation:Enum"
35+
36+
// KubebuilderFormatMarker is the marker that indicates that a field has a format in kubebuilder.
37+
KubebuilderFormatMarker = "kubebuilder:validation:Format"
38+
39+
// KubebuilderMaxItemsMarker is the marker that indicates that a field has a maximum number of items in kubebuilder.
40+
KubebuilderMaxItemsMarker = "kubebuilder:validation:MaxItems"
41+
42+
// KubebuilderMaxLengthMarker is the marker that indicates that a field has a maximum length in kubebuilder.
43+
KubebuilderMaxLengthMarker = "kubebuilder:validation:MaxLength"
44+
45+
// KubebuilderOptionalMarker is the marker that indicates that a field is optional in kubebuilder.
46+
KubebuilderOptionalMarker = "kubebuilder:validation:Optional"
47+
48+
// KubebuilderRequiredMarker is the marker that indicates that a field is required in kubebuilder.
49+
KubebuilderRequiredMarker = "kubebuilder:validation:Required"
50+
51+
// KubebuilderItemsMaxLengthMarker is the marker that indicates that a field has a maximum length in kubebuilder.
52+
KubebuilderItemsMaxLengthMarker = "kubebuilder:validation:items:MaxLength"
53+
54+
// KubebuilderItemsEnumMarker is the marker that indicates that a field has an enum in kubebuilder.
55+
KubebuilderItemsEnumMarker = "kubebuilder:validation:items:Enum"
56+
57+
// KubebuilderItemsFormatMarker is the marker that indicates that a field has a format in kubebuilder.
58+
KubebuilderItemsFormatMarker = "kubebuilder:validation:items:Format"
59+
)

pkg/analysis/maxlength/analyzer.go

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,6 @@ import (
2828

2929
const (
3030
name = "maxlength"
31-
32-
kubebuilderMaxLength = "kubebuilder:validation:MaxLength"
33-
kubebuilderEnum = "kubebuilder:validation:Enum"
34-
kubebuilderFormat = "kubebuilder:validation:Format"
35-
36-
kubebuilderItemsMaxLength = "kubebuilder:validation:items:MaxLength"
37-
kubebuilderItemsEnum = "kubebuilder:validation:items:Enum"
38-
kubebuilderItemsFormat = "kubebuilder:validation:items:Format"
39-
40-
kubebuilderMaxItems = "kubebuilder:validation:MaxItems"
4131
)
4232

4333
// Analyzer is the analyzer for the maxlength package.
@@ -70,7 +60,7 @@ func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markers.Mar
7060
fieldName := field.Names[0].Name
7161
prefix := fmt.Sprintf("field %s", fieldName)
7262

73-
checkTypeExpr(pass, field.Type, field, nil, markersAccess, prefix, kubebuilderMaxLength, needsStringMaxLength)
63+
checkTypeExpr(pass, field.Type, field, nil, markersAccess, prefix, markers.KubebuilderMaxLengthMarker, needsStringMaxLength)
7464
}
7565

7666
func checkIdent(pass *analysis.Pass, ident *ast.Ident, node ast.Node, aliases []*ast.TypeSpec, markersAccess markers.Markers, prefix, marker string, needsMaxLength func(markers.MarkerSet) bool) {
@@ -132,7 +122,7 @@ func checkArrayType(pass *analysis.Pass, arrayType *ast.ArrayType, node ast.Node
132122
NamePos: ident.NamePos,
133123
Name: "string",
134124
}
135-
checkString(pass, i, node, aliases, markersAccess, prefix, kubebuilderMaxLength, needsStringMaxLength)
125+
checkString(pass, i, node, aliases, markersAccess, prefix, markers.KubebuilderMaxLengthMarker, needsStringMaxLength)
136126

137127
return
138128
}
@@ -141,16 +131,16 @@ func checkArrayType(pass *analysis.Pass, arrayType *ast.ArrayType, node ast.Node
141131
}
142132
}
143133

144-
markers := getCombinedMarkers(markersAccess, node, aliases)
134+
markerSet := getCombinedMarkers(markersAccess, node, aliases)
145135

146-
if !markers.Has(kubebuilderMaxItems) {
147-
pass.Reportf(node.Pos(), "%s must have a maximum items, add %s marker", prefix, kubebuilderMaxItems)
136+
if !markerSet.Has(markers.KubebuilderMaxItemsMarker) {
137+
pass.Reportf(node.Pos(), "%s must have a maximum items, add %s marker", prefix, markers.KubebuilderMaxItemsMarker)
148138
}
149139
}
150140

151141
func checkArrayElementIdent(pass *analysis.Pass, ident *ast.Ident, node ast.Node, aliases []*ast.TypeSpec, markersAccess markers.Markers, prefix string) {
152142
if ident.Obj == nil { // Built-in type
153-
checkString(pass, ident, node, aliases, markersAccess, prefix, kubebuilderItemsMaxLength, needsItemsMaxLength)
143+
checkString(pass, ident, node, aliases, markersAccess, prefix, markers.KubebuilderItemsMaxLengthMarker, needsItemsMaxLength)
154144

155145
return
156146
}
@@ -162,7 +152,7 @@ func checkArrayElementIdent(pass *analysis.Pass, ident *ast.Ident, node ast.Node
162152

163153
// If the array element wasn't directly a string, allow a string alias to be used
164154
// with either the items style markers or the on alias style markers.
165-
checkTypeSpec(pass, tSpec, node, append(aliases, tSpec), markersAccess, fmt.Sprintf("%s type", prefix), kubebuilderMaxLength, func(ms markers.MarkerSet) bool {
155+
checkTypeSpec(pass, tSpec, node, append(aliases, tSpec), markersAccess, fmt.Sprintf("%s type", prefix), markers.KubebuilderMaxLengthMarker, func(ms markers.MarkerSet) bool {
166156
return needsStringMaxLength(ms) && needsItemsMaxLength(ms)
167157
})
168158
}
@@ -193,8 +183,8 @@ func getMarkers(markersAccess markers.Markers, node ast.Node) markers.MarkerSet
193183
// or if they are an enum, or if they are a date, date-time or duration.
194184
func needsStringMaxLength(markerSet markers.MarkerSet) bool {
195185
switch {
196-
case markerSet.Has(kubebuilderMaxLength),
197-
markerSet.Has(kubebuilderEnum),
186+
case markerSet.Has(markers.KubebuilderMaxLengthMarker),
187+
markerSet.Has(markers.KubebuilderEnumMarker),
198188
markerSet.HasWithValue(kubebuilderFormatWithValue("date")),
199189
markerSet.HasWithValue(kubebuilderFormatWithValue("date-time")),
200190
markerSet.HasWithValue(kubebuilderFormatWithValue("duration")):
@@ -206,8 +196,8 @@ func needsStringMaxLength(markerSet markers.MarkerSet) bool {
206196

207197
func needsItemsMaxLength(markerSet markers.MarkerSet) bool {
208198
switch {
209-
case markerSet.Has(kubebuilderItemsMaxLength),
210-
markerSet.Has(kubebuilderItemsEnum),
199+
case markerSet.Has(markers.KubebuilderItemsMaxLengthMarker),
200+
markerSet.Has(markers.KubebuilderItemsEnumMarker),
211201
markerSet.HasWithValue(kubebuilderItemsFormatWithValue("date")),
212202
markerSet.HasWithValue(kubebuilderItemsFormatWithValue("date-time")),
213203
markerSet.HasWithValue(kubebuilderItemsFormatWithValue("duration")):
@@ -218,9 +208,9 @@ func needsItemsMaxLength(markerSet markers.MarkerSet) bool {
218208
}
219209

220210
func kubebuilderFormatWithValue(value string) string {
221-
return fmt.Sprintf("%s:=%s", kubebuilderFormat, value)
211+
return fmt.Sprintf("%s:=%s", markers.KubebuilderFormatMarker, value)
222212
}
223213

224214
func kubebuilderItemsFormatWithValue(value string) string {
225-
return fmt.Sprintf("%s:=%s", kubebuilderItemsFormat, value)
215+
return fmt.Sprintf("%s:=%s", markers.KubebuilderItemsFormatMarker, value)
226216
}

pkg/analysis/optionalorrequired/analyzer.go

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,14 @@ import (
2929

3030
const (
3131
name = "optionalorrequired"
32-
33-
// OptionalMarker is the marker that indicates that a field is optional.
34-
OptionalMarker = "optional"
35-
36-
// RequiredMarker is the marker that indicates that a field is required.
37-
RequiredMarker = "required"
38-
39-
// KubebuilderOptionalMarker is the marker that indicates that a field is optional in kubebuilder.
40-
KubebuilderOptionalMarker = "kubebuilder:validation:Optional"
41-
42-
// KubebuilderRequiredMarker is the marker that indicates that a field is required in kubebuilder.
43-
KubebuilderRequiredMarker = "kubebuilder:validation:Required"
4432
)
4533

4634
func init() {
4735
markers.DefaultRegistry().Register(
48-
OptionalMarker,
49-
RequiredMarker,
50-
KubebuilderOptionalMarker,
51-
KubebuilderRequiredMarker,
36+
markers.OptionalMarker,
37+
markers.RequiredMarker,
38+
markers.KubebuilderOptionalMarker,
39+
markers.KubebuilderRequiredMarker,
5240
)
5341
}
5442

@@ -67,21 +55,21 @@ func newAnalyzer(cfg config.OptionalOrRequiredConfig) *analysis.Analyzer {
6755
a := &analyzer{}
6856

6957
switch cfg.PreferredOptionalMarker {
70-
case OptionalMarker:
71-
a.primaryOptionalMarker = OptionalMarker
72-
a.secondaryOptionalMarker = KubebuilderOptionalMarker
73-
case KubebuilderOptionalMarker:
74-
a.primaryOptionalMarker = KubebuilderOptionalMarker
75-
a.secondaryOptionalMarker = OptionalMarker
58+
case markers.OptionalMarker:
59+
a.primaryOptionalMarker = markers.OptionalMarker
60+
a.secondaryOptionalMarker = markers.KubebuilderOptionalMarker
61+
case markers.KubebuilderOptionalMarker:
62+
a.primaryOptionalMarker = markers.KubebuilderOptionalMarker
63+
a.secondaryOptionalMarker = markers.OptionalMarker
7664
}
7765

7866
switch cfg.PreferredRequiredMarker {
79-
case RequiredMarker:
80-
a.primaryRequiredMarker = RequiredMarker
81-
a.secondaryRequiredMarker = KubebuilderRequiredMarker
82-
case KubebuilderRequiredMarker:
83-
a.primaryRequiredMarker = KubebuilderRequiredMarker
84-
a.secondaryRequiredMarker = RequiredMarker
67+
case markers.RequiredMarker:
68+
a.primaryRequiredMarker = markers.RequiredMarker
69+
a.secondaryRequiredMarker = markers.KubebuilderRequiredMarker
70+
case markers.KubebuilderRequiredMarker:
71+
a.primaryRequiredMarker = markers.KubebuilderRequiredMarker
72+
a.secondaryRequiredMarker = markers.RequiredMarker
8573
}
8674

8775
return &analysis.Analyzer{
@@ -244,10 +232,10 @@ func (a *analyzer) checkTypeSpec(pass *analysis.Pass, typeSpec *ast.TypeSpec, ma
244232

245233
func defaultConfig(cfg *config.OptionalOrRequiredConfig) {
246234
if cfg.PreferredOptionalMarker == "" {
247-
cfg.PreferredOptionalMarker = OptionalMarker
235+
cfg.PreferredOptionalMarker = markers.OptionalMarker
248236
}
249237

250238
if cfg.PreferredRequiredMarker == "" {
251-
cfg.PreferredRequiredMarker = RequiredMarker
239+
cfg.PreferredRequiredMarker = markers.RequiredMarker
252240
}
253241
}

pkg/analysis/optionalorrequired/analyzer_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"testing"
2020

2121
"golang.org/x/tools/go/analysis/analysistest"
22+
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
2223
"sigs.k8s.io/kube-api-linter/pkg/analysis/optionalorrequired"
2324
"sigs.k8s.io/kube-api-linter/pkg/config"
2425
)
@@ -39,8 +40,8 @@ func TestSwappedMarkerPriority(t *testing.T) {
3940

4041
a, err := optionalorrequired.Initializer().Init(config.LintersConfig{
4142
OptionalOrRequired: config.OptionalOrRequiredConfig{
42-
PreferredOptionalMarker: optionalorrequired.KubebuilderOptionalMarker,
43-
PreferredRequiredMarker: optionalorrequired.KubebuilderRequiredMarker,
43+
PreferredOptionalMarker: markers.KubebuilderOptionalMarker,
44+
PreferredRequiredMarker: markers.KubebuilderRequiredMarker,
4445
},
4546
})
4647
if err != nil {

pkg/analysis/requiredfields/analyzer.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,10 @@ import (
3030

3131
const (
3232
name = "requiredfields"
33-
34-
requiredMarker = "required"
35-
kubebuilderRequiredMarker = "kubebuilder:validation:Required"
3633
)
3734

3835
func init() {
39-
markers.DefaultRegistry().Register(requiredMarker, kubebuilderRequiredMarker)
36+
markers.DefaultRegistry().Register(markers.RequiredMarker, markers.KubebuilderRequiredMarker)
4037
}
4138

4239
type analyzer struct {
@@ -79,7 +76,7 @@ func (a *analyzer) checkField(pass *analysis.Pass, field *ast.Field, fieldMarker
7976

8077
fieldName := field.Names[0].Name
8178

82-
if !fieldMarkers.Has(requiredMarker) && !fieldMarkers.Has(kubebuilderRequiredMarker) {
79+
if !fieldMarkers.Has(markers.RequiredMarker) && !fieldMarkers.Has(markers.KubebuilderRequiredMarker) {
8380
// The field is not marked required, so we don't need to check it.
8481
return
8582
}

pkg/analysis/statussubresource/analyzer.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ const (
3232
name = "statussubresource"
3333

3434
statusJSONTag = "status"
35-
36-
// kubebuilderRootMarker is the marker that indicates that a struct is the object root for code and CRD generation.
37-
kubebuilderRootMarker = "kubebuilder:object:root:=true"
38-
39-
// kubebuilderStatusSubresourceMarker is the marker that indicates that the CRD generated for a struct should include the /status subresource.
40-
kubebuilderStatusSubresourceMarker = "kubebuilder:subresource:status"
4135
)
4236

4337
type analyzer struct{}
@@ -104,11 +98,11 @@ func (a *analyzer) checkStruct(pass *analysis.Pass, sTyp *ast.StructType, name s
10498
return
10599
}
106100

107-
if !structMarkers.HasWithValue(kubebuilderRootMarker) {
101+
if !structMarkers.HasWithValue(kubebuilderFormatWithValue("true")) {
108102
return
109103
}
110104

111-
hasStatusSubresourceMarker := structMarkers.Has(kubebuilderStatusSubresourceMarker)
105+
hasStatusSubresourceMarker := structMarkers.Has(markers.KubebuilderStatusSubresourceMarker)
112106
hasStatusField := hasStatusField(sTyp, jsonTags)
113107

114108
switch {
@@ -117,12 +111,12 @@ func (a *analyzer) checkStruct(pass *analysis.Pass, sTyp *ast.StructType, name s
117111
case hasStatusSubresourceMarker && !hasStatusField:
118112
// Might be able to have some suggested fixes here, but it is likely much more complex
119113
// so for now leave it with a descriptive failure message.
120-
pass.Reportf(sTyp.Pos(), "root object type %q is marked to enable the status subresource with marker %q but has no status field", name, kubebuilderStatusSubresourceMarker)
114+
pass.Reportf(sTyp.Pos(), "root object type %q is marked to enable the status subresource with marker %q but has no status field", name, markers.KubebuilderStatusSubresourceMarker)
121115
case !hasStatusSubresourceMarker && hasStatusField:
122116
// In this case we can suggest the autofix to add the status subresource marker
123117
pass.Report(analysis.Diagnostic{
124118
Pos: sTyp.Pos(),
125-
Message: fmt.Sprintf("root object type %q has a status field but does not have the marker %q to enable the status subresource", name, kubebuilderStatusSubresourceMarker),
119+
Message: fmt.Sprintf("root object type %q has a status field but does not have the marker %q to enable the status subresource", name, markers.KubebuilderStatusSubresourceMarker),
126120
SuggestedFixes: []analysis.SuggestedFix{
127121
{
128122
Message: "should add the kubebuilder:subresource:status marker",
@@ -158,3 +152,7 @@ func hasStatusField(sTyp *ast.StructType, jsonTags extractjsontags.StructFieldTa
158152

159153
return false
160154
}
155+
156+
func kubebuilderFormatWithValue(value string) string {
157+
return fmt.Sprintf("%s:=%s", markers.KubebuilderRootMarker, value)
158+
}

pkg/validation/linters_config.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"fmt"
2020
"regexp"
2121

22-
"sigs.k8s.io/kube-api-linter/pkg/analysis/optionalorrequired"
22+
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
2323
"sigs.k8s.io/kube-api-linter/pkg/config"
2424

2525
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -95,15 +95,15 @@ func validateOptionalOrRequiredConfig(oorc config.OptionalOrRequiredConfig, fldP
9595
fieldErrors := field.ErrorList{}
9696

9797
switch oorc.PreferredOptionalMarker {
98-
case "", optionalorrequired.OptionalMarker, optionalorrequired.KubebuilderOptionalMarker:
98+
case "", markers.OptionalMarker, markers.KubebuilderOptionalMarker:
9999
default:
100-
fieldErrors = append(fieldErrors, field.Invalid(fldPath.Child("preferredOptionalMarker"), oorc.PreferredOptionalMarker, fmt.Sprintf("invalid value, must be one of %q, %q or omitted", optionalorrequired.OptionalMarker, optionalorrequired.KubebuilderOptionalMarker)))
100+
fieldErrors = append(fieldErrors, field.Invalid(fldPath.Child("preferredOptionalMarker"), oorc.PreferredOptionalMarker, fmt.Sprintf("invalid value, must be one of %q, %q or omitted", markers.OptionalMarker, markers.KubebuilderOptionalMarker)))
101101
}
102102

103103
switch oorc.PreferredRequiredMarker {
104-
case "", optionalorrequired.RequiredMarker, optionalorrequired.KubebuilderRequiredMarker:
104+
case "", markers.RequiredMarker, markers.KubebuilderRequiredMarker:
105105
default:
106-
fieldErrors = append(fieldErrors, field.Invalid(fldPath.Child("preferredRequiredMarker"), oorc.PreferredRequiredMarker, fmt.Sprintf("invalid value, must be one of %q, %q or omitted", optionalorrequired.RequiredMarker, optionalorrequired.KubebuilderRequiredMarker)))
106+
fieldErrors = append(fieldErrors, field.Invalid(fldPath.Child("preferredRequiredMarker"), oorc.PreferredRequiredMarker, fmt.Sprintf("invalid value, must be one of %q, %q or omitted", markers.RequiredMarker, markers.KubebuilderRequiredMarker)))
107107
}
108108

109109
return fieldErrors

pkg/validation/linters_config_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
. "github.com/onsi/ginkgo/v2"
2020
. "github.com/onsi/gomega"
2121

22+
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
2223
"sigs.k8s.io/kube-api-linter/pkg/config"
2324
"sigs.k8s.io/kube-api-linter/pkg/validation"
2425

@@ -224,17 +225,17 @@ var _ = Describe("LintersConfig", func() {
224225
Entry("With a valid OptionalOrRequiredConfig", validateLintersConfigTableInput{
225226
config: config.LintersConfig{
226227
OptionalOrRequired: config.OptionalOrRequiredConfig{
227-
PreferredOptionalMarker: "optional",
228-
PreferredRequiredMarker: "required",
228+
PreferredOptionalMarker: markers.OptionalMarker,
229+
PreferredRequiredMarker: markers.RequiredMarker,
229230
},
230231
},
231232
expectedErr: "",
232233
}),
233234
Entry("With kubebuilder preferred markers", validateLintersConfigTableInput{
234235
config: config.LintersConfig{
235236
OptionalOrRequired: config.OptionalOrRequiredConfig{
236-
PreferredOptionalMarker: "kubebuilder:validation:Optional",
237-
PreferredRequiredMarker: "kubebuilder:validation:Required",
237+
PreferredOptionalMarker: markers.KubebuilderOptionalMarker,
238+
PreferredRequiredMarker: markers.KubebuilderRequiredMarker,
238239
},
239240
},
240241
expectedErr: "",

0 commit comments

Comments
 (0)