Skip to content

Commit 1bf22cb

Browse files
committed
feature: +enum validation marker
1 parent ae8400e commit 1bf22cb

File tree

9 files changed

+361
-0
lines changed

9 files changed

+361
-0
lines changed

pkg/crd/markers/crd.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,12 @@ type SelectableField struct {
399399
JSONPath string `marker:"JSONPath"`
400400
}
401401

402+
// +controllertools:marker:generateHelp:category="CRD validation"
403+
404+
// Enum marker marks a string alias type as an enum.
405+
// It infers the members from constants declared of that type.
406+
type InferredEnum struct{}
407+
402408
func (s SelectableField) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error {
403409
var selectableFields *[]apiext.SelectableField
404410
for i := range crd.Versions {

pkg/crd/markers/validation.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ var ValidationIshMarkers = []*definitionWithHelp{
113113
WithHelp(XPreserveUnknownFields{}.Help()),
114114
must(markers.MakeDefinition("kubebuilder:pruning:PreserveUnknownFields", markers.DescribesType, XPreserveUnknownFields{})).
115115
WithHelp(XPreserveUnknownFields{}.Help()),
116+
must(markers.MakeDefinition("enum", markers.DescribesType, InferredEnum{})).
117+
WithHelp(InferredEnum{}.Help()),
116118
}
117119

118120
func init() {

pkg/crd/markers/zz_generated.markerhelp.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/crd/parser.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package crd
1818

1919
import (
2020
"fmt"
21+
"go/ast"
2122

2223
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2324
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -122,6 +123,21 @@ func (p *Parser) init() {
122123
}
123124
}
124125

126+
func (p *Parser) indexEnumMemberConstantDeclarations(pkg *loader.Package) {
127+
loader.EachConstDecl(pkg, func(spec *ast.ValueSpec) {
128+
if id, ok := spec.Type.(*ast.Ident); ok {
129+
if typeinfo, ok := p.Types[TypeIdent{
130+
pkg, id.Name,
131+
}]; ok {
132+
typeinfo.EnumValues = append(typeinfo.EnumValues, markers.EnumMemberInfo{
133+
Name: spec.Names[0].Name,
134+
ValueSpec: spec,
135+
})
136+
}
137+
}
138+
})
139+
}
140+
125141
// indexTypes loads all types in the package into Types.
126142
func (p *Parser) indexTypes(pkg *loader.Package) {
127143
// autodetect
@@ -220,6 +236,7 @@ func (p *Parser) AddPackage(pkg *loader.Package) {
220236
return
221237
}
222238
p.indexTypes(pkg)
239+
p.indexEnumMemberConstantDeclarations(pkg)
223240
p.Checker.Check(pkg)
224241
p.packages[pkg] = struct{}{}
225242
}

pkg/crd/schema.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ limitations under the License.
1717
package crd
1818

1919
import (
20+
"encoding/json"
2021
"errors"
2122
"fmt"
2223
"go/ast"
2324
"go/token"
2425
"go/types"
2526
"sort"
27+
"strconv"
2628
"strings"
2729

2830
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -274,6 +276,29 @@ func localNamedToSchema(ctx *schemaContext, ident *ast.Ident) *apiext.JSONSchema
274276
if err != nil {
275277
ctx.pkg.AddError(loader.ErrFromNode(err, ident))
276278
}
279+
var enumMembers []apiext.JSON
280+
if ctx.info.Markers.Get("enum") != nil && typ == "string" {
281+
enumMembers = make([]apiext.JSON, 0, len(ctx.info.EnumValues))
282+
var ok bool
283+
for i := range ctx.info.EnumValues {
284+
var member = &ctx.info.EnumValues[i]
285+
var v *ast.BasicLit
286+
if v, ok = member.Values[0].(*ast.BasicLit); !ok {
287+
ctx.pkg.AddError(loader.ErrFromNode(errors.New("constants for a +enum decorated type should be stirngs"), ident))
288+
}
289+
var value string
290+
if value, err = strconv.Unquote(v.Value); err != nil {
291+
ctx.pkg.AddError(loader.ErrFromNode(err, ident))
292+
continue
293+
}
294+
var j apiext.JSON
295+
if j.Raw, err = json.Marshal(value); err != nil {
296+
ctx.pkg.AddError(loader.ErrFromNode(err, ident))
297+
continue
298+
}
299+
enumMembers = append(enumMembers, j)
300+
}
301+
}
277302
// Check for type aliasing to a basic type for gotypesalias=0. See more
278303
// in documentation https://pkg.go.dev/go/types#Alias:
279304
// > For gotypesalias=1, alias declarations produce an Alias type.
@@ -284,12 +309,14 @@ func localNamedToSchema(ctx *schemaContext, ident *ast.Ident) *apiext.JSONSchema
284309
link := TypeRefLink("", ident.Name)
285310
return &apiext.JSONSchemaProps{
286311
Type: typ,
312+
Enum: enumMembers,
287313
Format: fmt,
288314
Ref: &link,
289315
}
290316
}
291317
return &apiext.JSONSchemaProps{
292318
Type: typ,
319+
Enum: enumMembers,
293320
Format: fmt,
294321
}
295322
}

pkg/crd/testdata/cronjob_types.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ import (
4242

4343
const DefaultRefValue = "defaultRefValue"
4444

45+
// +enum
46+
type EnumType string
47+
48+
const (
49+
EnumType_One EnumType = "one"
50+
EnumType_Two EnumType = "two"
51+
EnumType_Three EnumType = "three"
52+
)
53+
4554
// CronJobSpec defines the desired state of CronJob
4655
// +kubebuilder:validation:XValidation:rule="has(oldSelf.forbiddenInt) || !has(self.forbiddenInt)",message="forbiddenInt is not allowed",fieldPath=".forbiddenInt",reason="FieldValueForbidden"
4756
type CronJobSpec struct {
@@ -332,6 +341,8 @@ type CronJobSpec struct {
332341
// +kubebuilder:validation:items:Enum=0;1;3
333342
EnumSlice []int `json:"enumSlice,omitempty"`
334343

344+
EnumValue EnumType `json:"enumValue,omitempty"`
345+
335346
HostsAlias Hosts `json:"hostsAlias,omitempty"`
336347

337348
// This tests that alias imported from a package is handled correctly. The

0 commit comments

Comments
 (0)