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

Commit

Permalink
Whitelist label ts using registry cache decorator
Browse files Browse the repository at this point in the history
We did not take the inheritance of labels from base images into account
while developing the timestamps from image labels feature. As a result,
users started experiencing issues with automated image updates, due to
the (moderately old) timestamp from the base image being picked up and
prioritized over the timestamp from the registry.

This commits adds the ability to decorate `ImageRepository` structures
before they are returned from cache by `GetImageRepositoryMetadata`.

The sole decorator for now is `TimestampLabelWhitelist`, which replaces
the `CreatedAt` field on the image info with the timestamp specified in
one of the labels if the canonical name of the image matches one of
the glob patterns on the whitelist.

As a result, the labels from images will only be used if explicitly
instructed by the user, dealing with the problem described at the top
of this comment.
  • Loading branch information
hiddeco committed Jul 10, 2019
1 parent 2a39f87 commit 3ba00ae
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 2 deletions.
4 changes: 4 additions & 0 deletions cmd/fluxd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func main() {
registryTrace = fs.Bool("registry-trace", false, "output trace of image registry requests to log")
registryInsecure = fs.StringSlice("registry-insecure-host", []string{}, "let these registry hosts skip TLS host verification and fall back to using HTTP instead of HTTPS; this allows man-in-the-middle attacks, so use with extreme caution")
registryExcludeImage = fs.StringSlice("registry-exclude-image", []string{"k8s.gcr.io/*"}, "do not scan images that match these glob expressions; the default is to exclude the 'k8s.gcr.io/*' images")
registryUseLabels = fs.StringSlice("registry-use-labels", []string{"index.docker.io/weaveworks/*"}, "use the timestamp (RFC3339) from labels for (canonical) image refs that match these glob expression")

// AWS authentication
registryAWSRegions = fs.StringSlice("registry-ecr-region", nil, "include just these AWS regions when scanning images in ECR; when not supplied, the cluster's region will included if it can be detected through the AWS API")
Expand Down Expand Up @@ -498,6 +499,9 @@ func main() {

cacheRegistry = &cache.Cache{
Reader: cacheClient,
Decorators: []cache.Decorator{
cache.TimestampLabelWhitelist(*registryUseLabels),
},
}
cacheRegistry = registry.NewInstrumentedRegistry(cacheRegistry)

Expand Down
2 changes: 1 addition & 1 deletion registry/cache/memcached/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestWarming_WarmerWriteCacheRead(t *testing.T) {
Trace: true,
}

r := &cache.Cache{mc}
r := &cache.Cache{Reader: mc}

w, _ := cache.NewWarmer(remote, mc, 125)
shutdown := make(chan struct{})
Expand Down
43 changes: 42 additions & 1 deletion registry/cache/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/pkg/errors"
"github.com/ryanuber/go-glob"

fluxerr "github.com/weaveworks/flux/errors"
"github.com/weaveworks/flux/image"
Expand All @@ -28,7 +29,42 @@ repository.

// Cache is a local cache of image metadata.
type Cache struct {
Reader Reader
Reader Reader
Decorators []Decorator
}

// Decorator is for decorating an ImageRepository before it is returned.
type Decorator interface {
apply(*ImageRepository)
}

// TimestampLabelWhitelist contains a string slice of glob patterns. Any
// canonical image reference that matches one of the glob patterns will
// prefer creation timestamps from labels over the one it received from
// the registry.
type TimestampLabelWhitelist []string

// apply checks if any of the canonical image references from the
// repository matches a glob pattern from the list. If it does, and the
// image record has a valid timestamp label, it will replace the Created
// field with the value from the label.
func (l TimestampLabelWhitelist) apply(r *ImageRepository) {
for k, i := range r.Images {
for _, exp := range l {
if !glob.Glob(exp, i.ID.CanonicalName().String()) {
continue
}

switch {
case !i.Labels.Created.IsZero():
i.CreatedAt = i.Labels.Created
case !i.Labels.BuildDate.IsZero():
i.CreatedAt = i.Labels.BuildDate
}
r.Images[k] = i
break
}
}
}

// GetImageRepositoryMetadata returns the metadata from an image
Expand All @@ -53,6 +89,11 @@ func (c *Cache) GetImageRepositoryMetadata(id image.Name) (image.RepositoryMetad
return image.RepositoryMetadata{}, ErrNotCached
}

// (Maybe) decorate the image repository.
for _, d := range c.Decorators {
d.apply(&repo)
}

return repo.RepositoryMetadata, nil
}

Expand Down
77 changes: 77 additions & 0 deletions registry/cache/registry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cache

import (
"encoding/json"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/weaveworks/flux/image"
)

// mockStorage holds a fixed ImageRepository item.
type mockStorage struct {
Item ImageRepository
}

// GetKey will always return the same item from the storage,
// and does not care about the key it receives.
func (m *mockStorage) GetKey(k Keyer) ([]byte, time.Time, error) {
b, err := json.Marshal(m.Item)
if err != nil {
return []byte{}, time.Time{}, err
}
return b, time.Time{}, nil
}

// appendImage adds an image to the mocked storage item.
func (m *mockStorage) appendImage(i image.Info) {
tag := i.ID.Tag

m.Item.Images[tag] = i
m.Item.Tags = append(m.Item.Tags, tag)
}

func mockReader() *mockStorage {
return &mockStorage{
Item: ImageRepository{
RepositoryMetadata: image.RepositoryMetadata{
Tags: []string{},
Images: map[string]image.Info{},
},
LastUpdate: time.Now(),
},
}
}

func Test_WhitelabelDecorator(t *testing.T) {
r := mockReader()

// Image with no timestamp label
r.appendImage(mustMakeInfo("docker.io/fluxcd/flux:equal", time.Time{}, time.Now().UTC()))
// Image with a timestamp label
r.appendImage(mustMakeInfo("docker.io/fluxcd/flux:label", time.Now().Add(-10*time.Second).UTC(), time.Now().UTC()))
// Image with the wrong canonical ref
r.appendImage(mustMakeInfo("docker.io/a/different:canonical", time.Now().Add(-10*time.Second).UTC(), time.Now().UTC()))

c := Cache{r, []Decorator{TimestampLabelWhitelist{"index.docker.io/fluxcd/*"}}}

rm, err := c.GetImageRepositoryMetadata(image.Name{})
assert.NoError(t, err)

assert.Equal(t, r.Item.Images["equal"].CreatedAt, rm.Images["equal"].CreatedAt)
assert.Equal(t, r.Item.Images["label"].Labels.Created, rm.Images["label"].CreatedAt)
assert.Equal(t, r.Item.Images["canonical"].CreatedAt, rm.Images["canonical"].CreatedAt)
}

func mustMakeInfo(ref string, label time.Time, created time.Time) image.Info {
r, err := image.ParseRef(ref)
if err != nil {
panic(err)
}
var labels image.Labels
if !label.IsZero() {
labels.Created = label
}
return image.Info{ID: r, Labels: labels, CreatedAt: created}
}

0 comments on commit 3ba00ae

Please sign in to comment.