Skip to content

Commit

Permalink
Add regexpReplace provisioner
Browse files Browse the repository at this point in the history
  • Loading branch information
mumoshu committed Dec 15, 2019
1 parent f2a660c commit 56a806a
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 11 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,35 @@ Navigate to the following examples to see practical usage of `mod`:

## API Reference

## `regexpReplace` provisioner

`regexpReplace` updates any text file like Dockerfile with regular expressions.

Let's say you want to automate updating the base image of the below Dockerfile:

```
FROM helmfile:0.94.0
RUN echo hello
```

You can write a `variant.mod` file like the below so that `mod` knows where is the image tag to be updated:

```yaml
provisioners:
regexpReplace:
Dockerfile:
from: "(FROM helmfile:)(\\S+)(\\s+)"
to: "${1}{{.Dependencies.helmfile.version}}${3}"

dependencies:
helmfile:
releasesFrom:
dockerImageTags:
source: quay.io/roboll/helmfile
version: "> 0.94.0"
```
### `docker` executable provisioner

Setting `provisioners.executables.NAME.platforms[].docker` allows you to run `mod exec -- NAME $args` where the executable is backed by a docker image which is managed by `mod`.
Expand Down
71 changes: 66 additions & 5 deletions pkg/variantmod/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"k8s.io/klog/klogr"
"os"
"path/filepath"
"regexp"
"strings"
)

Expand All @@ -45,10 +46,11 @@ type ParametersSpec struct {
}

type ProvisionersSpec struct {
Files map[string]FileSpec `yaml:"files"`
Executables execversionmanager.Config `yaml:",inline"`
TextReplace map[string]TextReplaceSpec `yaml:"textReplace"`
YamlPatch map[string][]YamlPatchSpec `yaml:"yamlPatch"`
Files map[string]FileSpec `yaml:"files"`
Executables execversionmanager.Config `yaml:",inline"`
TextReplace map[string]TextReplaceSpec `yaml:"textReplace"`
RegexpReplace map[string]RegexpReplaceSpec `yaml:"regexpReplace"`
YamlPatch map[string][]YamlPatchSpec `yaml:"yamlPatch"`
}

type FileSpec struct {
Expand All @@ -61,6 +63,11 @@ type TextReplaceSpec struct {
To string `yaml:"to"`
}

type RegexpReplaceSpec struct {
From string `yaml:"from"`
To string `yaml:"to"`
}

type YamlPatchSpec struct {
Op string `yaml:"op"`
Path string `yaml:"path"`
Expand Down Expand Up @@ -492,6 +499,16 @@ func (m *ModuleManager) load(depspec DependencySpec) (mod *Module, err error) {
files = append(files, f)
}

regexpReplaces := []RegexpReplace{}
for path, tspec := range spec.Provisioners.RegexpReplace {
t := RegexpReplace{
Path: path,
From: tspec.From,
To: tspec.To,
}
regexpReplaces = append(regexpReplaces, t)
}

textReplaces := []TextReplace{}
for path, tspec := range spec.Provisioners.TextReplace {
t := TextReplace{
Expand Down Expand Up @@ -554,6 +571,7 @@ func (m *ModuleManager) load(depspec DependencySpec) (mod *Module, err error) {
Values: vals,
ValuesSchema: schema,
Files: files,
RegexpReplaces: regexpReplaces,
TextReplaces: textReplaces,
Yamls: yamls,
Executable: execset,
Expand Down Expand Up @@ -670,7 +688,7 @@ func (m *ModuleManager) PullRequest(title, body, base, head string, skipDuplicat
owner := ownerRepo[0]
repo := ownerRepo[1]

b, err:= tmpl.Render("body", body, mod.Values)
b, err := tmpl.Render("body", body, mod.Values)
if err != nil {
return err
}
Expand Down Expand Up @@ -997,6 +1015,49 @@ func (m *ModuleManager) doBuildSingle(mod *Module) (r *BuildResult, err error) {
r.Files = append(r.Files, t.Path)
}

for _, t := range mod.RegexpReplaces {
from, err := regexp.Compile(t.From)
if err != nil {
m.Logger.V(1).Info(err.Error())
return nil, err
}

to, err := tmpl.Render("to", t.To, values)
if err != nil {
m.Logger.V(1).Info(err.Error())
return nil, err
}

to = strings.TrimSpace(to)

path, err := tmpl.Render("path", t.Path, values)
if err != nil {
m.Logger.V(1).Info(err.Error())
return nil, err
}

target := filepath.Join(m.AbsWorkDir, path)
m.Logger.V(1).Info("regexpReplace", "path", target, "from", from, "to", to)
contents, err := m.fs.ReadFile(target)
if err != nil {
m.Logger.V(1).Info(err.Error())
return nil, err
}

res, err := regexpReplace(contents, from, to)
if err != nil {
m.Logger.V(1).Info(err.Error())
return nil, err
}

if err := m.fs.WriteFile(target, res, 0644); err != nil {
m.Logger.V(1).Info(err.Error())
return nil, err
}

r.Files = append(r.Files, t.Path)
}

for _, y := range mod.Yamls {
path, err := tmpl.Render("path", y.Path, values)
if err != nil {
Expand Down
123 changes: 123 additions & 0 deletions pkg/variantmod/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -815,3 +815,126 @@ nodeGroups:
}

}


func TestDependencyLockinge_Dockerfile_RegexpReplace(t *testing.T) {
if testing.Verbose() {
}

files := map[string]interface{}{
"/path/to/variant.mod": `
name: myapp
provisioners:
regexpReplace:
Dockerfile:
from: "(FROM helmfile:)(\\S+)(\\s+)"
to: "${1}{{.Dependencies.helmfile.version}}${3}"
dependencies:
helmfile:
releasesFrom:
exec:
command: go
args:
- run
- main.go
version: "> 0.94.0"
`,
"/path/to/Dockerfile": `FROM helmfile:0.94.0
RUN echo hello
`,
"/path/to/variant.lock": `
dependencies:
helmfile:
version: "0.94.1"
`,
}
fs, clean, err := vfst.NewTestFS(files)
if err != nil {
t.Fatal(err)
}
defer clean()
log := klogr.New()
klog.SetOutput(os.Stderr)
klog.V(2).Info(fmt.Sprintf("temp dir: %v", fs.TempDir()))

expectedInput := cmdsite.NewInput("go", []string{"run", "main.go"}, map[string]string{})
expectedStdout := `0.94.1
0.95.0
`
cmdr := cmdsite.NewTester(map[cmdsite.CommandInput]cmdsite.CommandOutput{
expectedInput: {Stdout: expectedStdout},
})

man, err := New(Logger(log), FS(fs), WD("/path/to"), GoGetterWD(filepath.Join(fs.TempDir(), "path", "to")), Commander(cmdr))
if err != nil {
t.Fatal(err)
}

mod, err := man.Load()
if err != nil {
t.Fatal(err)
}

if _, err := man.doBuild(mod); err != nil {
t.Fatal(err)
}

dockerfile1Expected := `FROM helmfile:0.94.1
RUN echo hello
`
dockerfile1Actual, err := fs.ReadFile("/path/to/Dockerfile")
if err != nil {
t.Fatal(err)
}
if string(dockerfile1Actual) != dockerfile1Expected {
t.Errorf("assertion failed: expected=%s, got=%s", dockerfile1Expected, string(dockerfile1Actual))
}

upMod, err := man.doUp()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if err := man.lock(upMod); err != nil {
t.Fatalf("unexpected error: %v", err)
}

lockActual, err := fs.ReadFile("/path/to/variant.lock")
if err != nil {
t.Fatal(err)
}
lockExpected := `dependencies:
helmfile:
version: 0.95.0
previousVersion: 0.94.1
`
if string(lockActual) != lockExpected {
t.Errorf("assertion failed: expected=%s, got=%s", lockExpected, string(lockActual))
}

mod2, err := man.Load()
if err != nil {
t.Fatal(err)
}

if _, err := man.doBuild(mod2); err != nil {
t.Fatal(err)
}

dockerfile2Expected := `FROM helmfile:0.95.0
RUN echo hello
`
dockerfile2Actual, err := fs.ReadFile("/path/to/Dockerfile")
if err != nil {
t.Fatal(err)
}
if string(dockerfile2Actual) != dockerfile2Expected {
t.Errorf("assertion failed: expected=%s, got=%s", dockerfile2Expected, string(dockerfile2Actual))
}

}
18 changes: 12 additions & 6 deletions pkg/variantmod/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ type Values map[string]interface{}
type Module struct {
Alias string

Values Values
ValuesSchema Values
Files []File
TextReplaces []TextReplace
Yamls []YamlPatch
Values Values
ValuesSchema Values
Files []File
TextReplaces []TextReplace
RegexpReplaces []RegexpReplace
Yamls []YamlPatch

ReleaseChannel *releasetracker.Tracker
Executable *execversionmanager.ExecVM
Expand All @@ -30,7 +31,7 @@ type Module struct {

type ModVersionLock struct {
Dependencies map[string]DepVersionLock `yaml:"dependencies"`
RawLock string `yaml:"-"`
RawLock string `yaml:"-"`
}

type DepVersionLock struct {
Expand Down Expand Up @@ -65,6 +66,11 @@ type TextReplace struct {
From, To string
}

type RegexpReplace struct {
Path string
From, To string
}

type YamlPatch struct {
Path string
Patches []Patch
Expand Down
19 changes: 19 additions & 0 deletions pkg/variantmod/regexp_replace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package variantmod

import (
"regexp"
)

func regexpReplace(source []byte, pat *regexp.Regexp, template string) ([]byte, error) {
var (
cur int
res []byte
)
for _, m := range pat.FindAllSubmatchIndex(source, -1) {
res = append(res, source[cur:m[0]]...)
res = pat.Expand(res, []byte(template), source, m)
cur = m[1]
}
res = append(res, source[cur:]...)
return res, nil
}

0 comments on commit 56a806a

Please sign in to comment.