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

Require creation metadata during auto release #1831

Merged
merged 2 commits into from Mar 20, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
80 changes: 40 additions & 40 deletions daemon/images.go
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/pkg/errors"

"github.com/weaveworks/flux"
"github.com/weaveworks/flux/cluster"
"github.com/weaveworks/flux/policy"
"github.com/weaveworks/flux/resource"
"github.com/weaveworks/flux/update"
Expand Down Expand Up @@ -40,46 +41,7 @@ func (d *Daemon) pollForNewImages(logger log.Logger) {
return
}

changes := &update.Automated{}
for _, workload := range workloads {
var p policy.Set
if resource, ok := candidateWorkloads[workload.ID]; ok {
p = resource.Policies()
}
containers:
for _, container := range workload.ContainersOrNil() {
currentImageID := container.Image
pattern := policy.GetTagPattern(p, container.Name)
repo := currentImageID.Name
logger := log.With(logger, "workload", workload.ID, "container", container.Name, "repo", repo, "pattern", pattern, "current", currentImageID)

filteredImages := imageRepos.GetRepoImages(repo).FilterAndSort(pattern)

if latest, ok := filteredImages.Latest(); ok && latest.ID != currentImageID {
if latest.ID.Tag == "" {
logger.Log("warning", "untagged image in available images", "action", "skip container")
continue containers
}
currentCreatedAt := ""
for _, info := range filteredImages {
if info.CreatedAt.IsZero() {
logger.Log("warning", "image with zero created timestamp", "image", info.ID, "action", "skip container")
continue containers
}
if info.ID == currentImageID {
currentCreatedAt = info.CreatedAt.String()
}
}
if currentCreatedAt == "" {
currentCreatedAt = "filtered out or missing"
logger.Log("warning", "current image not in filtered images", "action", "proceed anyway")
}
newImage := currentImageID.WithNewTag(latest.ID.Tag)
changes.Add(workload.ID, container, newImage)
logger.Log("info", "added update to automation run", "new", newImage, "reason", fmt.Sprintf("latest %s (%s) > current %s (%s)", latest.ID.Tag, latest.CreatedAt, currentImageID.Tag, currentCreatedAt))
}
}
}
changes := calculateChanges(logger, candidateWorkloads, workloads, imageRepos)

if len(changes.Changes) > 0 {
d.UpdateManifests(ctx, update.Spec{Type: update.Auto, Spec: changes})
Expand Down Expand Up @@ -113,3 +75,41 @@ func (d *Daemon) getAllowedAutomatedResources(ctx context.Context) (resources, e
}
return result, nil
}

func calculateChanges(logger log.Logger, candidateWorkloads resources, workloads []cluster.Workload, imageRepos update.ImageRepos) *update.Automated {
changes := &update.Automated{}

for _, workload := range workloads {
var p policy.Set
if resource, ok := candidateWorkloads[workload.ID]; ok {
p = resource.Policies()
}
containers:
for _, container := range workload.ContainersOrNil() {
currentImageID := container.Image
pattern := policy.GetTagPattern(p, container.Name)
repo := currentImageID.Name
logger := log.With(logger, "workload", workload.ID, "container", container.Name, "repo", repo, "pattern", pattern, "current", currentImageID)

images := imageRepos.GetRepoImages(repo)
filteredImages := images.FilterAndSort(pattern)

if latest, ok := filteredImages.Latest(); ok && latest.ID != currentImageID {
if latest.ID.Tag == "" {
logger.Log("warning", "untagged image in available images", "action", "skip container")
continue containers
}
current := images.FindWithRef(currentImageID)
if current.CreatedAt.IsZero() || latest.CreatedAt.IsZero() {
logger.Log("warning", "image with zero created timestamp", "current", fmt.Sprintf("%s (%s)", current.ID, current.CreatedAt), "latest", fmt.Sprintf("%s (%s)", latest.ID, latest.CreatedAt), "action", "skip container")
continue containers
}
newImage := currentImageID.WithNewTag(latest.ID.Tag)
changes.Add(workload.ID, container, newImage)
logger.Log("info", "added update to automation run", "new", newImage, "reason", fmt.Sprintf("latest %s (%s) > current %s (%s)", latest.ID.Tag, latest.CreatedAt, currentImageID.Tag, current.CreatedAt))
}
}
}

return changes
}
210 changes: 210 additions & 0 deletions daemon/images_test.go
@@ -0,0 +1,210 @@
package daemon

import (
"github.com/weaveworks/flux/policy"
"testing"
"time"

"github.com/go-kit/kit/log"
"github.com/weaveworks/flux"
"github.com/weaveworks/flux/cluster"
"github.com/weaveworks/flux/image"
"github.com/weaveworks/flux/registry"
registryMock "github.com/weaveworks/flux/registry/mock"
"github.com/weaveworks/flux/resource"
"github.com/weaveworks/flux/update"
)

const (
container1 = "container1"
container2 = "container2"

currentContainer1Image = "container1/application:current"
newContainer1Image = "container1/application:new"

currentContainer2Image = "container2/application:current"
newContainer2Image = "container2/application:new"
noTagContainer2Image = "container2/application"
)

type candidate struct {
resourceID flux.ResourceID
policies policy.Set
}

func (c candidate) ResourceID() flux.ResourceID {
return c.resourceID
}

func (c candidate) Policies() policy.Set {
return c.policies
}

func (candidate) Source() string {
return ""
}

func (candidate) Bytes() []byte {
return []byte{}
}

func TestCalculateChanges_Automated(t *testing.T) {
logger := log.NewNopLogger()
resourceID := flux.MakeResourceID(ns, "deployment", "application")
candidateWorkloads := resources{
resourceID: candidate{
resourceID: resourceID,
policies: policy.Set{
policy.Automated: "true",
},
},
}
workloads := []cluster.Workload{
cluster.Workload{
ID: resourceID,
Containers: cluster.ContainersOrExcuse{
Containers: []resource.Container{
{
Name: container1,
Image: mustParseImageRef(currentContainer1Image),
},
},
},
},
}
var imageRegistry registry.Registry
{
current := makeImageInfo(currentContainer1Image, time.Now())
new := makeImageInfo(newContainer1Image, time.Now().Add(1*time.Second))
imageRegistry = &registryMock.Registry{
Images: []image.Info{
current,
new,
},
}
}
imageRepos, err := update.FetchImageRepos(imageRegistry, clusterContainers(workloads), logger)
if err != nil {
t.Fatal(err)
}

changes := calculateChanges(logger, candidateWorkloads, workloads, imageRepos)

if len := len(changes.Changes); len != 1 {
t.Errorf("Expected exactly 1 change, got %d changes", len)
} else if newImage := changes.Changes[0].ImageID.String(); newImage != newContainer1Image {
t.Errorf("Expected changed image to be %s, got %s", newContainer1Image, newImage)
}
}
func TestCalculateChanges_UntaggedImage(t *testing.T) {
logger := log.NewNopLogger()
resourceID := flux.MakeResourceID(ns, "deployment", "application")
candidateWorkloads := resources{
resourceID: candidate{
resourceID: resourceID,
policies: policy.Set{
policy.Automated: "true",
},
},
}
workloads := []cluster.Workload{
cluster.Workload{
ID: resourceID,
Containers: cluster.ContainersOrExcuse{
Containers: []resource.Container{
{
Name: container1,
Image: mustParseImageRef(currentContainer1Image),
},
{
Name: container2,
Image: mustParseImageRef(currentContainer2Image),
},
},
},
},
}
var imageRegistry registry.Registry
{
current1 := makeImageInfo(currentContainer1Image, time.Now())
new1 := makeImageInfo(newContainer1Image, time.Now().Add(1*time.Second))
current2 := makeImageInfo(currentContainer2Image, time.Now())
noTag2 := makeImageInfo(noTagContainer2Image, time.Now().Add(1*time.Second))
imageRegistry = &registryMock.Registry{
Images: []image.Info{
current1,
new1,
current2,
noTag2,
},
}
}
imageRepos, err := update.FetchImageRepos(imageRegistry, clusterContainers(workloads), logger)
if err != nil {
t.Fatal(err)
}

changes := calculateChanges(logger, candidateWorkloads, workloads, imageRepos)

if len := len(changes.Changes); len != 1 {
t.Errorf("Expected exactly 1 change, got %d changes", len)
} else if newImage := changes.Changes[0].ImageID.String(); newImage != newContainer1Image {
t.Errorf("Expected changed image to be %s, got %s", newContainer1Image, newImage)
}
}
func TestCalculateChanges_ZeroTimestamp(t *testing.T) {
logger := log.NewNopLogger()
resourceID := flux.MakeResourceID(ns, "deployment", "application")
candidateWorkloads := resources{
resourceID: candidate{
resourceID: resourceID,
policies: policy.Set{
policy.Automated: "true",
},
},
}
workloads := []cluster.Workload{
cluster.Workload{
ID: resourceID,
Containers: cluster.ContainersOrExcuse{
Containers: []resource.Container{
{
Name: container1,
Image: mustParseImageRef(currentContainer1Image),
},
{
Name: container2,
Image: mustParseImageRef(currentContainer2Image),
},
},
},
},
}
var imageRegistry registry.Registry
{
current1 := makeImageInfo(currentContainer1Image, time.Now())
new1 := makeImageInfo(newContainer1Image, time.Now().Add(1*time.Second))
zeroTimestampCurrent2 := image.Info{ID: mustParseImageRef(currentContainer2Image)}
new2 := makeImageInfo(newContainer2Image, time.Now().Add(1*time.Second))
imageRegistry = &registryMock.Registry{
Images: []image.Info{
current1,
new1,
zeroTimestampCurrent2,
new2,
},
}
}
imageRepos, err := update.FetchImageRepos(imageRegistry, clusterContainers(workloads), logger)
if err != nil {
t.Fatal(err)
}

changes := calculateChanges(logger, candidateWorkloads, workloads, imageRepos)

if len := len(changes.Changes); len != 1 {
t.Errorf("Expected exactly 1 change, got %d changes", len)
} else if newImage := changes.Changes[0].ImageID.String(); newImage != newContainer1Image {
t.Errorf("Expected changed image to be %s, got %s", newContainer1Image, newImage)
}
}