diff --git a/example/observed-regex.yaml b/example/observed-regex.yaml index 56e32d9..4912e05 100644 --- a/example/observed-regex.yaml +++ b/example/observed-regex.yaml @@ -4,7 +4,7 @@ metadata: annotations: crossplane.io/external-name: foo crossplane.io/composition-resource-name: first-subresource-2 - name: first + name: first-subresource-2 spec: forProvider: conditionAfter: @@ -32,7 +32,7 @@ metadata: annotations: crossplane.io/external-name: foo crossplane.io/composition-resource-name: first-subresource-1 - name: first + name: first-subresource-1 spec: forProvider: conditionAfter: diff --git a/fn.go b/fn.go index 68ff21b..5fb9c5d 100644 --- a/fn.go +++ b/fn.go @@ -71,21 +71,14 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ continue } for _, before := range sequence[:i] { - strictPattern := string(before) - if !strings.HasPrefix(strictPattern, START) && !strings.HasSuffix(strictPattern, END) { - // if the user provides a delimited regex, we'll use it as is - // if not, add the regex with ^ & $ to match the entire string - // possibly avoid using regex for matching literal strings - strictPattern = fmt.Sprintf("%s%s%s", START, string(before), END) - } - re, err := regexp.Compile(strictPattern) + beforeRegex, err := getStrictRegex(string(before)) if err != nil { - response.Fatal(rsp, errors.Wrapf(err, "cannot compile regex %s", strictPattern)) + response.Fatal(rsp, errors.Wrapf(err, "cannot compile regex %s", before)) return rsp, nil } keys := []resource.Name{} for k := range desiredComposed { - if re.MatchString(string(k)) { + if beforeRegex.MatchString(string(k)) { keys = append(keys, k) } } @@ -103,11 +96,11 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ if desired == 0 || desired != readyResources { // no resource created - msg := fmt.Sprintf("Delaying creation of resource %q because %q does not exist yet", r, before) + msg := fmt.Sprintf("Delaying creation of resource(s) matching %q because %q does not exist yet", r, before) if desired > 0 { // provide a nicer message if there are resources. msg = fmt.Sprintf( - "Delaying creation of resource %q because %q is not fully ready (%d of %d)", + "Delaying creation of resource(s) matching %q because %q is not fully ready (%d of %d)", r, before, readyResources, @@ -116,7 +109,13 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ } response.Normal(rsp, msg) f.log.Info(msg) - delete(desiredComposed, r) + // find all objects that match the regex and delete them from the desiredComposed map + currentRegex, _ := getStrictRegex(string(r)) + for k := range desiredComposed { + if currentRegex.MatchString(string(k)) { + delete(desiredComposed, k) + } + } break } } @@ -126,3 +125,13 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ rsp.Desired.Resources = nil return rsp, response.SetDesiredComposedResources(rsp, desiredComposed) } + +func getStrictRegex(pattern string) (*regexp.Regexp, error) { + if !strings.HasPrefix(pattern, START) && !strings.HasSuffix(pattern, END) { + // if the user provides a delimited regex, we'll use it as is + // if not, add the regex with ^ & $ to match the entire string + // possibly avoid using regex for matching literal strings + pattern = fmt.Sprintf("%s%s%s", START, pattern, END) + } + return regexp.Compile(pattern) +} diff --git a/fn_test.go b/fn_test.go index 32f97a5..98d7c99 100644 --- a/fn_test.go +++ b/fn_test.go @@ -89,7 +89,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, - Message: "Delaying creation of resource \"third\" because \"first\" is not fully ready (0 of 1)", + Message: "Delaying creation of resource(s) matching \"third\" because \"first\" is not fully ready (0 of 1)", }, }, Desired: &fnv1beta1.State{ @@ -142,7 +142,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, - Message: "Delaying creation of resource \"second\" because \"first\" does not exist yet", + Message: "Delaying creation of resource(s) matching \"second\" because \"first\" does not exist yet", }, }, Desired: &fnv1beta1.State{ @@ -197,7 +197,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, - Message: "Delaying creation of resource \"second\" because \"first\" is not fully ready (0 of 1)", + Message: "Delaying creation of resource(s) matching \"second\" because \"first\" is not fully ready (0 of 1)", }, }, Desired: &fnv1beta1.State{ @@ -383,11 +383,11 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, - Message: "Delaying creation of resource \"second\" because \"first\" is not fully ready (0 of 1)", + Message: "Delaying creation of resource(s) matching \"second\" because \"first\" is not fully ready (0 of 1)", }, { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, - Message: "Delaying creation of resource \"fourth\" because \"third\" is not fully ready (0 of 1)", + Message: "Delaying creation of resource(s) matching \"fourth\" because \"third\" is not fully ready (0 of 1)", }, }, Desired: &fnv1beta1.State{ @@ -528,7 +528,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, - Message: "Delaying creation of resource \"second\" because \"first\" is not fully ready (0 of 1)", + Message: "Delaying creation of resource(s) matching \"second\" because \"first\" is not fully ready (0 of 1)", }, }, Desired: &fnv1beta1.State{ @@ -597,7 +597,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, - Message: "Delaying creation of resource \"second\" because \"first-.*\" is not fully ready (2 of 3)", + Message: "Delaying creation of resource(s) matching \"second\" because \"first-.*\" is not fully ready (2 of 3)", }, }, Desired: &fnv1beta1.State{ @@ -679,7 +679,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, - Message: "Delaying creation of resource \"third\" because \"second-.*\" is not fully ready (1 of 2)", + Message: "Delaying creation of resource(s) matching \"third\" because \"second-.*\" is not fully ready (1 of 2)", }, }, Desired: &fnv1beta1.State{ @@ -761,7 +761,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, - Message: "Delaying creation of resource \"third\" because \"second-.*\" is not fully ready (1 of 2)", + Message: "Delaying creation of resource(s) matching \"third\" because \"second-.*\" is not fully ready (1 of 2)", }, }, Desired: &fnv1beta1.State{ @@ -785,6 +785,84 @@ func TestRunFunction(t *testing.T) { }, }, }, + "SequenceRegexWaitComplex": { + reason: "The function should not modify the sequence regex, since it's already prefixed", + args: args{ + req: &fnv1beta1.RunFunctionRequest{ + Input: resource.MustStructObject(&v1beta1.Input{ + Rules: []v1beta1.SequencingRule{ + { + Sequence: []resource.Name{ + "first-.*", + "second$", + "third-resource", + }, + }, + }, + }), + Observed: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*fnv1beta1.Resource{}, + }, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*fnv1beta1.Resource{ + "first-0": { + Resource: resource.MustStructJSON(mr), + Ready: fnv1beta1.Ready_READY_TRUE, + }, + "first-1": { + Resource: resource.MustStructJSON(mr), + Ready: fnv1beta1.Ready_READY_FALSE, + }, + "0-second": { + Resource: resource.MustStructJSON(mr), + }, + "1-second": { + Resource: resource.MustStructJSON(mr), + }, + "third-resource": { + Resource: resource.MustStructJSON(mr), + }, + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*fnv1beta1.Result{ + { + Severity: fnv1beta1.Severity_SEVERITY_NORMAL, + Message: "Delaying creation of resource(s) matching \"second$\" because \"first-.*\" is not fully ready (1 of 2)", + }, + { + Severity: fnv1beta1.Severity_SEVERITY_NORMAL, + Message: "Delaying creation of resource(s) matching \"third-resource\" because \"first-.*\" is not fully ready (1 of 2)", + }, + }, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + Resources: map[string]*fnv1beta1.Resource{ + "first-0": { + Resource: resource.MustStructJSON(mr), + Ready: fnv1beta1.Ready_READY_TRUE, + }, + "first-1": { + Resource: resource.MustStructJSON(mr), + Ready: fnv1beta1.Ready_READY_FALSE, + }, + }, + }, + }, + }, + }, "SequenceRegexAlreadyPrefixed": { reason: "The function should not modify the sequence regex, since it's already prefixed", args: args{ @@ -837,7 +915,7 @@ func TestRunFunction(t *testing.T) { Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, - Message: "Delaying creation of resource \"fourth\" because \"third-.*$\" is not fully ready (0 of 1)", + Message: "Delaying creation of resource(s) matching \"fourth\" because \"third-.*$\" is not fully ready (0 of 1)", }, }, Desired: &fnv1beta1.State{