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

Commit

Permalink
Support semver in container filter tag
Browse files Browse the repository at this point in the history
Currently, containers can be tagged in manifests to filter what image
tags are considered when doing automated releases. Filtering is done by
specifying a wildcard glob. An optional prefix `glob:` can be used.

This PR adds support for tag filters based on [semantic versioning][0]
by using the prefix `semver:` instead. Version constraints can be
specified that filter images. Since versions have an implicit ordering
this also changes the way images are sorted when trying to determine the
newest image. For glob filtering this falls back to image creation date.

[0]: https://semver.org
  • Loading branch information
rndstr committed Aug 1, 2018
1 parent 22be705 commit 54caaba
Show file tree
Hide file tree
Showing 27 changed files with 590 additions and 106 deletions.
4 changes: 2 additions & 2 deletions Gopkg.toml
Expand Up @@ -47,5 +47,5 @@ required = ["k8s.io/code-generator/cmd/client-gen"]
version = "v1.0.0"

[[constraint]]
branch = "master"
name = "github.com/pkg/term"
name = "github.com/Masterminds/semver"
version = "1.4.0"
9 changes: 6 additions & 3 deletions api/v6/container.go
Expand Up @@ -3,6 +3,7 @@ package v6
import (
"github.com/pkg/errors"
"github.com/weaveworks/flux/image"
"github.com/weaveworks/flux/policy"
"github.com/weaveworks/flux/registry"
"github.com/weaveworks/flux/update"
)
Expand All @@ -26,7 +27,9 @@ type Container struct {
}

// NewContainer creates a Container given a list of images and the current image
func NewContainer(name string, images update.ImageInfos, currentImage image.Info, tagPattern string, fields []string) (Container, error) {
func NewContainer(name string, images update.ImageInfos, currentImage image.Info, tagPattern policy.Pattern, fields []string) (Container, error) {
images.Sort(tagPattern)

// All images
imagesCount := len(images)
imagesErr := ""
Expand All @@ -35,7 +38,7 @@ func NewContainer(name string, images update.ImageInfos, currentImage image.Info
}
var newImages []image.Info
for _, img := range images {
if img.CreatedAt.After(currentImage.CreatedAt) {
if tagPattern.ImageNewerFunc()(&img, &currentImage) {
newImages = append(newImages, img)
}
}
Expand All @@ -46,7 +49,7 @@ func NewContainer(name string, images update.ImageInfos, currentImage image.Info
filteredImagesCount := len(filteredImages)
var newFilteredImages []image.Info
for _, img := range filteredImages {
if img.CreatedAt.After(currentImage.CreatedAt) {
if tagPattern.ImageNewerFunc()(&img, &currentImage) {
newFilteredImages = append(newFilteredImages, img)
}
}
Expand Down
40 changes: 31 additions & 9 deletions api/v6/container_test.go
Expand Up @@ -4,19 +4,26 @@ import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"

"github.com/weaveworks/flux/image"
"github.com/weaveworks/flux/policy"
"github.com/weaveworks/flux/update"
)

func TestNewContainer(t *testing.T) {

testImage := image.Info{ImageID: "test"}

currentSemver := image.Info{ID: image.Ref{Tag: "1.0.0"}}
oldSemver := image.Info{ID: image.Ref{Tag: "0.9.0"}}
newSemver := image.Info{ID: image.Ref{Tag: "1.2.3"}}

type args struct {
name string
images update.ImageInfos
currentImage image.Info
tagPattern string
tagPattern policy.Pattern
fields []string
}
tests := []struct {
Expand All @@ -31,7 +38,7 @@ func TestNewContainer(t *testing.T) {
name: "container1",
images: update.ImageInfos{testImage},
currentImage: testImage,
tagPattern: "*",
tagPattern: policy.PatternAll,
},
want: Container{
Name: "container1",
Expand All @@ -45,17 +52,32 @@ func TestNewContainer(t *testing.T) {
},
wantErr: false,
},
{
name: "Semver filtering and sorting",
args: args{
name: "container-semver",
images: update.ImageInfos{currentSemver, newSemver, oldSemver, testImage},
currentImage: currentSemver,
tagPattern: policy.NewPattern("semver:*"),
},
want: Container{
Name: "container-semver",
Current: currentSemver,
LatestFiltered: newSemver,
Available: []image.Info{newSemver, currentSemver, oldSemver, testImage},
AvailableImagesCount: 4,
NewAvailableImagesCount: 1,
FilteredImagesCount: 3,
NewFilteredImagesCount: 1,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewContainer(tt.args.name, tt.args.images, tt.args.currentImage, tt.args.tagPattern, tt.args.fields)
if (err != nil) != tt.wantErr {
t.Errorf("NewContainer() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewContainer() = %v, want %v", got, tt.want)
}
assert.Equal(t, tt.wantErr, err != nil)
assert.Equal(t, tt.want, got)
})
}
}
Expand Down
9 changes: 6 additions & 3 deletions cluster/kubernetes/policies.go
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"

"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"

"github.com/weaveworks/flux"
kresource "github.com/weaveworks/flux/cluster/kubernetes/resource"
Expand All @@ -27,16 +27,19 @@ func (m *Manifests) UpdatePolicies(def []byte, id flux.ResourceID, update policy
}

for _, container := range containers {
if tagAll == "glob:*" {
if tagAll == policy.PatternAll.String() {
del = del.Add(policy.TagPrefix(container.Name))
} else {
add = add.Set(policy.TagPrefix(container.Name), tagAll)
}
}
}

args := []string{}
var args []string
for pol, val := range add {
if policy.Tag(pol) && !policy.NewPattern(val).Valid() {
return nil, fmt.Errorf("invalid tag pattern: %q", val)
}
args = append(args, fmt.Sprintf("%s%s=%s", kresource.PolicyPrefix, pol, val))
}
for pol, _ := range del {
Expand Down
64 changes: 55 additions & 9 deletions cluster/kubernetes/policies_test.go
Expand Up @@ -5,6 +5,8 @@ import (
"testing"
"text/template"

"github.com/stretchr/testify/assert"

"github.com/weaveworks/flux"
"github.com/weaveworks/flux/policy"
)
Expand All @@ -14,6 +16,7 @@ func TestUpdatePolicies(t *testing.T) {
name string
in, out []string
update policy.Update
wantErr bool
}{
{
name: "adding annotation with others existing",
Expand Down Expand Up @@ -113,17 +116,60 @@ func TestUpdatePolicies(t *testing.T) {
Remove: policy.Set{policy.LockedMsg: "foo"},
},
},
{
name: "add tag policy",
in: nil,
out: []string{"flux.weave.works/tag.nginx", "glob:*"},
update: policy.Update{
Add: policy.Set{policy.TagPrefix("nginx"): "glob:*"},
},
},
{
name: "add non-glob tag policy",
in: nil,
out: []string{"flux.weave.works/tag.nginx", "foo"},
update: policy.Update{
Add: policy.Set{policy.TagPrefix("nginx"): "foo"},
},
},
{
name: "add semver tag policy",
in: nil,
out: []string{"flux.weave.works/tag.nginx", "semver:*"},
update: policy.Update{
Add: policy.Set{policy.TagPrefix("nginx"): "semver:*"},
},
},
{
name: "add invalid semver tag policy",
in: nil,
out: []string{"flux.weave.works/tag.nginx", "semver:*"},
update: policy.Update{
Add: policy.Set{policy.TagPrefix("nginx"): "semver:invalid"},
},
wantErr: true,
},
} {
caseIn := templToString(t, annotationsTemplate, c.in)
caseOut := templToString(t, annotationsTemplate, c.out)
resourceID := flux.MustParseResourceID("default:deployment/nginx")
out, err := (&Manifests{}).UpdatePolicies([]byte(caseIn), resourceID, c.update)
if err != nil {
t.Errorf("[%s] %v", c.name, err)
} else if string(out) != caseOut {
t.Errorf("[%s] Did not get expected result:\n\n%s\n\nInstead got:\n\n%s", c.name, caseOut, string(out))
}
t.Run(c.name, func(t *testing.T) {
caseIn := templToString(t, annotationsTemplate, c.in)
caseOut := templToString(t, annotationsTemplate, c.out)
resourceID := flux.MustParseResourceID("default:deployment/nginx")
out, err := (&Manifests{}).UpdatePolicies([]byte(caseIn), resourceID, c.update)
assert.Equal(t, c.wantErr, err != nil)
if !c.wantErr {
assert.Equal(t, string(out), caseOut)
}
})
}
}

func TestUpdatePolicies_invalidTagPattern(t *testing.T) {
resourceID := flux.MustParseResourceID("default:deployment/nginx")
update := policy.Update{
Add: policy.Set{policy.TagPrefix("nginx"): "semver:invalid"},
}
_, err := (&Manifests{}).UpdatePolicies(nil, resourceID, update)
assert.Error(t, err)
}

var annotationsTemplate = template.Must(template.New("").Parse(`---
Expand Down
27 changes: 27 additions & 0 deletions cluster/kubernetes/testfiles/data.go
Expand Up @@ -52,6 +52,7 @@ var ResourceMap = map[flux.ResourceID]string{
flux.MustParseResourceID("default:service/multi-service"): "multi.yaml",
flux.MustParseResourceID("default:deployment/list-deploy"): "list.yaml",
flux.MustParseResourceID("default:service/list-service"): "list.yaml",
flux.MustParseResourceID("default:deployment/semver"): "semver-deploy.yaml",
}

// ServiceMap ... given a base path, construct the map representing
Expand All @@ -64,6 +65,7 @@ func ServiceMap(dir string) map[flux.ResourceID][]string {
flux.MustParseResourceID("default:deployment/test-service"): []string{filepath.Join(dir, "test/test-service-deploy.yaml")},
flux.MustParseResourceID("default:deployment/multi-deploy"): []string{filepath.Join(dir, "multi.yaml")},
flux.MustParseResourceID("default:deployment/list-deploy"): []string{filepath.Join(dir, "list.yaml")},
flux.MustParseResourceID("default:deployment/semver"): []string{filepath.Join(dir, "semver-deploy.yaml")},
}
}

Expand Down Expand Up @@ -96,6 +98,31 @@ spec:
- -addr=:8080
ports:
- containerPort: 8080
`,
// Automated deployment with semver enabled
"semver-deploy.yaml": `---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: semver
annotations:
flux.weave.works/automated: "true"
flux.weave.works/tag.greeter: semver:*
spec:
minReadySeconds: 1
replicas: 5
template:
metadata:
labels:
name: semver
spec:
containers:
- name: greeter
image: quay.io/weaveworks/helloworld:master-a000001
args:
- -msg=Ahoy
ports:
- containerPort: 80
`,
"locked-service-deploy.yaml": `apiVersion: extensions/v1beta1
kind: Deployment
Expand Down
4 changes: 2 additions & 2 deletions cmd/fluxctl/policy_cmd.go
Expand Up @@ -142,7 +142,7 @@ func calculatePolicyChanges(opts *controllerPolicyOpts) (policy.Update, error) {
Add(policy.LockedUser)
}
if opts.tagAll != "" {
add = add.Set(policy.TagAll, "glob:"+opts.tagAll)
add = add.Set(policy.TagAll, policy.PatternAll.String())
}

for _, tagPair := range opts.tags {
Expand All @@ -153,7 +153,7 @@ func calculatePolicyChanges(opts *controllerPolicyOpts) (policy.Update, error) {

container, tag := parts[0], parts[1]
if tag != "*" {
add = add.Set(policy.TagPrefix(container), "glob:"+tag)
add = add.Set(policy.TagPrefix(container), policy.NewPattern(tag).String())
} else {
remove = remove.Add(policy.TagPrefix(container))
}
Expand Down
21 changes: 14 additions & 7 deletions daemon/daemon.go
Expand Up @@ -134,14 +134,21 @@ func (d *Daemon) ListServices(ctx context.Context, namespace string) ([]v6.Contr
return res, nil
}

type clusterContainers []cluster.Controller
type clusterContainers struct {
controllers []cluster.Controller
policies policy.ResourceMap
}

func (cs clusterContainers) Len() int {
return len(cs)
return len(cs.controllers)
}

func (cs clusterContainers) Containers(i int) []resource.Container {
return cs[i].ContainersOrNil()
return cs.controllers[i].ContainersOrNil()
}

func (cs clusterContainers) Pattern(i int, container string) policy.Pattern {
return policy.GetTagPattern(cs.policies, cs.controllers[i].ID, container)
}

// ListImages - deprecated from v10, lists the images available for set of services
Expand All @@ -163,14 +170,14 @@ func (d *Daemon) ListImagesWithOptions(ctx context.Context, opts v10.ListImagesO
services, err = d.Cluster.SomeControllers([]flux.ResourceID{id})
}

imageRepos, err := update.FetchImageRepos(d.Registry, clusterContainers(services), d.Logger)
policyResourceMap, _, err := d.getPolicyResourceMap(ctx)
if err != nil {
return nil, errors.Wrap(err, "getting images for services")
return nil, err
}

policyResourceMap, _, err := d.getPolicyResourceMap(ctx)
imageRepos, err := update.FetchImageRepos(d.Registry, clusterContainers{controllers: services, policies: policyResourceMap}, d.Logger)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "getting images for services")
}

var res []v6.ImageStatus
Expand Down

0 comments on commit 54caaba

Please sign in to comment.