Skip to content

Commit

Permalink
Merge pull request #32 from deveeztech/backport-strict-regex-to-relea…
Browse files Browse the repository at this point in the history
…se-0.2

Backport strict regex to release 0.2
  • Loading branch information
turkenh authored Nov 26, 2024
2 parents 3b7c54d + 3438209 commit 4ca0f46
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 14 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ See `example/composition.yaml` for a complete example.

It also allows you to provide a regex to match and capture a whole group of resources and wait for them to be ready.

For example, the pipeline step below, will ensure that `second-resource` is not created until all `first-subresource-*` objects are ready.
For example, the pipeline step below, will ensure that `second-resource` is not created until all `first-subresource-.*` objects are ready.

```yaml
- step: sequence-creation
Expand All @@ -38,10 +38,19 @@ For example, the pipeline step below, will ensure that `second-resource` is not
kind: Input
rules:
- sequence:
- first-subresource-*
- first-subresource-.*
- second-resource
```

You can write the regex as strict as you want, but keep in mind that it defaults to strict matching (start and end are enforced).
In other words, the following rules apply:
```yaml
- resource # this has no explicit start or end, so it will match EXACTLY ^resource$ (normal behaviour)
- a-group-.* # this has no explicit start or end, so it will match EXACTLY ^a-group-.*$
- ^a-group # this has an explicit start, so it will match EVERYTHING that starts with a-group
- a-group$ # this has an explicit end, so it will match EVERYTHING that ends with a-group
```

See `example/composition-regex.yaml` for a complete example.

## Installation
Expand Down
24 changes: 20 additions & 4 deletions example/composition-regex.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,27 @@ spec:
- time: 10s
conditionType: Ready
conditionStatus: "True"
- name: second-resource
- name: second-object
base:
apiVersion: nop.crossplane.io/v1alpha1
kind: NopResource
metadata:
name: second-resource
name: second-object
spec:
forProvider:
conditionAfter:
- time: 5s
conditionType: Ready
conditionStatus: "False"
- time: 10s
conditionType: Ready
conditionStatus: "True"
- name: third-resource
base:
apiVersion: nop.crossplane.io/v1alpha1
kind: NopResource
metadata:
name: third-resource
spec:
forProvider:
conditionAfter:
Expand All @@ -78,5 +93,6 @@ spec:
kind: Input
rules:
- sequence:
- first-subresource-*
- second-resource
- first-subresource-.*
- object$ # this will match everything that ends with "object"
- third-resource
21 changes: 20 additions & 1 deletion fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"regexp"
"strings"

"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/logging"
Expand All @@ -21,6 +22,13 @@ type Function struct {
log logging.Logger
}

const (
// START marks the start of a regex pattern.
START = "^"
// END marks the end of a regex pattern.
END = "$"
)

// RunFunction runs the Function.
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) { //nolint:gocyclo // This function is unavoidably complex.
f.log.Info("Running function", "tag", req.GetMeta().GetTag())
Expand Down Expand Up @@ -63,7 +71,18 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ
continue
}
for _, before := range sequence[:i] {
re := regexp.MustCompile(string(before))
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)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot compile regex %s", strictPattern))
return rsp, nil
}
keys := []resource.Name{}
for k := range desiredComposed {
if re.MatchString(string(k)) {
Expand Down
166 changes: 159 additions & 7 deletions fn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ func TestRunFunction(t *testing.T) {
Rules: []v1beta1.SequencingRule{
{
Sequence: []resource.Name{
"first-*",
"first-.*",
"second",
},
},
Expand Down Expand Up @@ -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 \"second\" because \"first-.*\" is not fully ready (2 of 3)",
},
},
Desired: &fnv1beta1.State{
Expand Down Expand Up @@ -629,8 +629,8 @@ func TestRunFunction(t *testing.T) {
Rules: []v1beta1.SequencingRule{
{
Sequence: []resource.Name{
"first-*",
"second-*",
"first-.*",
"second-.*",
"third",
},
},
Expand Down Expand Up @@ -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 \"third\" because \"second-.*\" is not fully ready (1 of 2)",
},
},
Desired: &fnv1beta1.State{
Expand Down Expand Up @@ -720,7 +720,7 @@ func TestRunFunction(t *testing.T) {
{
Sequence: []resource.Name{
"first",
"second-*",
"second-.*",
"third",
},
},
Expand Down Expand Up @@ -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 \"third\" because \"second-.*\" is not fully ready (1 of 2)",
},
},
Desired: &fnv1beta1.State{
Expand All @@ -785,6 +785,158 @@ func TestRunFunction(t *testing.T) {
},
},
},
"SequenceRegexAlreadyPrefixed": {
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-.*$",
"fourth",
},
},
},
}),
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_TRUE,
},
"second-0": {
Resource: resource.MustStructJSON(mr),
Ready: fnv1beta1.Ready_READY_TRUE,
},
"third-0": {
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 \"fourth\" because \"third-.*$\" is not fully ready (0 of 1)",
},
},
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_TRUE,
},
"second-0": {
Resource: resource.MustStructJSON(mr),
Ready: fnv1beta1.Ready_READY_TRUE,
},
"third-0": {
Resource: resource.MustStructJSON(mr),
},
},
},
},
},
},
"SequenceRegexInvalidRegex": {
reason: "The function should return a fatal error because the regex is invalid",
args: args{
req: &fnv1beta1.RunFunctionRequest{
Input: resource.MustStructObject(&v1beta1.Input{
Rules: []v1beta1.SequencingRule{
{
Sequence: []resource.Name{
`^(`,
"second",
},
},
},
}),
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_TRUE,
},
"second": {
Resource: resource.MustStructJSON(mr),
Ready: fnv1beta1.Ready_READY_TRUE,
},
},
},
},
},
want: want{
rsp: &fnv1beta1.RunFunctionResponse{
Meta: &fnv1beta1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)},
Results: []*fnv1beta1.Result{
{
Severity: fnv1beta1.Severity_SEVERITY_FATAL,
Message: "cannot compile regex ^(: error parsing regexp: missing closing ): `^(`",
},
},
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_TRUE,
},
"second": {
Resource: resource.MustStructJSON(mr),
Ready: fnv1beta1.Ready_READY_TRUE,
},
},
},
},
},
},
}

for name, tc := range cases {
Expand Down

0 comments on commit 4ca0f46

Please sign in to comment.