Skip to content
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

Allow the user to provide a list of specific kube markers to be ignored #27

Merged
merged 14 commits into from
Aug 7, 2024
Merged
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ Other supported options are:
* when set to `true`, the native openapi schemas will be used for Integer types instead of Solo wrappers that add Kubernetes extension headers to the schema to treat int as strings.
* `disable_kube_markers`
* when set to `true`, kubebuilder markers and validations such as PreserveUnknownFields, MinItems, default, and all CEL rules will be omitted from the OpenAPI schema. The Type and Required markers will be maintained.
* `ignored_kube_markers`
* when set, ignores the contained kubebuilder markers and validations, and prevents them from being applied to the OpenAPI schema.
7 changes: 7 additions & 0 deletions changelog/v0.2.5/allow_ignored_kube_markers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
changelog:
- type: NEW_FEATURE
issueLink: https://github.com/solo-io/gloo-mesh-enterprise/issues/18119
resolvesIssue: false
description: |
Allows the user to define one or more kube markers to ignore. This is useful when using protos that contain
unsupported kubebuilder decorators.
23 changes: 23 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ import (
"path/filepath"
"strings"
"testing"
"regexp"
)

const goldenDir = "testdata/golden/"

func TestOpenAPIGeneration(t *testing.T) {
regexp.MustCompile("(:?kubebuilder:altName)")

testcases := []struct {
name string
id string
Expand Down Expand Up @@ -109,6 +112,26 @@ func TestOpenAPIGeneration(t *testing.T) {
},
wantFiles: []string{"test7/openapiv3.yaml"},
},
{
name: "Test no markers are ignored when ignored_kube_markers is zero length",
id: "test7",
perPackage: false,
genOpts: "yaml=true,single_file=true,proto_oneof=true,int_native=true,multiline_description=true,disable_kube_markers=true,ignored_kube_markers=",
inputFiles: map[string][]string{
"test7": {"./testdata/test7/markers.proto"},
},
wantFiles: []string{"test7/openapiv3.yaml"},
},
{
name: "Test ignored_kube_markers option ignores specific markers",
id: "test8",
perPackage: false,
genOpts: "yaml=true,single_file=true,proto_oneof=true,int_native=true,multiline_description=true,disable_kube_markers=true,ignored_kube_markers=Required",
inputFiles: map[string][]string{
"test8": {"./testdata/test8/markers.proto"},
},
wantFiles: []string{"test8/openapiv3.yaml"},
},
}

for _, tc := range testcases {
Expand Down
6 changes: 6 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func generate(request pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorRes
disableKubeMarkers := false

var messagesWithEmptySchema []string
var ignoredKubeMarkers []string

p := extractParams(request.GetParameter())
for k, v := range p {
Expand Down Expand Up @@ -147,6 +148,10 @@ func generate(request pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorRes
default:
return nil, fmt.Errorf("unknown value '%s' for disable_kube_markers", v)
}
} else if k == "ignored_kube_markers" {
if len(v) > 0 {
ignoredKubeMarkers = strings.Split(v, "+")
}
} else {
return nil, fmt.Errorf("unknown argument '%s' specified", k)
}
Expand Down Expand Up @@ -184,6 +189,7 @@ func generate(request pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorRes
protoOneof,
intNative,
disableKubeMarkers,
ignoredKubeMarkers,
)
return g.generateOutput(filesToGen)
}
Expand Down
7 changes: 6 additions & 1 deletion openapiGenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ type openapiGenerator struct {
// If set to true, kubebuilder markers and validations such as PreserveUnknownFields, MinItems, default, and all CEL rules will be omitted from the OpenAPI schema.
// The Type and Required markers will be maintained.
disableKubeMarkers bool

// If provided, ignores the contained kubebuilder markers and validations, and prevents them from being applied to the OpenAPI schema.
// If none are provided, no markers will be ignored.
ignoredKubeMarkers []string
}

type DescriptionConfiguration struct {
Expand All @@ -143,8 +147,9 @@ func newOpenAPIGenerator(
protoOneof bool,
intNative bool,
disableKubeMarkers bool,
ignoredKubeMarkers []string,
) *openapiGenerator {
mRegistry, err := markers.NewRegistry()
mRegistry, err := markers.NewRegistry(ignoredKubeMarkers)
if err != nil {
log.Panicf("error initializing marker registry: %v", err)
}
Expand Down
16 changes: 13 additions & 3 deletions pkg/markers/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"reflect"

"sigs.k8s.io/controller-tools/pkg/markers"
"regexp"
"fmt"
"strings"
)

const (
Expand All @@ -17,13 +20,20 @@ type definitionWithHelp struct {
}

type Registry struct {
mRegistry *markers.Registry
mRegistry *markers.Registry
ignoredKubeMarkersRegex *regexp.Regexp
}

func NewRegistry() (*Registry, error) {
func NewRegistry(ignoredKubeMarkers []string) (*Registry, error) {
var ignoredKubeMarkersRegexp *regexp.Regexp
if len(ignoredKubeMarkers) > 0 {
toIgnore := strings.Join(ignoredKubeMarkers, "|")
ignoredKubeMarkersRegexp = regexp.MustCompile(fmt.Sprintf("(?:%s)", toIgnore))
}
mReg := &markers.Registry{}
r := &Registry{
mRegistry: mReg,
mRegistry: mReg,
ignoredKubeMarkersRegex: ignoredKubeMarkersRegexp,
}
return r, Register(mReg)
}
Expand Down
22 changes: 22 additions & 0 deletions pkg/markers/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,12 @@ func (r *Registry) ApplyRulesToSchema(
o *openapi3.Schema,
target markers.TargetType,
) error {

for _, rule := range rules {
if r.isIgnoredKubeMarker(rule) {
continue
}

defn := r.mRegistry.Lookup(rule, target)
if defn == nil {
return fmt.Errorf("no definition found for rule: %s", rule)
Expand All @@ -164,6 +169,10 @@ func (r *Registry) GetSchemaType(
target markers.TargetType,
) Type {
for _, rule := range rules {
if r.isIgnoredKubeMarker(rule) {
continue
}

defn := r.mRegistry.Lookup(rule, target)
if defn == nil {
log.Panicf("no definition found for rule: %s", rule)
Expand All @@ -183,6 +192,10 @@ func (r *Registry) IsRequired(
rules []string,
) bool {
for _, rule := range rules {
if r.isIgnoredKubeMarker(rule) {
continue
}

defn := r.mRegistry.Lookup(rule, markers.DescribesField)
if defn == nil {
log.Panicf("no definition found for rule: %s", rule)
Expand All @@ -191,5 +204,14 @@ func (r *Registry) IsRequired(
return true
}
}

return false
}

func (r *Registry) isIgnoredKubeMarker(rule string) bool {
if r.ignoredKubeMarkersRegex == nil {
return false
}

return r.ignoredKubeMarkersRegex.MatchString(rule)
}
47 changes: 47 additions & 0 deletions testdata/golden/test8/openapiv3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
components:
schemas:
test7.Msg:
description: This is a top-level message.
properties:
a:
format: int32
type: integer
blist:
items:
type: string
type: array
nested:
properties:
a:
type: string
b:
type: string
c:
type: string
d:
type: string
defaultValue:
type: string
embedded:
type: string
intOrString:
type: string
schemaless:
description: Schemaless field
type: string
type: object
object:
description: Should maintain valid Type marker and not enumerate subfields.
type: object
x-kubernetes-preserve-unknown-fields: true
recursive:
type: object
x-kubernetes-preserve-unknown-fields: true
val:
x-kubernetes-preserve-unknown-fields: true
type: object
info:
title: OpenAPI Spec for Solo APIs.
version: ""
openapi: 3.0.1
paths: null
85 changes: 85 additions & 0 deletions testdata/test8/markers.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
syntax = "proto3";

package test7;

import "struct.proto";

// This is a top-level message.
//
// +kubebuilder:pruning:PreserveUnknownFields
message Msg {
// +kubebuilder:pruning:PreserveUnknownFields
Nested nested = 1;

// +kubebuilder:validation:Maximum=100
// +kubebuilder:validation:Minimum=5
// +kubebuilder:validation:ExclusiveMaximum=true
// +kubebuilder:validation:ExclusiveMinimum=true
// +kubebuilder:validation:MultipleOf=2
// +kubebuilder:validation:XValidation:rule="self != 27",message="must not equal 27"
int32 a = 2;

// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=5
// +kubebuilder:validation:UniqueItems=true
repeated string blist = 3;

// +kubebuilder:validation:Type=value
google.protobuf.Value val = 4;

// Should maintain valid Type marker and not enumerate subfields.
//
// +kubebuilder:validation:Type=object
Nested2 object = 5;

// +kubebuilder:validation:Type=object
Recursive recursive = 6;

// This is a nested message.
//
// +kubebuilder:validation:MinProperties=1
// +kubebuilder:validation:MaxProperties=2
message Nested {
// +kubebuilder:validation:Pattern="^[a-zA-Z0-9_]*$"
// +kubebuilder:validation:Required
string a = 1;

// +kubebuilder:validation:Enum=Allow;Forbid;Replace
// +kubebuilder:validation:Required
string b = 2;

// +kubebuilder:validation:MaxLength=100
// +kubebuilder:validation:MinLength=1
string c = 3;

// +kubebuilder:validation:Format=date-time
string d = 4;

// +kubebuilder:validation:XIntOrString
string int_or_string = 5;

// +kubebuilder:default=forty-two
// +kubebuilder:example=forty-two
string default_value = 6;

// Schemaless field
//
// +kubebuilder:validation:Schemaless
string schemaless = 7;

// +kubebuilder:validation:EmbeddedResource
// +kubebuilder:validation:Nullable
string embedded = 8;
}

message Nested2 {
string a = 1;
string b = 2;
int32 c = 3;
}

message Recursive {
Recursive r = 1;
}
}

Loading