diff --git a/fn.go b/fn.go index f08b563..4a6aa98 100644 --- a/fn.go +++ b/fn.go @@ -187,8 +187,13 @@ func (f *Function) RunFunction(_ context.Context, req *v1.RunFunctionRequest) (* for c, o := range observedComposed { if currentRegex.MatchString(string(c)) && !isUsage(o, in.UsageVersion) { for _, k := range keys { + obs, ok := observedComposed[k] + if !ok { + f.log.Debug("Skipping usage; before-resource not yet observed", "k:", k, "by c:", c) + continue + } f.log.Debug("Generate Usage of ", "k:", k, "by c:", c) - usage := GenerateUsage(&observedComposed[k].Resource.Unstructured, &o.Resource.Unstructured, in.ReplayDeletion, in.UsageVersion) + usage := GenerateUsage(&obs.Resource.Unstructured, &o.Resource.Unstructured, in.ReplayDeletion, in.UsageVersion) usageComposed := composed.New() if err := convertViaJSON(usageComposed, usage); err != nil { response.Fatal(rsp, errors.Wrapf(err, "cannot convert to JSON %s", usage)) diff --git a/fn_test.go b/fn_test.go index a21c9bd..10df34a 100644 --- a/fn_test.go +++ b/fn_test.go @@ -1679,6 +1679,72 @@ func TestRunFunction(t *testing.T) { }, }, }, + "DeletionSequencingBeforeResourceNotObserved": { + reason: "The function should not panic when a before-resource is in desiredComposed but not in observedComposed with deletion sequencing enabled", + args: args{ + req: &v1.RunFunctionRequest{ + Input: resource.MustStructObject(&v1beta1.Input{ + EnableDeletionSequencing: true, + Rules: []v1beta1.SequencingRule{ + { + Sequence: []resource.Name{ + "first-.*", + "second-.*", + }, + }, + }, + UsageVersion: v1beta1.UsageV2, + }), + Observed: &v1.State{ + Composite: &v1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*v1.Resource{ + "second-foo": { + Resource: resource.MustStructJSON(mr), + Ready: v1.Ready_READY_TRUE, + }, + }, + }, + Desired: &v1.State{ + Composite: &v1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*v1.Resource{ + "first-foo": { + Resource: resource.MustStructJSON(xr), + Ready: v1.Ready_READY_TRUE, + }, + "second-foo": { + Resource: resource.MustStructJSON(mr), + Ready: v1.Ready_READY_TRUE, + }, + }, + }, + }, + }, + want: want{ + rsp: &v1.RunFunctionResponse{ + Meta: &v1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*v1.Result{}, + Desired: &v1.State{ + Composite: &v1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*v1.Resource{ + "first-foo": { + Resource: resource.MustStructJSON(xr), + Ready: v1.Ready_READY_TRUE, + }, + "second-foo": { + Resource: resource.MustStructJSON(mr), + Ready: v1.Ready_READY_TRUE, + }, + }, + }, + }, + }, + }, "MarkCompositeNotReady": { reason: "Set the Composite ready flag to false", args: args{