diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 1d22d0f..f5054b3 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 - name: Open Backport PR - uses: zeebe-io/backport-action@7c3f6cd5843cac11bc59a04a1b7699af93261670 # v4.5 + uses: zeebe-io/backport-action@bf97bcfb53d5250af8b9a15fab0f56158a63b224 # v4.5 with: github_token: ${{ secrets.GITHUB_TOKEN }} github_workspace: ${{ github.workspace }} diff --git a/fn.go b/fn.go index f08b563..991e05d 100644 --- a/fn.go +++ b/fn.go @@ -122,7 +122,7 @@ func (f *Function) RunFunction(_ context.Context, req *v1.RunFunctionRequest) (* // We only sequence creation of resources that don't exist yet. continue } - for _, before := range sequence[:i] { + for b, before := range sequence[:i] { beforeRegex, err := getStrictRegex(string(before)) if err != nil { response.Fatal(rsp, errors.Wrapf(err, "cannot compile regex %s", before)) @@ -183,12 +183,17 @@ func (f *Function) RunFunction(_ context.Context, req *v1.RunFunctionRequest) (* } break } - if in.EnableDeletionSequencing { + if b == i-1 && in.EnableDeletionSequencing { 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..52c6bef 100644 --- a/fn_test.go +++ b/fn_test.go @@ -1344,7 +1344,7 @@ func TestRunFunction(t *testing.T) { }, }, "MixedRegexUsageV1": { - reason: "The function should delay the creation of second and fourth resources because the first and third are not ready", + reason: "The function should create all resources and their Usages", args: args{ req: &v1.RunFunctionRequest{ Input: resource.MustStructObject(&v1beta1.Input{ @@ -1455,6 +1455,114 @@ func TestRunFunction(t *testing.T) { }, }, }, + "MixedRegexUsageCase2": { + reason: "The function should create all resources and Usages only for the prior element", + args: args{ + req: &v1.RunFunctionRequest{ + Input: resource.MustStructObject(&v1beta1.Input{ + EnableDeletionSequencing: true, + ReplayDeletion: true, + Rules: []v1beta1.SequencingRule{ + { + Sequence: []resource.Name{ + "first", + "second", + "third-.*", + }, + }, + }, + UsageVersion: v1beta1.UsageV1, + }), + Observed: &v1.State{ + Composite: &v1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*v1.Resource{ + "first": { + Resource: resource.MustStructJSON(xr), + Ready: v1.Ready_READY_TRUE, + }, + "second": { + Resource: resource.MustStructJSON(mr), + Ready: v1.Ready_READY_TRUE, + }, + "third-0": { + Resource: resource.MustStructJSON(mr), + Ready: v1.Ready_READY_TRUE, + }, + "third-1": { + 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": { + Resource: resource.MustStructJSON(xr), + Ready: v1.Ready_READY_TRUE, + }, + "second": { + Resource: resource.MustStructJSON(mr), + Ready: v1.Ready_READY_TRUE, + }, + "third-0": { + Resource: resource.MustStructJSON(mr), + Ready: v1.Ready_READY_TRUE, + }, + "third-1": { + 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": { + Resource: resource.MustStructJSON(xr), + Ready: v1.Ready_READY_TRUE, + }, + "second": { + Resource: resource.MustStructJSON(mr), + Ready: v1.Ready_READY_TRUE, + }, + "third-0": { + Resource: resource.MustStructJSON(mr), + Ready: v1.Ready_READY_TRUE, + }, + "second-first-usage": { + Resource: resource.MustStructJSON(uv1), + Ready: v1.Ready_READY_TRUE, + }, + "third-0-second-usage": { + Resource: resource.MustStructJSON(u2v1), + Ready: v1.Ready_READY_TRUE, + }, + "third-1-second-usage": { + Resource: resource.MustStructJSON(u2v1), + Ready: v1.Ready_READY_TRUE, + }, + "third-1": { + Resource: resource.MustStructJSON(mr), + Ready: v1.Ready_READY_TRUE, + }, + }, + }, + }, + }, + }, "MixedRegexUsageV2Cluster": { reason: "The function should delay the creation of second and fourth resources because the first and third are not ready", args: args{ @@ -1679,6 +1787,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{