diff --git a/cluster/kubernetes/policies_test.go b/cluster/kubernetes/policies_test.go index f17bba15d..985c49d71 100644 --- a/cluster/kubernetes/policies_test.go +++ b/cluster/kubernetes/policies_test.go @@ -149,6 +149,23 @@ func TestUpdatePolicies(t *testing.T) { }, wantErr: true, }, + { + name: "add regexp tag policy", + in: nil, + out: []string{"flux.weave.works/tag.nginx", "regexp:(.*?)"}, + update: policy.Update{ + Add: policy.Set{policy.TagPrefix("nginx"): "regexp:(.*?)"}, + }, + }, + { + name: "add invalid regexp tag policy", + in: nil, + out: []string{"flux.weave.works/tag.nginx", "regexp:(.*?)"}, + update: policy.Update{ + Add: policy.Set{policy.TagPrefix("nginx"): "regexp:*"}, + }, + wantErr: true, + }, } { t.Run(c.name, func(t *testing.T) { caseIn := templToString(t, annotationsTemplate, c.in) diff --git a/policy/pattern.go b/policy/pattern.go index bebaab494..c89864767 100644 --- a/policy/pattern.go +++ b/policy/pattern.go @@ -5,11 +5,13 @@ import ( "github.com/ryanuber/go-glob" "github.com/weaveworks/flux/image" "strings" + "regexp" ) const ( globPrefix = "glob:" semverPrefix = "semver:" + regexpPrefix = "regexp:" ) var ( @@ -39,17 +41,28 @@ type SemverPattern struct { constraints *semver.Constraints } -// NewPattern instantiates a Pattern according to the prefix -// it finds. The prefix can be either `glob:` (default if omitted) -// or `semver:`. +// RegexpPattern matches by regular expression. +type RegexpPattern struct { + pattern string // pattern without prefix + regexp *regexp.Regexp +} +// NewPattern instantiates a Pattern according to the prefix +// it finds. The prefix can be either `glob:` (default if omitted), +// `semver:` or `regexp:`. func NewPattern(pattern string) Pattern { - if strings.HasPrefix(pattern, semverPrefix) { + switch { + case strings.HasPrefix(pattern, semverPrefix): pattern = strings.TrimPrefix(pattern, semverPrefix) c, _ := semver.NewConstraint(pattern) return SemverPattern{pattern, c} + case strings.HasPrefix(pattern, regexpPrefix): + pattern = strings.TrimPrefix(pattern, regexpPrefix) + r, _ := regexp.Compile(pattern) + return RegexpPattern{pattern, r} + default: + return GlobPattern(strings.TrimPrefix(pattern, globPrefix)) } - return GlobPattern(strings.TrimPrefix(pattern, globPrefix)) } func (g GlobPattern) Matches(tag string) bool { @@ -91,3 +104,23 @@ func (s SemverPattern) Newer(a, b *image.Info) bool { func (s SemverPattern) Valid() bool { return s.constraints != nil } + +func (r RegexpPattern) Matches(tag string) bool { + if r.regexp == nil { + // Invalid regexp match anything + return true + } + return r.regexp.MatchString(tag) +} + +func (r RegexpPattern) String() string { + return regexpPrefix + r.pattern +} + +func (r RegexpPattern) Newer(a, b *image.Info) bool { + return image.NewerByCreated(a, b) +} + +func (r RegexpPattern) Valid() bool { + return r.regexp != nil +} diff --git a/policy/pattern_test.go b/policy/pattern_test.go index 38a9b9a17..35678b42f 100644 --- a/policy/pattern_test.go +++ b/policy/pattern_test.go @@ -86,3 +86,38 @@ func TestSemverPattern_Matches(t *testing.T) { } } } + +func TestRegexpPattern_Matches(t *testing.T) { + for _, tt := range []struct { + name string + pattern string + true []string + false []string + }{ + { + name: "all prefixed", + pattern: "regexp:(.*?)", + true: []string{"", "1", "foo"}, + false: nil, + }, + { + name: "regexp", + pattern: "regexp:^([a-zA-Z]+)$", + true: []string{"foo", "BAR", "fooBAR"}, + false: []string{"1", "foo-1"}, + }, + } { + pattern := NewPattern(tt.pattern) + assert.IsType(t, RegexpPattern{}, pattern) + for _, tag := range tt.true { + t.Run(fmt.Sprintf("%s[%q]", tt.name, tag), func(t *testing.T) { + assert.True(t, pattern.Matches(tag)) + }) + } + for _, tag := range tt.false { + t.Run(fmt.Sprintf("%s[%q]", tt.name, tag), func(t *testing.T) { + assert.False(t, pattern.Matches(tag)) + }) + } + } +} diff --git a/site/using.md b/site/using.md index 8ad4e9ed8..38e13b8ce 100644 --- a/site/using.md +++ b/site/using.md @@ -416,6 +416,20 @@ fluxctl release --controller=default:deployment/helloworld --update-all-images - Please note that automation might immediately undo this. +## Filter pattern types + +Flux currently offers support for `glob`, `semver` and `regexp` based filtering. + +### Glob + +The glob (`*`) filter is the simplest filter Flux supports, a filter can contain +multiple globs: +``` +fluxctl policy --controller=default:deployment/helloworld --tag-all='glob:master-v1.*.*' +``` + +### Semver + If your images use [semantic versioning](https://semver.org) you can filter by image tags that adhere to certain constraints: ``` @@ -430,6 +444,16 @@ fluxctl policy --controller=default:deployment/helloworld --tag-all='semver:*' Using a semver filter will also affect how flux sorts images, so that the higher versions will be considered newer. +### Regexp + +If your images have complex tags you can filter by regular expression: +``` +fluxctl policy --controller=default:deployment/helloworld --tag-all='regexp:^([a-zA-Z]+)$' +``` + +Please bear in mind that if you want to match the whole tag, +you must bookend your pattern with `^` and `$`. + ## Actions triggered through `fluxctl` `fluxctl` provides the following flags for the message and author customization: