add support for opencontainers meta labels

feat/opencontainers-labels
nils måsén 10 months ago
parent 9f60766692
commit 77a46ab3bd

@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"time" "time"
dockerTypes "github.com/docker/docker/api/types"
t "github.com/containrrr/watchtower/pkg/types" t "github.com/containrrr/watchtower/pkg/types"
) )
@ -66,6 +68,11 @@ func (client MockClient) RemoveImageByID(_ t.ImageID) error {
return nil return nil
} }
// GetImage is a mock method
func (client MockClient) GetImage(_ t.ImageID) (dockerTypes.ImageInspect, error) {
return dockerTypes.ImageInspect{}, nil
}
// GetContainer is a mock method // GetContainer is a mock method
func (client MockClient) GetContainer(_ t.ContainerID) (t.Container, error) { func (client MockClient) GetContainer(_ t.ContainerID) (t.Container, error) {
return client.TestData.Containers[0], nil return client.TestData.Containers[0], nil

@ -33,7 +33,7 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
staleCheckFailed := 0 staleCheckFailed := 0
for i, targetContainer := range containers { for i, targetContainer := range containers {
stale, newestImage, err := client.IsContainerStale(targetContainer) stale, newestImageID, err := client.IsContainerStale(targetContainer)
shouldUpdate := stale && !params.NoRestart && !params.MonitorOnly && !targetContainer.IsMonitorOnly() shouldUpdate := stale && !params.NoRestart && !params.MonitorOnly && !targetContainer.IsMonitorOnly()
if err == nil && shouldUpdate { if err == nil && shouldUpdate {
// Check to make sure we have all the necessary information for recreating the container // Check to make sure we have all the necessary information for recreating the container
@ -55,12 +55,15 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
staleCheckFailed++ staleCheckFailed++
progress.AddSkipped(targetContainer, err) progress.AddSkipped(targetContainer, err)
} else { } else {
progress.AddScanned(targetContainer, newestImage) progress.AddScanned(targetContainer, newestImageID)
} }
containers[i].SetStale(stale) containers[i].SetStale(stale)
if stale { if stale {
staleCount++ staleCount++
if latestImage, err := client.GetImage(newestImageID); err == nil {
progress.UpdateLatestImage(targetContainer.ID(), latestImage)
}
} }
} }

@ -34,6 +34,7 @@ type Client interface {
ExecuteCommand(containerID t.ContainerID, command string, timeout int) (SkipUpdate bool, err error) ExecuteCommand(containerID t.ContainerID, command string, timeout int) (SkipUpdate bool, err error)
RemoveImageByID(t.ImageID) error RemoveImageByID(t.ImageID) error
WarnOnHeadPullFailed(container t.Container) bool WarnOnHeadPullFailed(container t.Container) bool
GetImage(imageID t.ImageID) (types.ImageInspect, error)
} }
// NewClient returns a new Client instance which can be used to interact with // NewClient returns a new Client instance which can be used to interact with
@ -397,6 +398,14 @@ func (client dockerClient) PullImage(ctx context.Context, container t.Container)
return nil return nil
} }
func (client dockerClient) GetImage(id t.ImageID) (types.ImageInspect, error) {
imageInfo, _, err := client.api.ImageInspectWithRaw(context.Background(), string(id))
if err != nil {
return types.ImageInspect{}, err
}
return imageInfo, nil
}
func (client dockerClient) RemoveImageByID(id t.ImageID) error { func (client dockerClient) RemoveImageByID(id t.ImageID) error {
log.Infof("Removing image %s", id.ShortID()) log.Infof("Removing image %s", id.ShortID())

@ -25,7 +25,9 @@ type ContainerStatus struct {
containerName string containerName string
imageName string imageName string
error error
state State state State
beforeMeta imageMeta
afterMeta imageMeta
} }
// ID returns the container ID // ID returns the container ID
@ -80,3 +82,13 @@ func (u *ContainerStatus) State() string {
return "Unknown" return "Unknown"
} }
} }
// Before returns the metadata for the image considered latest before the session
func (u *ContainerStatus) Before() wt.ImageMeta {
return u.beforeMeta
}
// After returns the metadata for the image considered latest after the session
func (u *ContainerStatus) After() wt.ImageMeta {
return u.afterMeta
}

@ -0,0 +1,55 @@
package session
import "strings"
type imageMeta map[string]string
func imageMetaFromLabels(labels map[string]string) imageMeta {
im := make(imageMeta)
for key, value := range labels {
if suffix, found := strings.CutPrefix(key, "org.opencontainers.image."); found {
im[suffix] = value
}
}
return im
}
func (im imageMeta) Authors() string {
return im["authors"]
}
func (im imageMeta) Created() string {
return im["created"]
}
func (im imageMeta) Description() string {
return im["description"]
}
func (im imageMeta) Documentation() string {
return im["documentation"]
}
func (im imageMeta) Licenses() string {
return im["licenses"]
}
func (im imageMeta) Revision() string {
return im["revision"]
}
func (im imageMeta) Source() string {
return im["source"]
}
func (im imageMeta) Title() string {
return im["title"]
}
func (im imageMeta) Url() string {
return im["url"]
}
func (im imageMeta) Version() string {
return im["version"]
}

@ -2,6 +2,7 @@ package session
import ( import (
"github.com/containrrr/watchtower/pkg/types" "github.com/containrrr/watchtower/pkg/types"
dockerTypes "github.com/docker/docker/api/types"
) )
// Progress contains the current session container status // Progress contains the current session container status
@ -9,6 +10,14 @@ type Progress map[types.ContainerID]*ContainerStatus
// UpdateFromContainer sets various status fields from their corresponding container equivalents // UpdateFromContainer sets various status fields from their corresponding container equivalents
func UpdateFromContainer(cont types.Container, newImage types.ImageID, state State) *ContainerStatus { func UpdateFromContainer(cont types.Container, newImage types.ImageID, state State) *ContainerStatus {
var beforeMeta imageMeta
if imageInfo := cont.ImageInfo(); imageInfo != nil && imageInfo.Config != nil {
beforeMeta = imageMetaFromLabels(imageInfo.Config.Labels)
} else {
beforeMeta = make(imageMeta)
}
return &ContainerStatus{ return &ContainerStatus{
containerID: cont.ID(), containerID: cont.ID(),
containerName: cont.Name(), containerName: cont.Name(),
@ -16,6 +25,8 @@ func UpdateFromContainer(cont types.Container, newImage types.ImageID, state Sta
oldImage: cont.SafeImageID(), oldImage: cont.SafeImageID(),
newImage: newImage, newImage: newImage,
state: state, state: state,
beforeMeta: beforeMeta,
afterMeta: beforeMeta,
} }
} }
@ -27,8 +38,9 @@ func (m Progress) AddSkipped(cont types.Container, err error) {
} }
// AddScanned adds a container to the Progress with the state set as scanned // AddScanned adds a container to the Progress with the state set as scanned
func (m Progress) AddScanned(cont types.Container, newImage types.ImageID) { func (m Progress) AddScanned(cont types.Container, newImageID types.ImageID) {
m.Add(UpdateFromContainer(cont, newImage, ScannedState)) m.Add(UpdateFromContainer(cont, newImageID, ScannedState))
} }
// UpdateFailed updates the containers passed, setting their state as failed with the supplied error // UpdateFailed updates the containers passed, setting their state as failed with the supplied error
@ -54,3 +66,9 @@ func (m Progress) MarkForUpdate(containerID types.ContainerID) {
func (m Progress) Report() types.Report { func (m Progress) Report() types.Report {
return NewReport(m) return NewReport(m)
} }
func (m Progress) UpdateLatestImage(containerID types.ContainerID, image dockerTypes.ImageInspect) {
if image.Config != nil {
m[containerID].afterMeta = imageMetaFromLabels(image.Config.Labels)
}
}

@ -0,0 +1,14 @@
package types
type ImageMeta interface {
Authors() string
Created() string
Description() string
Documentation() string
Licenses() string
Revision() string
Source() string
Title() string
Url() string
Version() string
}

@ -20,4 +20,6 @@ type ContainerReport interface {
ImageName() string ImageName() string
Error() string Error() string
State() string State() string
Before() ImageMeta
After() ImageMeta
} }

Loading…
Cancel
Save