Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Image filtering with regular expressions #1292

Merged
merged 3 commits into from
Aug 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions cluster/kubernetes/policies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
43 changes: 38 additions & 5 deletions policy/pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
35 changes: 35 additions & 0 deletions policy/pattern_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
}
}
}
24 changes: 24 additions & 0 deletions site/using.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
```
Expand All @@ -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]+)$'
```

This comment was marked as abuse.

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:
Expand Down