From 333254cd298a6917f7d1f61541d443edc4558fc6 Mon Sep 17 00:00:00 2001 From: Bob Haddleton Date: Mon, 4 May 2026 12:21:36 -0500 Subject: [PATCH 1/4] Fix extra Usage creation from regex Signed-off-by: Bob Haddleton --- fn.go | 4 +- fn_test.go | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/fn.go b/fn.go index f08b563..713a26c 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,7 +183,7 @@ 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 { diff --git a/fn_test.go b/fn_test.go index a21c9bd..a3f8d81 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{ From 2b147b5e2737c881683526f25cf1526bdc479866 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 08:31:42 -0500 Subject: [PATCH 2/4] chore(deps): update zeebe-io/backport-action digest to bf97bcf (#141) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 }} From d86622437744daf045e0a53d072df2f42ab098b9 Mon Sep 17 00:00:00 2001 From: Gabriel Gaudreau Date: Wed, 6 May 2026 10:09:16 -0400 Subject: [PATCH 3/4] fix: nil pointer dereference in deletion sequencing when a before-resource is not yet observed (#140) when enableDeletionSequencing=true, the function panics when a before-resource exists in desired but not yet in observed Signed-off-by: Gabriel Gaudreau Co-authored-by: Gabriel Gaudreau Co-authored-by: Bob Haddleton --- fn.go | 7 +++++- fn_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) 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{ From 7d88f10854a8489f8d619f9fe17f55f2b5b1a00d Mon Sep 17 00:00:00 2001 From: Bob Haddleton Date: Mon, 4 May 2026 12:21:36 -0500 Subject: [PATCH 4/4] Fix extra Usage creation from regex Signed-off-by: Bob Haddleton --- fn.go | 4 +- fn_test.go | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/fn.go b/fn.go index 4a6aa98..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,7 +183,7 @@ 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 { diff --git a/fn_test.go b/fn_test.go index 10df34a..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{