Skip to content
Closed
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
56 changes: 40 additions & 16 deletions pkg/controller/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"time"

corev1 "k8s.io/api/core/v1"

Expand All @@ -18,10 +19,12 @@ type Checker struct {
}

type Result struct {
CurrentVersion string
LatestVersion string
IsLatest bool
ImageURL string
CurrentVersion string
CurrentTimestamp time.Time
LatestVersion string
LatestTimestamp time.Time
IsLatest bool
ImageURL string
}

func New(search search.Searcher) *Checker {
Expand All @@ -47,12 +50,31 @@ func (c *Checker) Container(ctx context.Context, log *logrus.Entry, pod *corev1.
}

imageURL = c.overrideImageURL(log, imageURL, opts)
timestamp, err := c.getTimestamp(ctx, imageURL, currentTag, currentSHA, opts.UseSHA)
if err != nil {
return nil, err
}

var result *Result
if opts.UseSHA {
return c.handleSHA(ctx, imageURL, statusSHA, opts, usingTag, currentTag)
result, err = c.handleSHA(ctx, imageURL, statusSHA, currentTag, usingTag, opts)
} else {
result, err = c.handleSemver(ctx, imageURL, statusSHA, currentTag, usingSHA, opts)
}
if err != nil {
return result, err
}
result.CurrentTimestamp = timestamp
return result, err
}

return c.handleSemver(ctx, imageURL, statusSHA, currentTag, usingSHA, opts)
func (c *Checker) getTimestamp(ctx context.Context, imageURL string, tag string, sha string, useSHA bool) (time.Time, error) {
currentImage, err := c.search.Image(ctx, imageURL, tag, sha, useSHA)
var timestamp time.Time
if currentImage != nil {
timestamp = currentImage.Timestamp
}
return timestamp, err
}

func (c *Checker) handleLatestOrEmptyTag(log *logrus.Entry, currentTag, currentSHA string, opts *api.Options) {
Expand All @@ -68,7 +90,7 @@ func (c *Checker) overrideImageURL(log *logrus.Entry, imageURL string, opts *api
return imageURL
}

func (c *Checker) handleSHA(ctx context.Context, imageURL, statusSHA string, opts *api.Options, usingTag bool, currentTag string) (*Result, error) {
func (c *Checker) handleSHA(ctx context.Context, imageURL, statusSHA string, currentTag string, usingTag bool, opts *api.Options) (*Result, error) {
result, err := c.isLatestSHA(ctx, imageURL, statusSHA, opts)
if err != nil {
return nil, err
Expand Down Expand Up @@ -98,10 +120,11 @@ func (c *Checker) handleSemver(ctx context.Context, imageURL, statusSHA, current
}

return &Result{
CurrentVersion: currentTag,
LatestVersion: latestVersion,
IsLatest: isLatest,
ImageURL: imageURL,
CurrentVersion: currentTag,
LatestVersion: latestVersion,
LatestTimestamp: latestImage.Timestamp,
IsLatest: isLatest,
ImageURL: imageURL,
}, nil
}

Expand Down Expand Up @@ -168,7 +191,7 @@ func (c *Checker) isLatestSemver(ctx context.Context, imageURL, currentSHA strin
return latestImage, isLatest, nil
}

// isLatestSHA will return the the result of whether the given image is the latest, according to image SHA.
// isLatestSHA will return the result of whether the given image is the latest, according to image SHA.
func (c *Checker) isLatestSHA(ctx context.Context, imageURL, currentSHA string, opts *api.Options) (*Result, error) {
latestImage, err := c.search.LatestImage(ctx, imageURL, opts)
if err != nil {
Expand All @@ -182,10 +205,11 @@ func (c *Checker) isLatestSHA(ctx context.Context, imageURL, currentSHA string,
}

return &Result{
CurrentVersion: currentSHA,
LatestVersion: latestVersion,
IsLatest: isLatest,
ImageURL: imageURL,
CurrentVersion: currentSHA,
LatestVersion: latestVersion,
LatestTimestamp: latestImage.Timestamp,
IsLatest: isLatest,
ImageURL: imageURL,
}, nil
}

Expand Down
36 changes: 32 additions & 4 deletions pkg/controller/checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"reflect"
"testing"

"github.com/aws/smithy-go/time"

"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"

Expand All @@ -19,6 +21,7 @@ func TestContainer(t *testing.T) {
imageURL string
opts *api.Options
searchResp *api.ImageTag
imageResp *api.ImageTag
expResult *Result
}{
"no status sha should return nil, nil": {
Expand All @@ -28,6 +31,29 @@ func TestContainer(t *testing.T) {
searchResp: nil,
expResult: nil,
},
"set timestamps from images": {
statusSHA: "localhost:5000/version-checker@sha:123",
imageURL: "localhost:5000/version-checker:v0.1.0",
opts: new(api.Options),
searchResp: &api.ImageTag{
Tag: "v0.2.0",
SHA: "sha:456",
Timestamp: time.ParseEpochSeconds(7654321),
},
imageResp: &api.ImageTag{
Tag: "v0.1.0",
SHA: "sha:123",
Timestamp: time.ParseEpochSeconds(1234567),
},
expResult: &Result{
CurrentVersion: "v0.1.0",
LatestVersion: "v0.2.0",
ImageURL: "localhost:5000/version-checker",
IsLatest: false,
LatestTimestamp: time.ParseEpochSeconds(7654321),
CurrentTimestamp: time.ParseEpochSeconds(1234567),
},
},
"if v0.2.0 is latest version, but different sha, then not latest": {
statusSHA: "localhost:5000/version-checker@sha:123",
imageURL: "localhost:5000/version-checker:v0.2.0",
Expand Down Expand Up @@ -263,7 +289,9 @@ func TestContainer(t *testing.T) {

for name, test := range tests {
t.Run(name, func(t *testing.T) {
checker := New(search.New().With(test.searchResp, nil))
checker := New(search.New().
WithLatestImage(test.searchResp, nil).
WithImage(test.imageResp, nil))
pod := &corev1.Pod{
Status: corev1.PodStatus{
ContainerStatuses: []corev1.ContainerStatus{
Expand All @@ -285,7 +313,7 @@ func TestContainer(t *testing.T) {
}

if !reflect.DeepEqual(test.expResult, result) {
t.Errorf("got unexpected result, exp=%#+v got=%#+v",
t.Errorf("got unexpected result,\nexp=%#+v\ngot=%#+v",
test.expResult, result)
}
})
Expand Down Expand Up @@ -475,7 +503,7 @@ func TestIsLatestSemver(t *testing.T) {

for name, test := range tests {
t.Run(name, func(t *testing.T) {
checker := New(search.New().With(test.searchResp, nil))
checker := New(search.New().WithLatestImage(test.searchResp, nil))
latestImage, isLatest, err := checker.isLatestSemver(context.TODO(), test.imageURL, test.currentSHA, test.currentImage, nil)
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -530,7 +558,7 @@ func TestIsLatestSHA(t *testing.T) {

for name, test := range tests {
t.Run(name, func(t *testing.T) {
checker := New(search.New().With(test.searchResp, nil))
checker := New(search.New().WithLatestImage(test.searchResp, nil))
result, err := checker.isLatestSHA(context.TODO(), test.imageURL, test.currentSHA, nil)
if err != nil {
t.Fatal(err)
Expand Down
17 changes: 16 additions & 1 deletion pkg/controller/internal/fake/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,33 @@ import (
var _ search.Searcher = &FakeSearch{}

type FakeSearch struct {
imageF func() (*api.ImageTag, error)
latestImageF func() (*api.ImageTag, error)
}

func New() *FakeSearch {
return &FakeSearch{
imageF: func() (*api.ImageTag, error) {
return nil, nil
},
latestImageF: func() (*api.ImageTag, error) {
return nil, nil
},
}
}

func (f *FakeSearch) With(image *api.ImageTag, err error) *FakeSearch {
func (f *FakeSearch) WithImage(image *api.ImageTag, err error) *FakeSearch {
f.imageF = func() (*api.ImageTag, error) {
return image, err
}
return f
}

func (f *FakeSearch) Image(context.Context, string, string, string, bool) (*api.ImageTag, error) {
return f.imageF()
}

func (f *FakeSearch) WithLatestImage(image *api.ImageTag, err error) *FakeSearch {
f.latestImageF = func() (*api.ImageTag, error) {
return image, err
}
Expand Down
21 changes: 19 additions & 2 deletions pkg/controller/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import (
// Searcher is the interface for Search to facilitate testing.
type Searcher interface {
Run(time.Duration)
// Image will get the api.ImageTag given an image URL, tag, sha and whether to only compare by sha.
Image(context.Context, string, string, string, bool) (*api.ImageTag, error)
// LatestImage will get the latest image given an image URL and options.
LatestImage(context.Context, string, *api.Options) (*api.ImageTag, error)
}

Expand Down Expand Up @@ -49,8 +52,22 @@ func (s *Search) Fetch(ctx context.Context, imageURL string, opts *api.Options)
return latestImage, nil
}

// LatestImage will get the latestImage image given an image URL and
// options. If not found in the cache, or is too old, then will do a fresh
func (s *Search) Image(ctx context.Context, imageURL, tag, sha string, mustUseSHA bool) (*api.ImageTag, error) {
refs, err := s.versionGetter.AllTagsFromImage(ctx, imageURL)
if err != nil {
return nil, err
}
for _, ref := range refs {
if ref.SHA == sha && sha != "" || // same sha is always fine
ref.Tag == tag && !mustUseSHA { // otherwise compare the tag, if sha use is not required
return &ref, nil
}
}
return nil, nil
}

// LatestImage will get the latest image given an image URL and options.
// If not found in the cache, or is too old, then will do a fresh
// lookup and commit to the cache.
func (s *Search) LatestImage(ctx context.Context, imageURL string, opts *api.Options) (*api.ImageTag, error) {
hashIndex, err := calculateHashIndex(imageURL, opts)
Expand Down
2 changes: 2 additions & 0 deletions pkg/controller/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"time"

"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -97,6 +98,7 @@ func (c *Controller) checkContainer(ctx context.Context, log *logrus.Entry, pod
container.Name, containerType,
result.ImageURL, result.IsLatest,
result.CurrentVersion, result.LatestVersion,
result.CurrentTimestamp.Format(time.DateOnly),
)

return nil
Expand Down
9 changes: 5 additions & 4 deletions pkg/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func New(log *logrus.Entry) *Metrics {
Help: "Where the container in use is using the latest upstream registry version",
},
[]string{
"namespace", "pod", "container", "container_type", "image", "current_version", "latest_version",
"namespace", "pod", "container", "container_type", "image", "current_version", "latest_version", "last_updated",
},
)

Expand Down Expand Up @@ -87,7 +87,7 @@ func (m *Metrics) Run(servingAddress string) error {
return nil
}

func (m *Metrics) AddImage(namespace, pod, container, containerType, imageURL string, isLatest bool, currentVersion, latestVersion string) {
func (m *Metrics) AddImage(namespace, pod, container, containerType, imageURL string, isLatest bool, currentVersion, latestVersion, lastUpdated string) {
// Remove old image url/version if it exists
m.RemoveImage(namespace, pod, container, containerType)

Expand All @@ -100,7 +100,7 @@ func (m *Metrics) AddImage(namespace, pod, container, containerType, imageURL st
}

m.containerImageVersion.With(
m.buildLabels(namespace, pod, container, containerType, imageURL, currentVersion, latestVersion),
m.buildLabels(namespace, pod, container, containerType, imageURL, currentVersion, latestVersion, lastUpdated),
).Set(isLatestF)

index := m.latestImageIndex(namespace, pod, container, containerType)
Expand Down Expand Up @@ -131,7 +131,7 @@ func (m *Metrics) latestImageIndex(namespace, pod, container, containerType stri
return strings.Join([]string{namespace, pod, container, containerType}, "")
}

func (m *Metrics) buildLabels(namespace, pod, container, containerType, imageURL, currentVersion, latestVersion string) prometheus.Labels {
func (m *Metrics) buildLabels(namespace, pod, container, containerType, imageURL, currentVersion, latestVersion, lastUpdated string) prometheus.Labels {
return prometheus.Labels{
"namespace": namespace,
"pod": pod,
Expand All @@ -140,6 +140,7 @@ func (m *Metrics) buildLabels(namespace, pod, container, containerType, imageURL
"image": imageURL,
"current_version": currentVersion,
"latest_version": latestVersion,
"last_updated": lastUpdated,
}
}

Expand Down
10 changes: 7 additions & 3 deletions pkg/metrics/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@ import (
"github.com/sirupsen/logrus"
)

const (
epoch = "1970-01-01"
)

func TestCache(t *testing.T) {
m := New(logrus.NewEntry(logrus.New()))

for i, typ := range []string{"init", "container"} {
version := fmt.Sprintf("0.1.%d", i)
m.AddImage("namespace", "pod", "container", typ, "url", true, version, version)
m.AddImage("namespace", "pod", "container", typ, "url", true, version, version, epoch)
}

for i, typ := range []string{"init", "container"} {
version := fmt.Sprintf("0.1.%d", i)
mt, _ := m.containerImageVersion.GetMetricWith(m.buildLabels("namespace", "pod", "container", typ, "url", version, version))
mt, _ := m.containerImageVersion.GetMetricWith(m.buildLabels("namespace", "pod", "container", typ, "url", version, version, epoch))
count := testutil.ToFloat64(mt)
if count != 1 {
t.Error("Should have added metric")
Expand All @@ -30,7 +34,7 @@ func TestCache(t *testing.T) {
}
for i, typ := range []string{"init", "container"} {
version := fmt.Sprintf("0.1.%d", i)
mt, _ := m.containerImageVersion.GetMetricWith(m.buildLabels("namespace", "pod", "container", typ, "url", version, version))
mt, _ := m.containerImageVersion.GetMetricWith(m.buildLabels("namespace", "pod", "container", typ, "url", version, version, epoch))
count := testutil.ToFloat64(mt)
if count != 0 {
t.Error("Should have removed metric")
Expand Down
12 changes: 10 additions & 2 deletions pkg/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,22 @@ func (v *Version) Run(refreshRate time.Duration) {
v.imageCache.StartGarbageCollector(refreshRate)
}

// AllTagsFromImage will return all tags given an imageURL.
func (v *Version) AllTagsFromImage(ctx context.Context, imageURL string) ([]api.ImageTag, error) {
if tagsI, err := v.imageCache.Get(ctx, imageURL, imageURL, nil); err != nil {
return nil, err
} else {
return tagsI.([]api.ImageTag), err
}
}

// LatestTagFromImage will return the latest tag given an imageURL, according
// to the given options.
func (v *Version) LatestTagFromImage(ctx context.Context, imageURL string, opts *api.Options) (*api.ImageTag, error) {
tagsI, err := v.imageCache.Get(ctx, imageURL, imageURL, nil)
tags, err := v.AllTagsFromImage(ctx, imageURL)
if err != nil {
return nil, err
}
tags := tagsI.([]api.ImageTag)

var tag *api.ImageTag

Expand Down