-
Notifications
You must be signed in to change notification settings - Fork 208
Add text regex support for Event watcher #2347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2812689
b7becc5
07dd0b1
c6213a8
18318f5
7441e9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,6 +24,7 @@ import ( | |
| "io/ioutil" | ||
| "os" | ||
| "path/filepath" | ||
| "regexp/syntax" | ||
| "strconv" | ||
| "sync" | ||
| "time" | ||
|
|
@@ -33,6 +34,7 @@ import ( | |
| "github.com/pipe-cd/pipe/pkg/config" | ||
| "github.com/pipe-cd/pipe/pkg/git" | ||
| "github.com/pipe-cd/pipe/pkg/model" | ||
| "github.com/pipe-cd/pipe/pkg/regexpool" | ||
| "github.com/pipe-cd/pipe/pkg/yamlprocessor" | ||
| ) | ||
|
|
||
|
|
@@ -239,6 +241,8 @@ func (w *watcher) commitFiles(ctx context.Context, eventCfg config.EventWatcherE | |
| // TODO: Empower Event watcher to parse JSON format | ||
| case r.HCLField != "": | ||
| // TODO: Empower Event watcher to parse HCL format | ||
| case r.Regex != "": | ||
| newContent, upToDate, err = modifyText(path, r.Regex, latestEvent.Data) | ||
| } | ||
| if err != nil { | ||
| return err | ||
|
|
@@ -269,7 +273,7 @@ func (w *watcher) commitFiles(ctx context.Context, eventCfg config.EventWatcherE | |
| // modifyYAML returns a new YAML content as a first returned value if the value of given | ||
| // field was outdated. True as a second returned value means it's already up-to-date. | ||
| func modifyYAML(path, field, newValue string) ([]byte, bool, error) { | ||
| yml, err := ioutil.ReadFile(path) | ||
| yml, err := os.ReadFile(path) | ||
| if err != nil { | ||
| return nil, false, fmt.Errorf("failed to read file: %w", err) | ||
| } | ||
|
|
@@ -320,3 +324,55 @@ func convertStr(value interface{}) (out string, err error) { | |
| } | ||
| return | ||
| } | ||
|
|
||
| // modifyText returns a new text replacing all matches of the given regex with the newValue. | ||
| // The only first capturing group enclosed by `()` will be replaced. | ||
| // True as a second returned value means it's already up-to-date. | ||
| func modifyText(path, regexText, newValue string) ([]byte, bool, error) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a comment to show the meaning of each returned value?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's more helpful 👍 |
||
| content, err := os.ReadFile(path) | ||
| if err != nil { | ||
| return nil, false, fmt.Errorf("failed to read file: %w", err) | ||
| } | ||
|
|
||
| pool := regexpool.DefaultPool() | ||
| regex, err := pool.Get(regexText) | ||
| if err != nil { | ||
| return nil, false, fmt.Errorf("failed to compile regex text (%s): %w", regexText, err) | ||
| } | ||
|
|
||
| // Extract the first capturing group. | ||
| firstGroup := "" | ||
| re, err := syntax.Parse(regexText, syntax.Perl) | ||
| if err != nil { | ||
| return nil, false, fmt.Errorf("failed to parse the first capturing group regex: %w", err) | ||
| } | ||
| for _, s := range re.Sub { | ||
| if s.Op == syntax.OpCapture { | ||
| firstGroup = s.String() | ||
| break | ||
| } | ||
| } | ||
| if firstGroup == "" { | ||
| return nil, false, fmt.Errorf("capturing group not found in the given regex") | ||
| } | ||
| subRegex, err := pool.Get(firstGroup) | ||
| if err != nil { | ||
| return nil, false, fmt.Errorf("failed to compile the first capturing group: %w", err) | ||
| } | ||
|
|
||
| var touched, outDated bool | ||
| newText := regex.ReplaceAllFunc(content, func(match []byte) []byte { | ||
| touched = true | ||
| outDated = string(subRegex.Find(match)) != newValue | ||
| // Return text replacing the only first capturing group with the newValue. | ||
| return subRegex.ReplaceAll(match, []byte(newValue)) | ||
| }) | ||
| if !touched { | ||
| return nil, false, fmt.Errorf("the content of %s doesn't match %s", path, regexText) | ||
| } | ||
| if !outDated { | ||
| return nil, true, nil | ||
| } | ||
|
|
||
| return newText, false, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,3 +117,122 @@ func TestModifyYAML(t *testing.T) { | |
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestModifyText(t *testing.T) { | ||
| testcases := []struct { | ||
| name string | ||
| path string | ||
| regex string | ||
| newValue string | ||
| want []byte | ||
| wantUpToDate bool | ||
| wantErr bool | ||
| }{ | ||
| { | ||
| name: "invalid regex given", | ||
| path: "testdata/with-template.yaml", | ||
| regex: "[", | ||
| newValue: "v0.2.0", | ||
| want: nil, | ||
| wantUpToDate: false, | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "no capturing group given", | ||
| path: "testdata/with-template.yaml", | ||
| regex: "image: gcr.io/pipecd/foo:v[0-9].[0-9].[0-9]", | ||
| newValue: "v0.2.0", | ||
| want: nil, | ||
| wantUpToDate: false, | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "invalid capturing group given", | ||
| path: "testdata/with-template.yaml", | ||
| regex: "image: gcr.io/pipecd/foo:([)", | ||
| newValue: "v0.2.0", | ||
| want: nil, | ||
| wantUpToDate: false, | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "the file doesn't match regex", | ||
| path: "testdata/with-template.yaml", | ||
| regex: "abcdefg", | ||
| newValue: "v0.1.0", | ||
| want: nil, | ||
| wantUpToDate: false, | ||
| wantErr: true, | ||
| }, | ||
| { | ||
| name: "the file is up-to-date", | ||
| path: "testdata/with-template.yaml", | ||
| regex: "image: gcr.io/pipecd/foo:(v[0-9].[0-9].[0-9])", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a case that captures/replaces the whole tag url? |
||
| newValue: "v0.1.0", | ||
| want: nil, | ||
| wantUpToDate: true, | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "replace a part of text", | ||
| path: "testdata/with-template.yaml", | ||
| regex: "image: gcr.io/pipecd/foo:(v[0-9].[0-9].[0-9])", | ||
| newValue: "v0.2.0", | ||
| want: []byte(`apiVersion: apps/v1 | ||
| kind: Deployment | ||
| metadata: | ||
| name: foo | ||
| spec: | ||
| template: | ||
| spec: | ||
| containers: | ||
| - name: foo | ||
| image: gcr.io/pipecd/foo:v0.2.0 | ||
| ports: | ||
| - containerPort: 9085 | ||
| env: | ||
| - name: FOO | ||
| value: {{ .encryptedSecrets.foo }} | ||
|
|
||
| --- | ||
| apiVersion: apps/v1 | ||
| kind: Deployment | ||
| metadata: | ||
| name: bar | ||
| spec: | ||
| template: | ||
| spec: | ||
| containers: | ||
| - name: bar | ||
| image: gcr.io/pipecd/bar:v0.1.0 | ||
| ports: | ||
| - containerPort: 9085 | ||
| env: | ||
| - name: BAR | ||
| value: {{ .encryptedSecrets.bar }} | ||
| `), | ||
| wantUpToDate: false, | ||
| wantErr: false, | ||
| }, | ||
| { | ||
| name: "replace text", | ||
| path: "testdata/kustomization.yaml", | ||
| regex: "newTag: (v[0-9].[0-9].[0-9])", | ||
| newValue: "v0.2.0", | ||
| want: []byte(`images: | ||
| - name: gcr.io/pipecd/foo | ||
| newTag: v0.2.0 | ||
| `), | ||
| wantUpToDate: false, | ||
| wantErr: false, | ||
| }, | ||
| } | ||
| for _, tc := range testcases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| got, gotUpToDate, err := modifyText(tc.path, tc.regex, tc.newValue) | ||
| assert.Equal(t, tc.wantErr, err != nil) | ||
| assert.Equal(t, tc.want, got) | ||
| assert.Equal(t, tc.wantUpToDate, gotUpToDate) | ||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| images: | ||
| - name: gcr.io/pipecd/foo | ||
| newTag: v0.1.0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| apiVersion: apps/v1 | ||
| kind: Deployment | ||
| metadata: | ||
| name: foo | ||
| spec: | ||
| template: | ||
| spec: | ||
| containers: | ||
| - name: foo | ||
| image: gcr.io/pipecd/foo:v0.1.0 | ||
| ports: | ||
| - containerPort: 9085 | ||
| env: | ||
| - name: FOO | ||
| value: {{ .encryptedSecrets.foo }} | ||
|
|
||
| --- | ||
| apiVersion: apps/v1 | ||
| kind: Deployment | ||
| metadata: | ||
| name: bar | ||
| spec: | ||
| template: | ||
| spec: | ||
| containers: | ||
| - name: bar | ||
| image: gcr.io/pipecd/bar:v0.1.0 | ||
| ports: | ||
| - containerPort: 9085 | ||
| env: | ||
| - name: BAR | ||
| value: {{ .encryptedSecrets.bar }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nits, should we add a comment for this just as
modifyYAMLfunction has? It's a nits and it's up to you 🙆♀️