Skip to content

Commit 58f54ec

Browse files
committed
feat(crd-from-oas): add support for additionalProperties
1 parent c0756bb commit 58f54ec

File tree

15 files changed

+410
-106
lines changed

15 files changed

+410
-106
lines changed

.mise.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ run = "controller-gen object:headerFile='../hack/generators/boilerplate.go.txt'
110110

111111
[tasks.test-crdsvalidation]
112112
description = "Run CRD validation tests"
113-
dir = "./{{env.DIR}}"
114113
# Update test paths below if more API groups are added
115114
run = '''
116115
export KUBEBUILDER_ASSETS=$(setup-envtest use -p path)

api/x-konnect/v1alpha1/konnecteventcontrolplane_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ type KonnectEventControlPlaneAPISpec struct {
6262
//
6363
//
6464
// +optional
65+
// +kubebuilder:validation:MaxProperties=50
6566
Labels Labels `json:"labels,omitempty"`
6667

6768
// The minimum runtime version supported by the API.

api/x-konnect/v1alpha1/portal_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ type PortalAPISpec struct {
129129
//
130130
//
131131
// +optional
132+
// +kubebuilder:validation:MaxProperties=50
132133
Labels LabelsUpdate `json:"labels,omitempty"`
133134

134135
// The name of the portal, used to distinguish it from other portals.

api/x-konnect/v1alpha1/schema_types.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ type CreateDcrProviderRequestAuth0 struct {
166166
//
167167
//
168168
// +optional
169+
// +kubebuilder:validation:MaxProperties=50
169170
Labels Labels `json:"labels,omitempty"`
170171
// The name of the DCR provider.
171172
// This is used to identify the DCR provider in the Konnect UI.
@@ -213,6 +214,7 @@ type CreateDcrProviderRequestAzureAd struct {
213214
//
214215
//
215216
// +optional
217+
// +kubebuilder:validation:MaxProperties=50
216218
Labels Labels `json:"labels,omitempty"`
217219
// The name of the DCR provider.
218220
// This is used to identify the DCR provider in the Konnect UI.
@@ -260,6 +262,7 @@ type CreateDcrProviderRequestCurity struct {
260262
//
261263
//
262264
// +optional
265+
// +kubebuilder:validation:MaxProperties=50
263266
Labels Labels `json:"labels,omitempty"`
264267
// The name of the DCR provider.
265268
// This is used to identify the DCR provider in the Konnect UI.
@@ -306,6 +309,7 @@ type CreateDcrProviderRequestHTTP struct {
306309
//
307310
//
308311
// +optional
312+
// +kubebuilder:validation:MaxProperties=50
309313
Labels Labels `json:"labels,omitempty"`
310314
// The name of the DCR provider.
311315
// This is used to identify the DCR provider in the Konnect UI.
@@ -352,6 +356,7 @@ type CreateDcrProviderRequestOkta struct {
352356
//
353357
//
354358
// +optional
359+
// +kubebuilder:validation:MaxProperties=50
355360
Labels Labels `json:"labels,omitempty"`
356361
// The name of the DCR provider.
357362
// This is used to identify the DCR provider in the Konnect UI.
@@ -459,12 +464,26 @@ type GatewayDescription string
459464
// GatewayName The name of the Gateway.
460465
type GatewayName string
461466

467+
// LabelsValue is the value type for Labels.
468+
//
469+
// +kubebuilder:validation:MinLength=1
470+
// +kubebuilder:validation:MaxLength=63
471+
// +kubebuilder:validation:Pattern=`^[a-z0-9A-Z]{1}([a-z0-9A-Z-._]*[a-z0-9A-Z]+)?$`
472+
type LabelsValue string
473+
462474
// Labels store metadata of an entity that can be used for filtering an entity
463475
// list or for searching across entity types.
464476
//
465477
// Keys must be of length 1-63 characters, and cannot start with "kong",
466478
// "konnect", "mesh", "kic", or "_".
467-
type Labels map[string]string
479+
type Labels map[string]LabelsValue
480+
481+
// LabelsUpdateValue is the value type for LabelsUpdate.
482+
//
483+
// +kubebuilder:validation:MinLength=1
484+
// +kubebuilder:validation:MaxLength=63
485+
// +kubebuilder:validation:Pattern=`^[a-z0-9A-Z]{1}([a-z0-9A-Z-._]*[a-z0-9A-Z]+)?$`
486+
type LabelsUpdateValue string
468487

469488
// LabelsUpdate Labels store metadata of an entity that can be used for
470489
// filtering an entity list or for searching across entity types.
@@ -473,7 +492,7 @@ type Labels map[string]string
473492
//
474493
// Keys must be of length 1-63 characters, and cannot start with "kong",
475494
// "konnect", "mesh", "kic", or "_".
476-
type LabelsUpdate map[string]string
495+
type LabelsUpdate map[string]LabelsUpdateValue
477496

478497
// MinRuntimeVersion The minimum runtime version supported by the API.
479498
// This is the lowest version of the data plane

config/crd/kong-operator/x-konnect.konghq.com_dcrproviders.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ spec:
112112
type: string
113113
labels:
114114
additionalProperties:
115+
description: LabelsValue is the value type for Labels.
116+
maxLength: 63
117+
minLength: 1
118+
pattern: ^[a-z0-9A-Z]{1}([a-z0-9A-Z-._]*[a-z0-9A-Z]+)?$
115119
type: string
116120
description: |-
117121
Labels store metadata of an entity that can be used for filtering an entity
@@ -178,6 +182,10 @@ spec:
178182
type: string
179183
labels:
180184
additionalProperties:
185+
description: LabelsValue is the value type for Labels.
186+
maxLength: 63
187+
minLength: 1
188+
pattern: ^[a-z0-9A-Z]{1}([a-z0-9A-Z-._]*[a-z0-9A-Z]+)?$
181189
type: string
182190
description: |-
183191
Labels store metadata of an entity that can be used for filtering an entity
@@ -244,6 +252,10 @@ spec:
244252
type: string
245253
labels:
246254
additionalProperties:
255+
description: LabelsValue is the value type for Labels.
256+
maxLength: 63
257+
minLength: 1
258+
pattern: ^[a-z0-9A-Z]{1}([a-z0-9A-Z-._]*[a-z0-9A-Z]+)?$
247259
type: string
248260
description: |-
249261
Labels store metadata of an entity that can be used for filtering an entity
@@ -339,6 +351,10 @@ spec:
339351
type: string
340352
labels:
341353
additionalProperties:
354+
description: LabelsValue is the value type for Labels.
355+
maxLength: 63
356+
minLength: 1
357+
pattern: ^[a-z0-9A-Z]{1}([a-z0-9A-Z-._]*[a-z0-9A-Z]+)?$
342358
type: string
343359
description: |-
344360
Labels store metadata of an entity that can be used for filtering an entity
@@ -396,6 +412,10 @@ spec:
396412
type: string
397413
labels:
398414
additionalProperties:
415+
description: LabelsValue is the value type for Labels.
416+
maxLength: 63
417+
minLength: 1
418+
pattern: ^[a-z0-9A-Z]{1}([a-z0-9A-Z-._]*[a-z0-9A-Z]+)?$
399419
type: string
400420
description: |-
401421
Labels store metadata of an entity that can be used for filtering an entity

config/crd/kong-operator/x-konnect.konghq.com_konnecteventcontrolplanes.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ spec:
6868
type: string
6969
labels:
7070
additionalProperties:
71+
description: LabelsValue is the value type for Labels.
72+
maxLength: 63
73+
minLength: 1
74+
pattern: ^[a-z0-9A-Z]{1}([a-z0-9A-Z-._]*[a-z0-9A-Z]+)?$
7175
type: string
7276
description: |-
7377
Labels store metadata of an entity that can be used for filtering an entity

config/crd/kong-operator/x-konnect.konghq.com_portals.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ spec:
187187
type: string
188188
labels:
189189
additionalProperties:
190+
description: LabelsUpdateValue is the value type for LabelsUpdate.
191+
maxLength: 63
192+
minLength: 1
193+
pattern: ^[a-z0-9A-Z]{1}([a-z0-9A-Z-._]*[a-z0-9A-Z]+)?$
190194
type: string
191195
description: |-
192196
Labels store metadata of an entity that can be used for filtering an entity

crd-from-oas/pkg/generator/generator.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,25 @@ func (g *Generator) generateSchemaTypes(refs map[string]bool, parsed *parser.Par
228228
fmt.Fprintf(&buf, "\t// %sDisabled sets %s as disabled.\n", goName, goName)
229229
fmt.Fprintf(&buf, "\t%sDisabled %s = \"Disabled\"\n", goName, goName)
230230
fmt.Fprintf(&buf, ")\n\n")
231+
232+
case schema.AdditionalProperties != nil:
233+
// Map type with value constraints: generate a dedicated value type
234+
// with native kubebuilder markers, then define the map using it.
235+
valueTypeName := refName + "Value"
236+
valueBaseType := propertyToGoBaseType(schema.AdditionalProperties)
237+
238+
fmt.Fprintf(&buf, "// %s is the value type for %s.\n", valueTypeName, refName)
239+
if markers := valueTypeMarkers(schema.AdditionalProperties); len(markers) > 0 {
240+
buf.WriteString("//\n")
241+
for _, marker := range markers {
242+
fmt.Fprintf(&buf, "// %s\n", marker)
243+
}
244+
}
245+
fmt.Fprintf(&buf, "type %s %s\n\n", valueTypeName, valueBaseType)
246+
247+
buf.WriteString(comment)
248+
fmt.Fprintf(&buf, "type %s map[string]%s\n\n", refName, valueTypeName)
249+
231250
default:
232251
// Generate based on the schema's actual type
233252
buf.WriteString(comment)

crd-from-oas/pkg/generator/generator_test.go

Lines changed: 83 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -11,61 +11,6 @@ import (
1111
"github.com/kong/kong-operator/v2/crd-from-oas/pkg/parser"
1212
)
1313

14-
func TestObjectRefTypeName(t *testing.T) {
15-
tests := []struct {
16-
name string
17-
commonTypes *config.CommonTypesConfig
18-
want string
19-
}{
20-
{
21-
name: "nil commonTypes returns ObjectRef",
22-
commonTypes: nil,
23-
want: "ObjectRef",
24-
},
25-
{
26-
name: "generate true returns ObjectRef",
27-
commonTypes: &config.CommonTypesConfig{
28-
ObjectRef: &config.ObjectRefConfig{
29-
Generate: new(true),
30-
},
31-
},
32-
want: "ObjectRef",
33-
},
34-
{
35-
name: "import with alias returns qualified name",
36-
commonTypes: &config.CommonTypesConfig{
37-
ObjectRef: &config.ObjectRefConfig{
38-
Import: &config.ImportConfig{
39-
Path: "github.com/kong/kong-operator/v2/api/common/v1alpha1",
40-
Alias: "commonv1alpha1",
41-
},
42-
},
43-
},
44-
want: "commonv1alpha1.ObjectRef",
45-
},
46-
{
47-
name: "import without alias uses last path segment",
48-
commonTypes: &config.CommonTypesConfig{
49-
ObjectRef: &config.ObjectRefConfig{
50-
Import: &config.ImportConfig{
51-
Path: "github.com/kong/kong-operator/v2/api/common/v1alpha1",
52-
},
53-
},
54-
},
55-
want: "v1alpha1.ObjectRef",
56-
},
57-
}
58-
59-
for _, tc := range tests {
60-
t.Run(tc.name, func(t *testing.T) {
61-
g := NewGenerator(Config{
62-
CommonTypes: tc.commonTypes,
63-
})
64-
assert.Equal(t, tc.want, g.objectRefTypeName())
65-
})
66-
}
67-
}
68-
6914
func TestGoType_ObjectRef(t *testing.T) {
7015
t.Run("without import uses ObjectRef", func(t *testing.T) {
7116
g := NewGenerator(Config{})
@@ -809,35 +754,6 @@ func TestGenerateSDKOps_NormalizesBooleanFields(t *testing.T) {
809754
assert.NotContains(t, content, "if err := normalizeSDKOpsBoolFields(payload); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to normalize PortalAPISpec for CreatePortal: %w\", err)")
810755
}
811756

812-
func TestGenerateSDKOpsTest_AssertsNormalizedPayload(t *testing.T) {
813-
g := NewGenerator(Config{APIVersion: "v1alpha1"})
814-
schema := &parser.Schema{
815-
Properties: []*parser.Property{
816-
{
817-
Name: "name",
818-
Type: "string",
819-
},
820-
{
821-
Name: "rbac_enabled",
822-
Type: "boolean",
823-
},
824-
},
825-
}
826-
opsConfig := &config.EntityOpsConfig{
827-
Ops: map[string]*config.OpConfig{
828-
"create": {
829-
Path: "github.com/Kong/sdk-konnect-go/models/components.CreatePortal",
830-
},
831-
},
832-
}
833-
834-
content, err := g.generateSDKOpsTest("Portal", schema, opsConfig)
835-
require.NoError(t, err)
836-
assert.Contains(t, content, `RBACEnabled: "Enabled"`)
837-
assert.Contains(t, content, `require.Equal(t, true, payload["rbac_enabled"])`)
838-
assert.Contains(t, content, `require.Equal(t, "test-value", payload["name"])`)
839-
}
840-
841757
func TestParseSDKTypePath(t *testing.T) {
842758
tests := []struct {
843759
name string
@@ -893,3 +809,86 @@ func TestParseSDKTypePath(t *testing.T) {
893809
})
894810
}
895811
}
812+
813+
func TestGenerateSchemaTypes_MapWithValueTypes(t *testing.T) {
814+
g := NewGenerator(Config{
815+
APIVersion: "v1alpha1",
816+
})
817+
818+
parsed := &parser.ParsedSpec{
819+
Schemas: map[string]*parser.Schema{
820+
"Labels": {
821+
Name: "Labels",
822+
Description: "Labels store metadata.",
823+
Type: "object",
824+
MaxProperties: func() *int64 { v := int64(50); return &v }(),
825+
AdditionalProperties: &parser.Property{
826+
Type: "string",
827+
MinLength: func() *int64 { v := int64(1); return &v }(),
828+
MaxLength: func() *int64 { v := int64(63); return &v }(),
829+
Pattern: `^[a-z0-9A-Z]+$`,
830+
},
831+
},
832+
"LabelsUpdate": {
833+
Name: "LabelsUpdate",
834+
Description: "LabelsUpdate store metadata.",
835+
Type: "object",
836+
AdditionalProperties: &parser.Property{
837+
Type: "string",
838+
MinLength: func() *int64 { v := int64(1); return &v }(),
839+
MaxLength: func() *int64 { v := int64(63); return &v }(),
840+
Pattern: `^[a-z0-9A-Z]+$`,
841+
},
842+
},
843+
},
844+
}
845+
846+
refs := map[string]bool{
847+
"Labels": true,
848+
"LabelsUpdate": true,
849+
}
850+
851+
content := g.generateSchemaTypes(refs, parsed)
852+
853+
// Labels should generate a value type with native markers, then a map type using it
854+
assert.Contains(t, content, "type LabelsValue string")
855+
assert.Contains(t, content, "type Labels map[string]LabelsValue")
856+
assert.Contains(t, content, "+kubebuilder:validation:MinLength=1")
857+
assert.Contains(t, content, "+kubebuilder:validation:MaxLength=63")
858+
assert.Contains(t, content, "+kubebuilder:validation:Pattern=`^[a-z0-9A-Z]+$`")
859+
860+
// LabelsUpdate should also generate a value type
861+
assert.Contains(t, content, "type LabelsUpdateValue string")
862+
assert.Contains(t, content, "type LabelsUpdate map[string]LabelsUpdateValue")
863+
864+
// No CEL XValidation rules or MaxProperties on the type (goes on the field)
865+
assert.NotContains(t, content, "XValidation")
866+
assert.NotContains(t, content, "MaxProperties")
867+
}
868+
869+
func TestGenerateSchemaTypes_NoValueTypeForNonMapTypes(t *testing.T) {
870+
g := NewGenerator(Config{
871+
APIVersion: "v1alpha1",
872+
})
873+
874+
parsed := &parser.ParsedSpec{
875+
Schemas: map[string]*parser.Schema{
876+
"GatewayName": {
877+
Name: "GatewayName",
878+
Description: "The name of the Gateway.",
879+
Type: "string",
880+
},
881+
},
882+
}
883+
884+
refs := map[string]bool{
885+
"GatewayName": true,
886+
}
887+
888+
content := g.generateSchemaTypes(refs, parsed)
889+
890+
assert.Contains(t, content, "type GatewayName string")
891+
assert.NotContains(t, content, "Value")
892+
assert.NotContains(t, content, "XValidation")
893+
assert.NotContains(t, content, "MaxProperties")
894+
}

0 commit comments

Comments
 (0)