Skip to content

Commit 8aead55

Browse files
authored
Merge pull request #864 from jakobmoellerdev/fixup-apiserver-schemaorbool-nil-pointer
fix: handle nil schemas in additional properties and add RGD reference test
2 parents 00732b7 + 8b720d2 commit 8aead55

File tree

2 files changed

+133
-3
lines changed

2 files changed

+133
-3
lines changed

pkg/cel/schemas.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import (
2222

2323
"github.com/google/cel-go/cel"
2424
"github.com/google/cel-go/common/types"
25-
"k8s.io/apiserver/pkg/cel/common"
26-
2725
apiservercel "k8s.io/apiserver/pkg/cel"
26+
"k8s.io/apiserver/pkg/cel/common"
27+
celopenapi "k8s.io/apiserver/pkg/cel/openapi"
2828
)
2929

3030
// XKubernetesPreserveUnknownFields is the key for the named open api extension, declaration type metadata field
@@ -104,7 +104,20 @@ func SchemaDeclTypeWithMetadata(s common.Schema, isResourceRoot bool) *apiserver
104104
return nil
105105
case "object":
106106
if s.AdditionalProperties() != nil && s.AdditionalProperties().Schema() != nil {
107-
propsType := SchemaDeclTypeWithMetadata(s.AdditionalProperties().Schema(), s.AdditionalProperties().Schema().IsXEmbeddedResource())
107+
additional := s.AdditionalProperties().Schema()
108+
var propsType *apiservercel.DeclType
109+
// TODO(jakobmoellerdev): revisit this once upstream is fixed
110+
// upstream bug in apiserver where SchemaOrBool returns a non nil pointer with nil content
111+
// if the underlying schema
112+
// is nil instead of returning nil
113+
// has been fixed in upstream api server but not part of main codebase as of kubernetes 1.34
114+
// https://github.com/kubernetes/apiserver/blob/master/pkg/cel/openapi/adaptor.go
115+
// https://github.com/kubernetes/apiserver/blob/v0.34.2/pkg/cel/openapi/adaptor.go
116+
if openapi, ok := additional.(*celopenapi.Schema); ok {
117+
if openapi.Schema != nil {
118+
propsType = SchemaDeclTypeWithMetadata(openapi, s.AdditionalProperties().Schema().IsXEmbeddedResource())
119+
}
120+
}
108121
mt := apiservercel.NewMapType(apiservercel.StringType, apiservercel.DynType, math.MaxInt)
109122
if propsType != nil {
110123
var maxProperties int64

test/integration/suites/core/unknown_fields_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,121 @@ var _ = Describe("Unknown Fields", func() {
307307
}, 10*time.Second, time.Second).WithContext(ctx).Should(Succeed())
308308
})
309309
})
310+
311+
Context("Second RGD status referencing first RGD status", func() {
312+
It("should not panic when the second RGD embeds the first RGD", func(ctx SpecContext) {
313+
basicRGD := generator.NewResourceGraphDefinition(
314+
"basic-app-"+rand.String(4),
315+
generator.WithSchema(
316+
"BasicApp", "v1alpha1",
317+
map[string]any{
318+
"name": "string",
319+
"image": "string | default=\"nginx\"",
320+
"replicas": "integer | default=3",
321+
},
322+
map[string]any{
323+
"deploymentConditions": "${deployment.status.conditions}",
324+
"availableReplicas": "${deployment.status.availableReplicas}",
325+
},
326+
),
327+
generator.WithResource(
328+
"deployment",
329+
map[string]any{
330+
"apiVersion": "apps/v1",
331+
"kind": "Deployment",
332+
"metadata": map[string]any{
333+
"name": "${schema.spec.name}",
334+
},
335+
"spec": map[string]any{
336+
"replicas": "${schema.spec.replicas}",
337+
"selector": map[string]any{
338+
"matchLabels": map[string]any{
339+
"app": "${schema.spec.name}",
340+
},
341+
},
342+
"template": map[string]any{
343+
"metadata": map[string]any{
344+
"labels": map[string]any{
345+
"app": "${schema.spec.name}",
346+
},
347+
},
348+
"spec": map[string]any{
349+
"containers": []any{
350+
map[string]any{
351+
"name": "${schema.spec.name}",
352+
"image": "${schema.spec.image}",
353+
"ports": []any{
354+
map[string]any{
355+
"containerPort": 80,
356+
},
357+
},
358+
},
359+
},
360+
},
361+
},
362+
},
363+
},
364+
nil,
365+
nil,
366+
),
367+
)
368+
369+
Expect(env.Client.Create(ctx, basicRGD)).To(Succeed())
370+
371+
Eventually(func(g Gomega, ctx SpecContext) {
372+
g.Expect(env.Client.Get(ctx,
373+
types.NamespacedName{Name: basicRGD.Name},
374+
basicRGD,
375+
)).To(Succeed())
376+
g.Expect(basicRGD.Status.State).
377+
To(Equal(krov1alpha1.ResourceGraphDefinitionStateActive))
378+
}, 20*time.Second, time.Second).WithContext(ctx).Should(Succeed())
379+
380+
advancedRGD := generator.NewResourceGraphDefinition(
381+
"advanced-app-"+rand.String(4),
382+
generator.WithSchema(
383+
"AdvancedApp", "v1alpha1",
384+
map[string]any{
385+
"name": "string",
386+
"replicas": "integer | default=3",
387+
"image": "string | default=\"nginx\"",
388+
},
389+
map[string]any{
390+
// This line forces the second RGD to load the BasicApp status schema
391+
"deploymentInfo": "${rgddep.status}",
392+
},
393+
),
394+
generator.WithResource(
395+
"rgddep",
396+
map[string]any{
397+
"apiVersion": "kro.run/v1alpha1",
398+
"kind": "BasicApp",
399+
"metadata": map[string]any{
400+
"name": "my-advanced-app-${schema.spec.name}",
401+
},
402+
"spec": map[string]any{
403+
"name": "${schema.spec.name}",
404+
"replicas": "${schema.spec.replicas}",
405+
"image": "${schema.spec.image}",
406+
},
407+
},
408+
nil,
409+
nil,
410+
),
411+
)
412+
413+
Expect(env.Client.Create(ctx, advancedRGD)).To(Succeed())
414+
415+
Eventually(func(g Gomega, ctx SpecContext) {
416+
g.Expect(env.Client.Get(ctx,
417+
types.NamespacedName{Name: advancedRGD.Name},
418+
advancedRGD,
419+
)).To(Succeed())
420+
421+
// IF THE PANIC STILL EXISTS: this never reaches Active
422+
g.Expect(advancedRGD.Status.State).
423+
To(Equal(krov1alpha1.ResourceGraphDefinitionStateActive))
424+
}, 20*time.Second, time.Second).WithContext(ctx).Should(Succeed())
425+
})
426+
})
310427
})

0 commit comments

Comments
 (0)