From 67a3fa281e84520037844486e8007a31d7ba7942 Mon Sep 17 00:00:00 2001 From: Bob Haddleton Date: Fri, 12 Dec 2025 11:29:17 -0600 Subject: [PATCH] Add support for setting composite readiness indicator Fixes: #78 Signed-off-by: Bob Haddleton --- README.md | 18 +++++ fn.go | 4 + fn_test.go | 74 +++++++++++++++++++ input/v1beta1/input.go | 3 + .../sequencer.fn.crossplane.io_inputs.yaml | 4 + 5 files changed, 103 insertions(+) diff --git a/README.md b/README.md index fea30fe..90f2347 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,24 @@ In other words, the following rules apply: See `example/composition-regex.yaml` for a complete example. +### Composite Readiness +Enabling the `resetCompositeReadiness` flag causes the function to set the Composite's `Ready` flag to `False` when at +least one desired resource is deleted from the request. This prevents the Composite resource from entering the `Ready` +state prematurely when there are pending resources that the composite reconciler is unaware of. + +```yaml + - step: sequence-creation + functionRef: + name: function-sequencer + input: + apiVersion: sequencer.fn.crossplane.io/v1beta1 + kind: Input + resetCompositeReadiness: true + rules: + - sequence: + - first-subresource-.* + - second-resource +``` ## Installation It can be installed as follows from the Upbound marketplace: https://marketplace.upbound.io/functions/crossplane-contrib/function-sequencer diff --git a/fn.go b/fn.go index 7fb6693..4b3db6a 100644 --- a/fn.go +++ b/fn.go @@ -119,6 +119,10 @@ func (f *Function) RunFunction(_ context.Context, req *v1.RunFunctionRequest) (* continue } delete(desiredComposed, k) + if in.ResetCompositeReadiness { + // Reset the composite ready indicator to false when a desired resource is deleted. + rsp.Desired.Composite.Ready = v1.Ready_READY_FALSE + } } } break diff --git a/fn_test.go b/fn_test.go index 9afbae7..dd2038b 100644 --- a/fn_test.go +++ b/fn_test.go @@ -1109,6 +1109,80 @@ func TestRunFunction(t *testing.T) { }, }, }, + "MarkCompositeNotReady": { + reason: "Set the Composite ready flag to false", + args: args{ + req: &v1.RunFunctionRequest{ + Input: resource.MustStructObject(&v1beta1.Input{ + ResetCompositeReadiness: true, + Rules: []v1beta1.SequencingRule{ + { + Sequence: []resource.Name{ + "first", + "second", + "third", + }, + }, + }, + }), + Observed: &v1.State{ + Composite: &v1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*v1.Resource{ + "first": { + Resource: resource.MustStructJSON(mr), + }, + "second": { + Resource: resource.MustStructJSON(mr), + }, + }, + }, + Desired: &v1.State{ + Composite: &v1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*v1.Resource{ + "first": { + Resource: resource.MustStructJSON(mr), + }, + "second": { + Resource: resource.MustStructJSON(mr), + }, + "third": { + Resource: resource.MustStructJSON(mr), + }, + }, + }, + }, + }, + want: want{ + rsp: &v1.RunFunctionResponse{ + Meta: &v1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*v1.Result{ + { + Severity: v1.Severity_SEVERITY_NORMAL, + Message: "Delaying creation of resource(s) matching \"third\" because \"first\" is not fully ready (0 of 1)", + Target: &target, + }, + }, + Desired: &v1.State{ + Composite: &v1.Resource{ + Ready: v1.Ready_READY_FALSE, + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*v1.Resource{ + "first": { + Resource: resource.MustStructJSON(mr), + }, + "second": { + Resource: resource.MustStructJSON(mr), + }, + }, + }, + }, + }, + }, } for name, tc := range cases { diff --git a/input/v1beta1/input.go b/input/v1beta1/input.go index 1a1870b..9283524 100644 --- a/input/v1beta1/input.go +++ b/input/v1beta1/input.go @@ -30,6 +30,9 @@ type Input struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` + // ResetCompositeReadiness sets the composite ready state to false if desired resources are removed from the request. + // +kubebuilder:object:default=false + ResetCompositeReadiness bool `json:"resetCompositeReadiness,omitempty"` // Rules is a list of rules that describe sequences of resources. Rules []SequencingRule `json:"rules"` } diff --git a/package/input/sequencer.fn.crossplane.io_inputs.yaml b/package/input/sequencer.fn.crossplane.io_inputs.yaml index 96b1525..6c48d6e 100644 --- a/package/input/sequencer.fn.crossplane.io_inputs.yaml +++ b/package/input/sequencer.fn.crossplane.io_inputs.yaml @@ -38,6 +38,10 @@ spec: type: string metadata: type: object + resetCompositeReadiness: + description: ResetCompositeReadiness sets the composite ready state to + false if desired resources are removed from the request. + type: boolean rules: description: Rules is a list of rules that describe sequences of resources. items: