Skip to content

Commit fd90bd5

Browse files
authored
Merge pull request #59 from jbw976/missing-schemas
Return fatal error when schema resolution gets empty response
2 parents 902d8c4 + 7a3f63d commit fd90bd5

2 files changed

Lines changed: 141 additions & 4 deletions

File tree

fn.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,9 +303,9 @@ func (f *Function) buildResolverFromSchemas(req *fnv1.RunFunctionRequest, gvks [
303303
schemas[gvk] = specSchema
304304
}
305305

306-
// There are no schemas we care about yet
306+
// Crossplane responded with required schemas, but we have no usable schemas from the response.
307307
if len(schemas) == 0 {
308-
return nil, nil, nil
308+
return nil, nil, errors.New("no usable schemas found in response")
309309
}
310310

311311
// Create the schema map resolver
@@ -367,9 +367,9 @@ func (f *Function) buildResolverFromCRDs(req *fnv1.RunFunctionRequest, gvks []sc
367367
crds = append(crds, crd)
368368
}
369369

370+
// Crossplane responded to our request for CRDs, but we have no usable CRDS from the response.
370371
if len(crds) == 0 {
371-
// No CRDs available yet.
372-
return nil, nil, nil
372+
return nil, nil, errors.New("no usable CRDs found in response")
373373
}
374374

375375
// Create combined resolver from CRDs.

fn_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,143 @@ func TestRunFunction(t *testing.T) {
316316
},
317317
},
318318
},
319+
"EmptySchemasFromRequiredSchemas": {
320+
reason: "When Crossplane responds via required_schemas but all schemas are nil, the function should return a fatal error rather than silently returning empty desired state",
321+
args: args{
322+
req: &fnv1.RunFunctionRequest{
323+
Meta: &fnv1.RequestMeta{Tag: "test", Capabilities: []fnv1.Capability{fnv1.Capability_CAPABILITY_CAPABILITIES, fnv1.Capability_CAPABILITY_REQUIRED_SCHEMAS}},
324+
Input: resource.MustStructJSON(`{
325+
"apiVersion": "kro.fn.crossplane.io/v1alpha1",
326+
"kind": "ResourceGraph",
327+
"resources": [{
328+
"id": "bucket",
329+
"template": {
330+
"apiVersion": "s3.aws.upbound.io/v1beta1",
331+
"kind": "Bucket",
332+
"metadata": {},
333+
"spec": {
334+
"forProvider": {
335+
"region": "us-west-2"
336+
}
337+
}
338+
}
339+
}],
340+
"status": {
341+
"bucketName": "${bucket.status.atProvider.id}"
342+
}
343+
}`),
344+
Observed: &fnv1.State{
345+
Composite: &fnv1.Resource{
346+
Resource: resource.MustStructJSON(`{
347+
"apiVersion": "example.crossplane.io/v1",
348+
"kind": "XBucket",
349+
"metadata": {"name": "test-bucket"},
350+
"spec": {}
351+
}`),
352+
},
353+
},
354+
// Crossplane responded with schema entries but all are nil,
355+
// simulating a problem causing all schemas to not be found.
356+
RequiredSchemas: map[string]*fnv1.Schema{
357+
"example.crossplane.io/v1, Kind=XBucket": nil,
358+
"s3.aws.upbound.io/v1beta1, Kind=Bucket": nil,
359+
},
360+
},
361+
},
362+
want: want{
363+
rsp: &fnv1.RunFunctionResponse{
364+
Meta: &fnv1.ResponseMeta{Tag: "test", Ttl: durationpb.New(response.DefaultTTL)},
365+
Requirements: &fnv1.Requirements{
366+
Schemas: map[string]*fnv1.SchemaSelector{
367+
"example.crossplane.io/v1, Kind=XBucket": {
368+
ApiVersion: "example.crossplane.io/v1",
369+
Kind: "XBucket",
370+
},
371+
"s3.aws.upbound.io/v1beta1, Kind=Bucket": {
372+
ApiVersion: "s3.aws.upbound.io/v1beta1",
373+
Kind: "Bucket",
374+
},
375+
},
376+
},
377+
Results: []*fnv1.Result{{
378+
// we should be returning a fatal result, we don't have usable schemas and we can't compose desired resources
379+
Severity: fnv1.Severity_SEVERITY_FATAL,
380+
Message: "cannot process schemas: no usable schemas found in response",
381+
Target: fnv1.Target_TARGET_COMPOSITE.Enum(),
382+
}},
383+
},
384+
},
385+
},
386+
"EmptyCRDsFromRequiredResources": {
387+
reason: "When Crossplane responds via required_resources but all CRD entries are empty, the function should return a fatal error rather than silently returning empty desired state",
388+
args: args{
389+
req: &fnv1.RunFunctionRequest{
390+
Meta: &fnv1.RequestMeta{Tag: "test", Capabilities: []fnv1.Capability{}},
391+
Input: resource.MustStructJSON(`{
392+
"apiVersion": "kro.fn.crossplane.io/v1alpha1",
393+
"kind": "ResourceGraph",
394+
"resources": [{
395+
"id": "bucket",
396+
"template": {
397+
"apiVersion": "s3.aws.upbound.io/v1beta1",
398+
"kind": "Bucket",
399+
"metadata": {},
400+
"spec": {
401+
"forProvider": {
402+
"region": "us-west-2"
403+
}
404+
}
405+
}
406+
}],
407+
"status": {
408+
"bucketName": "${bucket.status.atProvider.id}"
409+
}
410+
}`),
411+
Observed: &fnv1.State{
412+
Composite: &fnv1.Resource{
413+
Resource: resource.MustStructJSON(`{
414+
"apiVersion": "example.crossplane.io/v1",
415+
"kind": "XBucket",
416+
"metadata": {"name": "test-bucket"},
417+
"spec": {}
418+
}`),
419+
},
420+
},
421+
// Crossplane responded for all GVKs but with empty items,
422+
// simulating CRDs that couldn't be found.
423+
RequiredResources: map[string]*fnv1.Resources{
424+
"example.crossplane.io/v1, Kind=XBucket": {Items: []*fnv1.Resource{}},
425+
"s3.aws.upbound.io/v1beta1, Kind=Bucket": {Items: []*fnv1.Resource{}},
426+
},
427+
},
428+
},
429+
want: want{
430+
rsp: &fnv1.RunFunctionResponse{
431+
Meta: &fnv1.ResponseMeta{Tag: "test", Ttl: durationpb.New(response.DefaultTTL)},
432+
Requirements: &fnv1.Requirements{
433+
Resources: map[string]*fnv1.ResourceSelector{
434+
"example.crossplane.io/v1, Kind=XBucket": {
435+
ApiVersion: "apiextensions.k8s.io/v1",
436+
Kind: "CustomResourceDefinition",
437+
Match: &fnv1.ResourceSelector_MatchName{MatchName: "xbuckets.example.crossplane.io"},
438+
},
439+
"s3.aws.upbound.io/v1beta1, Kind=Bucket": {
440+
ApiVersion: "apiextensions.k8s.io/v1",
441+
Kind: "CustomResourceDefinition",
442+
Match: &fnv1.ResourceSelector_MatchName{MatchName: "buckets.s3.aws.upbound.io"},
443+
},
444+
},
445+
},
446+
Results: []*fnv1.Result{{
447+
// we should return a fatal result because we have no CRD schemas to use
448+
// and cannot compose desired resources without them
449+
Severity: fnv1.Severity_SEVERITY_FATAL,
450+
Message: "cannot process schemas: no usable CRDs found in response",
451+
Target: fnv1.Target_TARGET_COMPOSITE.Enum(),
452+
}},
453+
},
454+
},
455+
},
319456
"DesiredXROnlyContainsDeclaredStatus": {
320457
reason: "The desired XR should contain resolved status expressions but not observed XR metadata (uid, resourceVersion) or existing status (conditions)",
321458
args: args{

0 commit comments

Comments
 (0)