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

Commit

Permalink
Split off SortedImageInfos to enforce constraint
Browse files Browse the repository at this point in the history
  • Loading branch information
rndstr committed Aug 3, 2018
1 parent f7a99c3 commit 4c8fabb
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 47 deletions.
24 changes: 12 additions & 12 deletions api/v6/container.go
Expand Up @@ -16,10 +16,10 @@ type Container struct {
LatestFiltered image.Info `json:",omitempty"`

// All available images (ignoring tag filters)
Available []image.Info `json:",omitempty"`
AvailableError string `json:",omitempty"`
AvailableImagesCount int `json:",omitempty"`
NewAvailableImagesCount int `json:",omitempty"`
Available update.SortedImageInfos `json:",omitempty"`
AvailableError string `json:",omitempty"`
AvailableImagesCount int `json:",omitempty"`
NewAvailableImagesCount int `json:",omitempty"`

// Filtered available images (matching tag filters)
FilteredImagesCount int `json:",omitempty"`
Expand All @@ -28,26 +28,26 @@ 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 policy.Pattern, fields []string) (Container, error) {
images.Sort(tagPattern)
sorted := images.Sort(tagPattern)

// All images
imagesCount := len(images)
imagesCount := len(sorted)
imagesErr := ""
if images == nil {
if sorted == nil {
imagesErr = registry.ErrNoImageData.Error()
}
var newImages []image.Info
for _, img := range images {
var newImages update.SortedImageInfos
for _, img := range sorted {
if tagPattern.Newer(&img, &currentImage) {
newImages = append(newImages, img)
}
}
newImagesCount := len(newImages)

// Filtered images
filteredImages := images.Filter(tagPattern)
filteredImages := sorted.Filter(tagPattern)
filteredImagesCount := len(filteredImages)
var newFilteredImages []image.Info
var newFilteredImages update.SortedImageInfos
for _, img := range filteredImages {
if tagPattern.Newer(&img, &currentImage) {
newFilteredImages = append(newFilteredImages, img)
Expand All @@ -61,7 +61,7 @@ func NewContainer(name string, images update.ImageInfos, currentImage image.Info
Current: currentImage,
LatestFiltered: latestFiltered,

Available: images,
Available: sorted,
AvailableError: imagesErr,
AvailableImagesCount: imagesCount,
NewAvailableImagesCount: newImagesCount,
Expand Down
2 changes: 1 addition & 1 deletion daemon/images.go
Expand Up @@ -48,7 +48,7 @@ func (d *Daemon) pollForNewImages(logger log.Logger) {
repo := currentImageID.Name
logger := log.With(logger, "service", service.ID, "container", container.Name, "repo", repo, "pattern", pattern, "current", currentImageID)

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

if latest, ok := filteredImages.Latest(); ok && latest.ID != currentImageID {
if latest.ID.Tag == "" {
Expand Down
2 changes: 1 addition & 1 deletion remote/rpc/clientV6.go
Expand Up @@ -160,7 +160,7 @@ func listImagesWithOptions(ctx context.Context, client listImagesWithOptionsClie
for j, container := range image.Containers {
tagPattern := policy.GetTagPattern(policyMap, image.ID, container.Name)
// Create a new container using the same function used in v10
newContainer, err := v6.NewContainer(container.Name, container.Available, container.Current, tagPattern, opts.OverrideContainerFields)
newContainer, err := v6.NewContainer(container.Name, update.ImageInfos(container.Available), container.Current, tagPattern, opts.OverrideContainerFields)
if err != nil {
return images, err
}
Expand Down
83 changes: 59 additions & 24 deletions update/images.go
Expand Up @@ -38,36 +38,26 @@ func (r ImageRepos) GetRepoImages(repo image.Name) ImageInfos {
// ImageInfos is a list of image.Info which can be filtered.
type ImageInfos []image.Info

// SortedImageInfos is a list of sorted image.Info
type SortedImageInfos []image.Info

// Filter returns only the images that match the pattern, in a new list.
// It also sorts the images according to the pattern's order.
func (ii ImageInfos) Filter(pattern policy.Pattern) ImageInfos {
var filtered ImageInfos
for _, i := range ii {
tag := i.ID.Tag
// Ignore latest if and only if it's not what the user wants.
if pattern != policy.PatternLatest && strings.EqualFold(tag, "latest") {
continue
}
if pattern.Matches(tag) {
filtered = append(filtered, i)
}
}
filtered.Sort(pattern)
return filtered
return filterImages(ii, pattern)
}

func (ii ImageInfos) Sort(pattern policy.Pattern) {
image.Sort(ii, pattern.Newer)
// Sort orders the images according to the pattern order in a new list.
func (ii ImageInfos) Sort(pattern policy.Pattern) SortedImageInfos {
return sortImages(ii, pattern)
}

// Latest returns the latest image from ImageInfos. If no such image exists,
// returns a zero value and `false`, and the caller can decide whether
// that's an error or not.
func (ii ImageInfos) Latest() (image.Info, bool) {
if len(ii) > 0 {
return ii[0], true
}
return image.Info{}, false
// FilterAndSort is an optimized helper function to compose filtering and sorting.
func (ii ImageInfos) FilterAndSort(pattern policy.Pattern) SortedImageInfos {
filtered := ii.Filter(pattern)
// Do not call sortImages() here which will clone the list that we already
// cloned in ImageInfos.Filter()
image.Sort(filtered, pattern.Newer)
return SortedImageInfos(filtered)
}

// FindWithRef returns image.Info given an image ref. If the image cannot be
Expand All @@ -81,6 +71,51 @@ func (ii ImageInfos) FindWithRef(ref image.Ref) image.Info {
return image.Info{ID: ref}
}

// Latest returns the latest image from SortedImageInfos. If no such image exists,
// returns a zero value and `false`, and the caller can decide whether
// that's an error or not.
func (is SortedImageInfos) Latest() (image.Info, bool) {
if len(is) > 0 {
return is[0], true
}
return image.Info{}, false
}

// Filter returns only the images that match the pattern, in a new list.
func (is SortedImageInfos) Filter(pattern policy.Pattern) SortedImageInfos {
return SortedImageInfos(filterImages(is, pattern))
}

// Sort orders the images according to the pattern order in a new list.
func (is SortedImageInfos) Sort(pattern policy.Pattern) SortedImageInfos {
return sortImages(is, pattern)
}

func sortImages(images []image.Info, pattern policy.Pattern) SortedImageInfos {
var sorted SortedImageInfos
for _, i := range images {
sorted = append(sorted, i)
}
image.Sort(sorted, pattern.Newer)
return sorted
}

// filterImages keeps the sort order pristine.
func filterImages(images []image.Info, pattern policy.Pattern) ImageInfos {
var filtered ImageInfos
for _, i := range images {
tag := i.ID.Tag
// Ignore latest if and only if it's not what the user wants.
if pattern != policy.PatternLatest && strings.EqualFold(tag, "latest") {
continue
}
if pattern.Matches(tag) {
filtered = append(filtered, i)
}
}
return filtered
}

// containers represents a collection of things that have containers
type containers interface {
Len() int
Expand Down
16 changes: 8 additions & 8 deletions update/images_test.go
Expand Up @@ -27,15 +27,15 @@ func TestDecanon(t *testing.T) {
name: infos,
}}

filteredImages := m.GetRepoImages(mustParseName("weaveworks/helloworld")).Filter(policy.PatternAll)
filteredImages := m.GetRepoImages(mustParseName("weaveworks/helloworld")).FilterAndSort(policy.PatternAll)
latest, ok := filteredImages.Latest()
if !ok {
t.Error("did not find latest image")
} else if latest.ID.Name != mustParseName("weaveworks/helloworld") {
t.Error("name did not match what was asked")
}

filteredImages = m.GetRepoImages(mustParseName("index.docker.io/weaveworks/helloworld")).Filter(policy.PatternAll)
filteredImages = m.GetRepoImages(mustParseName("index.docker.io/weaveworks/helloworld")).FilterAndSort(policy.PatternAll)
latest, ok = filteredImages.Latest()
if !ok {
t.Error("did not find latest image")
Expand All @@ -62,10 +62,10 @@ func TestImageInfos_Filter_latest(t *testing.T) {
ID: image.Ref{Name: image.Name{Image: "moon"}, Tag: "v0"},
}
ii := ImageInfos{latest, other}
assert.Equal(t, ImageInfos{latest}, ii.Filter(policy.PatternLatest))
assert.Equal(t, ImageInfos{latest}, ii.Filter(policy.NewPattern("latest")))
assert.Equal(t, ImageInfos{other}, ii.Filter(policy.PatternAll))
assert.Equal(t, ImageInfos{other}, ii.Filter(policy.NewPattern("*")))
assert.Equal(t, SortedImageInfos{latest}, ii.FilterAndSort(policy.PatternLatest))
assert.Equal(t, SortedImageInfos{latest}, ii.FilterAndSort(policy.NewPattern("latest")))
assert.Equal(t, SortedImageInfos{other}, ii.FilterAndSort(policy.PatternAll))
assert.Equal(t, SortedImageInfos{other}, ii.FilterAndSort(policy.NewPattern("*")))
}

func TestImageInfos_Filter_semver(t *testing.T) {
Expand All @@ -74,8 +74,8 @@ func TestImageInfos_Filter_semver(t *testing.T) {
semver1 := image.Info{ID: image.Ref{Name: image.Name{Image: "earth"}, Tag: "1.0.0"}}

ii := ImageInfos{latest, semver0, semver1}
assert.Equal(t, ImageInfos{semver1, semver0}, ii.Filter(policy.NewPattern("semver:*")))
assert.Equal(t, ImageInfos{semver1}, ii.Filter(policy.NewPattern("semver:~1")))
assert.Equal(t, SortedImageInfos{semver1, semver0}, ii.FilterAndSort(policy.NewPattern("semver:*")))
assert.Equal(t, SortedImageInfos{semver1}, ii.FilterAndSort(policy.NewPattern("semver:~1")))
}

func TestAvail(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion update/release.go
Expand Up @@ -231,7 +231,7 @@ func (s ReleaseSpec) calculateImageUpdates(rc ReleaseContext, candidates []*Cont
for _, container := range containers {
currentImageID := container.Image

filteredImages := imageRepos.GetRepoImages(currentImageID.Name).Filter(policy.PatternAll)
filteredImages := imageRepos.GetRepoImages(currentImageID.Name).FilterAndSort(policy.PatternAll)
latestImage, ok := filteredImages.Latest()
if !ok {
if currentImageID.CanonicalName() != singleRepo {
Expand Down

0 comments on commit 4c8fabb

Please sign in to comment.