Skip to content

Commit

Permalink
Ignore unknown k8s:validation comments tags
Browse files Browse the repository at this point in the history
  • Loading branch information
jpbetz committed Dec 7, 2024
1 parent 67ed584 commit 1dbbfa0
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 5 deletions.
42 changes: 39 additions & 3 deletions pkg/generators/markers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ import (
"encoding/json"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"sync"

"k8s.io/gengo/v2/types"
openapi "k8s.io/kube-openapi/pkg/common"
Expand Down Expand Up @@ -61,6 +63,26 @@ func (c *CELTag) Validate() error {
return nil
}

func isKnownTagCommentKey(key string) bool {
commentTag, _, _ := strings.Cut(key, ":")
_, ok := tagKeys()[commentTag]
return ok
}

var tagKeys = sync.OnceValue(func() map[string]struct{} {
result := map[string]struct{}{}
t := reflect.TypeOf(commentTags{})
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if jsonTag := field.Tag.Get("json"); jsonTag != "" {
if key, _, _ := strings.Cut(jsonTag, ","); key != "" {
result[key] = struct{}{}
}
}
}
return result
})

// commentTags represents the parsed comment tags for a given type. These types are then used to generate schema validations.
// These only include the newer prefixed tags. The older tags are still supported,
// but are not included in this struct. Comment Tags are transformed into a
Expand Down Expand Up @@ -385,13 +407,24 @@ func memberWithJSONName(t *types.Type, key string) *types.Member {
return nil
}

type ParseCommentTagsOptions struct {
IgnoreUnknown bool
}

// Parses the given comments into a CommentTags type. Validates the parsed comment tags, and returns the result.
// Accepts an optional type to validate against, and a prefix to filter out markers not related to validation.
// Accepts a prefix to filter out markers not related to validation.
// Returns any errors encountered while parsing or validating the comment tags.
func ParseCommentTags(t *types.Type, comments []string, prefix string) (*spec.Schema, error) {
func ParseCommentTags(t *types.Type, comments []string, prefix string, opts ...ParseCommentTagsOptions) (*spec.Schema, error) {
options := ParseCommentTagsOptions{}
if len(opts) > 0 {
options = opts[0]
if len(opts) > 1 {
return nil, fmt.Errorf("only one option is allowed")
}
}

markers, err := parseMarkers(comments, prefix)
markers, err := parseMarkers(comments, prefix, options.IgnoreUnknown)
if err != nil {
return nil, fmt.Errorf("failed to parse marker comments: %w", err)
}
Expand Down Expand Up @@ -597,7 +630,7 @@ func extractCommentTags(marker string, lines []string) (map[string]string, error
// Accepts a prefix to filter out markers not related to validation.
// The prefix is removed from the key in the returned map.
// Empty keys and invalid values will return errors, refs are currently unsupported and will be skipped.
func parseMarkers(markerComments []string, prefix string) (map[string]any, error) {
func parseMarkers(markerComments []string, prefix string, ignoreUnknown bool) (map[string]any, error) {
markers, err := extractCommentTags(prefix, markerComments)
if err != nil {
return nil, err
Expand All @@ -606,6 +639,9 @@ func parseMarkers(markerComments []string, prefix string) (map[string]any, error
// Parse the values as JSON
result := map[string]any{}
for key, value := range markers {
if ignoreUnknown && !isKnownTagCommentKey(key) {
continue
}
var unmarshalled interface{}

if len(key) == 0 {
Expand Down
22 changes: 21 additions & 1 deletion pkg/generators/markers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"testing"

"github.com/stretchr/testify/require"

"k8s.io/gengo/v2/types"
"k8s.io/kube-openapi/pkg/generators"
"k8s.io/kube-openapi/pkg/validation/spec"
Expand Down Expand Up @@ -626,6 +627,7 @@ func TestCommentTags_Validate(t *testing.T) {
comments []string
t *types.Type
errorMessage string
options generators.ParseCommentTagsOptions
}{
{
name: "invalid minimum type",
Expand Down Expand Up @@ -960,11 +962,29 @@ func TestCommentTags_Validate(t *testing.T) {
},
errorMessage: `failed to validate property "name": pattern can only be used on string types`,
},
{
name: "ignore unknown field with unparsable value",
comments: []string{
`+k8s:validation:xyz=a=b`, // a=b is not a valid value
},
t: &types.Type{
Kind: types.Struct,
Name: types.Name{Name: "struct"},
Members: []types.Member{
{
Name: "name",
Type: types.String,
Tags: `json:"name"`,
},
},
},
options: generators.ParseCommentTagsOptions{IgnoreUnknown: true},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := generators.ParseCommentTags(tc.t, tc.comments, "+k8s:validation:")
_, err := generators.ParseCommentTags(tc.t, tc.comments, "+k8s:validation:", tc.options)
if tc.errorMessage != "" {
require.Error(t, err)
require.Equal(t, "invalid marker comments: "+tc.errorMessage, err.Error())
Expand Down
3 changes: 2 additions & 1 deletion pkg/generators/openapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/google/go-cmp/cmp"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/packages/packagestest"

"k8s.io/gengo/v2/generator"
"k8s.io/gengo/v2/namer"
"k8s.io/gengo/v2/parser"
Expand Down Expand Up @@ -2386,7 +2387,7 @@ func TestMarkerComments(t *testing.T) {
// +k8s:validation:pattern="^foo$[0-9]+"
StringValue string
// +k8s:validation:maxitems=10
// +k8s:validation:maxItems=10
// +k8s:validation:minItems=1
// +k8s:validation:uniqueItems
ArrayValue []string
Expand Down

0 comments on commit 1dbbfa0

Please sign in to comment.