Compare commits

..

5 Commits

Author SHA1 Message Date
01f61b3f88 Change renaming behaviour
Create new container with name *.lb.new and then rename it instead of
renaming old container first
2021-06-01 19:40:54 +02:00
86816daab1 Remove file server 2021-02-01 15:27:55 +01:00
ee4c68cfba Add basic healthcheck route 2021-02-01 15:27:13 +01:00
e016c6714d Update readme 2020-11-11 16:09:59 +01:00
5984349df3 Implement same_tag versioning mode 2020-11-11 15:21:21 +01:00
13 changed files with 286 additions and 204 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.idea

View File

@ -11,7 +11,7 @@ RUN go mod download
# Copy source and build app # Copy source and build app
COPY . /app COPY . /app
RUN go build . RUN go build hulthe.net/lookbuilding/cmd/lookbuilding
FROM alpine FROM alpine

View File

@ -1,6 +1,4 @@
# lookbuilding # lookbuilding
_WIP_
Scuffed lookalike of watchtower Scuffed lookalike of watchtower
This project will, when triggered by an http request, scan the This project will, when triggered by an http request, scan the
@ -25,4 +23,4 @@ lookbuilding.mode = semver_patch
#### Environment variables: #### Environment variables:
- `LOOKBUILDING_ADDR` - set the http bind address, default "0.0.0.0:8000" - `LOOKBUILDING_ADDR` - set the http bind address, default "0.0.0.0:8000"

View File

@ -12,6 +12,8 @@ import (
const EnvAddr = "LOOKBUILDING_ADDR" const EnvAddr = "LOOKBUILDING_ADDR"
func main() { func main() {
//l.Logger.Level = logrus.DebugLevel
addr, isPresent := os.LookupEnv(EnvAddr) addr, isPresent := os.LookupEnv(EnvAddr)
if !isPresent { if !isPresent {
addr = "0.0.0.0:8000" addr = "0.0.0.0:8000"
@ -22,8 +24,10 @@ func main() {
fmt.Fprintf(w, "OK") fmt.Fprintf(w, "OK")
}) })
fs := http.FileServer(http.Dir("static/")) http.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
http.Handle("/static/", http.StripPrefix("/static/", fs)) // TODO: if the last scan errored, this should not return OK
fmt.Fprintf(w, "OK")
})
l.Logger.Infof(`listening on %s`, addr) l.Logger.Infof(`listening on %s`, addr)

View File

@ -42,7 +42,6 @@ func (lc LabeledContainer) GetName() string {
} }
} }
func CombineImageParts(owner *string, repository string, tag *string) string { func CombineImageParts(owner *string, repository string, tag *string) string {
image := repository image := repository
if owner != nil { if owner != nil {

126
internal/pkg/docker/mode.go Normal file
View File

@ -0,0 +1,126 @@
package docker
import (
"sort"
l "hulthe.net/lookbuilding/internal/pkg/logging"
"hulthe.net/lookbuilding/internal/pkg/registry"
"hulthe.net/lookbuilding/internal/pkg/semver"
)
const VersioningModeLabel = "lookbuilding.mode"
type VersioningMode interface {
Label() string
ShouldUpdate(container LabeledContainer, availableTags []registry.Tag) *registry.Tag
}
type SameTag struct{}
type SemVerMajor struct{}
type SemVerMinor struct{}
type SemVerPatch struct{}
var (
AllModes = [...]VersioningMode{
SameTag{},
SemVerMajor{},
SemVerMinor{},
SemVerPatch{},
}
)
func (SameTag) Label() string { return "same_tag" }
func (SameTag) ShouldUpdate(container LabeledContainer, availableTags []registry.Tag) *registry.Tag {
_, _, currentTag := container.SplitImageParts()
if currentTag == nil {
l.Logger.Errorf("container %s has no tag, won't try to update", container.GetName())
return nil
}
for _, tag := range availableTags {
if tag.Name == *currentTag {
remoteDigest, err := tag.GetDigest()
if err != nil {
l.Logger.Errorf("failed to get digest for tag %s", tag.Name)
l.Logger.Error(err)
}
l.Logger.Debug(remoteDigest.String(), container.ImageDigest)
if container.ImageDigest != remoteDigest {
return &tag
}
return nil
}
}
return nil
}
func semVerShouldUpdate(container LabeledContainer, availableTags []registry.Tag, isValid func(current, available semver.Tag) bool) *registry.Tag {
_, _, currentTag := container.SplitImageParts()
if currentTag == nil {
l.Logger.Errorf("container %s has no tag, won't try to update", container.GetName())
return nil
}
currentSemVer := semver.ParseTagAsSemVer(*currentTag)
if currentSemVer == nil {
return nil
}
semverTags := make([]registry.Tag, 0)
for _, tag := range availableTags {
if tag.SemVer != nil && isValid(*currentSemVer, *tag.SemVer) {
semverTags = append(semverTags, tag)
}
}
if len(semverTags) == 0 {
return nil
}
sort.Slice(semverTags, func(i, j int) bool {
a := semverTags[i].SemVer.Version
b := semverTags[j].SemVer.Version
return b.LessThan(a)
})
return &semverTags[0]
}
func (SemVerMajor) Label() string { return "semver_major" }
func (SemVerMajor) ShouldUpdate(container LabeledContainer, availableTags []registry.Tag) *registry.Tag {
return semVerShouldUpdate(container, availableTags, func(current, available semver.Tag) bool {
// The new version should be greater
return current.Version.LessThan(available.Version)
})
}
func (SemVerMinor) Label() string { return "semver_minor" }
func (SemVerMinor) ShouldUpdate(container LabeledContainer, availableTags []registry.Tag) *registry.Tag {
return semVerShouldUpdate(container, availableTags, func(current, available semver.Tag) bool {
// The new version should be greater, but still the same major number
return current.Version.LessThan(available.Version) &&
current.Version.Major == available.Version.Major
})
}
func (SemVerPatch) Label() string { return "semver_patch" }
func (SemVerPatch) ShouldUpdate(container LabeledContainer, availableTags []registry.Tag) *registry.Tag {
return semVerShouldUpdate(container, availableTags, func(current, available semver.Tag) bool {
// The new version should be greater, but still the same major & minor number
return current.Version.LessThan(available.Version) &&
current.Version.Major == available.Version.Major &&
current.Version.Minor == available.Version.Minor
})
}
func ParseMode(input string) *VersioningMode {
for _, mode := range AllModes {
if mode.Label() == input {
return &mode
}
}
return nil
}

View File

@ -3,17 +3,19 @@ package docker
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
l "hulthe.net/lookbuilding/internal/pkg/logging" l "hulthe.net/lookbuilding/internal/pkg/logging"
"hulthe.net/lookbuilding/internal/pkg/registry" "hulthe.net/lookbuilding/internal/pkg/registry"
"hulthe.net/lookbuilding/internal/pkg/versioning"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"
d "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
) )
func GetLabeledContainers(cli *client.Client) []LabeledContainer { func GetLabeledContainers(cli *client.Client) ([]LabeledContainer, error) {
out := make([]LabeledContainer, 0) out := make([]LabeledContainer, 0)
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{}) containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
@ -23,19 +25,42 @@ func GetLabeledContainers(cli *client.Client) []LabeledContainer {
l.Logger.Infof("scanning running container labels") l.Logger.Infof("scanning running container labels")
for _, container := range containers { for _, container := range containers {
l.Logger.Debugf("checking %s %s", container.ID[:10], container.Image) lc := LabeledContainer{Container: container}
if container.Image == container.ImageID {
l.Logger.Errorf("ignoring container %s which has an untagged image", lc.GetName())
continue
}
l.Logger.Debugf("checking %s %s", lc.GetName(), container.Image)
for k, v := range container.Labels { for k, v := range container.Labels {
l.Logger.Debugf(` - "%s": "%s"`, k, v) l.Logger.Debugf(` - "%s": "%s"`, k, v)
if k == versioning.ModeLabel {
mode := versioning.ParseMode(v) if k == VersioningModeLabel {
mode := ParseMode(v)
if mode == nil { if mode == nil {
l.Logger.Errorf(`Failed to parse "%s" as a versioning mode`, v) l.Logger.Errorf(`failed to parse "%s" as a versioning mode`, v)
continue continue
} }
lc.Mode = *mode
lc := LabeledContainer{ inspect, _, err := cli.ImageInspectWithRaw(context.Background(), container.ImageID)
container, if err != nil {
*mode, return nil, errors.Wrapf(err, "failed to inspect container %s", lc.GetName())
}
if len(inspect.RepoDigests) >= 2 {
// TODO: find out if having more than one digest could break same_tag version mode
l.Logger.Warnf("unexpected: container %s had more than one RepoDigest", lc.GetName())
} else if len(inspect.RepoDigests) == 0 {
return nil, errors.Errorf("unexpected: container %s has no RepoDigests", lc.GetName())
}
imageDigest := inspect.RepoDigests[0]
atIndex := strings.Index(imageDigest, "@")
lc.ImageDigest, err = d.Parse(imageDigest[atIndex+1:])
if err != nil {
return nil, errors.Wrapf(err, "failed to parse image digest of running container %s", lc.GetName())
} }
out = append(out, lc) out = append(out, lc)
@ -44,7 +69,7 @@ func GetLabeledContainers(cli *client.Client) []LabeledContainer {
} }
} }
return out return out, nil
} }
func (lc LabeledContainer) UpdateTo(cli *client.Client, tag registry.Tag) error { func (lc LabeledContainer) UpdateTo(cli *client.Client, tag registry.Tag) error {
@ -55,63 +80,60 @@ func (lc LabeledContainer) UpdateTo(cli *client.Client, tag registry.Tag) error
canonicalImage := fmt.Sprintf("docker.io/%s", image) canonicalImage := fmt.Sprintf("docker.io/%s", image)
l.Logger.Infof(`pulling image "%s"`, canonicalImage) l.Logger.Infof(`pulling image "%s"`, canonicalImage)
//containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
imageReader, err := cli.ImagePull(ctx, canonicalImage, types.ImagePullOptions{}) imageReader, err := cli.ImagePull(ctx, canonicalImage, types.ImagePullOptions{})
if err != nil { if err != nil {
return err return errors.Wrapf(err, `failed to pull image "%s"`, canonicalImage)
} }
defer imageReader.Close()
loadResponse, err := cli.ImageLoad(ctx, imageReader, false) loadResponse, err := cli.ImageLoad(ctx, imageReader, false)
if err != nil { if err != nil {
return err return errors.Wrapf(err, `failed to load pulled image "%s"`, canonicalImage)
} }
defer loadResponse.Body.Close() err = loadResponse.Body.Close()
if err != nil {
return errors.Wrapf(err, `failed to close rpc response when loading image "%s"`, canonicalImage)
}
err = imageReader.Close()
if err != nil {
return errors.Wrapf(err, `failed to close rpc response when pulling image "%s"`, canonicalImage)
}
fmt.Printf("Stopping container %s\n", lc.Container.ID) l.Logger.Infof("stopping container %s", lc.GetName())
err = cli.ContainerStop(ctx, lc.Container.ID, nil) err = cli.ContainerStop(ctx, lc.Container.ID, nil)
if err != nil { if err != nil {
return err return errors.Wrapf(err, `failed to stop container "%s"`, lc.GetName())
} }
oldContainer, err := cli.ContainerInspect(ctx, lc.Container.ID) oldContainer, err := cli.ContainerInspect(ctx, lc.Container.ID)
if err != nil { if err != nil {
return err return errors.Wrapf(err, `failed to inspect container "%s"`, lc.GetName())
} }
name := oldContainer.Name tmpName := fmt.Sprintf("%s.lb.new", lc.GetName())
tmpOldName := fmt.Sprintf("%s.lb.old", name)
config := oldContainer.Config config := *oldContainer.Config
config.Image = image config.Image = image
hostConfig := oldContainer.HostConfig hostConfig := *oldContainer.HostConfig
hostConfig.VolumesFrom = []string{tmpOldName} hostConfig.VolumesFrom = []string{lc.GetName()}
l.Logger.Infof(`renaming container %s`, lc.Container.ID) l.Logger.Infof("creating new container %s", tmpName)
err = cli.ContainerRename(ctx, lc.Container.ID, tmpOldName) new, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{
if err != nil {
return err
}
l.Logger.Infof("creating new container")
new, err := cli.ContainerCreate(ctx, oldContainer.Config, hostConfig, &network.NetworkingConfig{
EndpointsConfig: oldContainer.NetworkSettings.Networks, EndpointsConfig: oldContainer.NetworkSettings.Networks,
}, name) }, tmpName)
if err != nil { if err != nil {
return err return errors.Wrapf(err, `failed to create container for new version of %s`, image)
} }
l.Logger.Infof("starting new container id: %s", new.ID) l.Logger.Infof("starting new container %s with id %s", tmpName, new.ID)
err = cli.ContainerStart(ctx, new.ID, types.ContainerStartOptions{}) err = cli.ContainerStart(ctx, new.ID, types.ContainerStartOptions{})
if err != nil { if err != nil {
return err return err
} }
l.Logger.Infof("removing old container") l.Logger.Infof("removing old container %s", lc.GetName())
err = cli.ContainerRemove(ctx, oldContainer.ID, types.ContainerRemoveOptions{ err = cli.ContainerRemove(ctx, oldContainer.ID, types.ContainerRemoveOptions{
RemoveVolumes: false, RemoveVolumes: false,
RemoveLinks: false, RemoveLinks: false,
@ -121,6 +143,11 @@ func (lc LabeledContainer) UpdateTo(cli *client.Client, tag registry.Tag) error
return err return err
} }
l.Logger.Infof(`renaming container %s to %s`, tmpName, lc.GetName())
err = cli.ContainerRename(ctx, new.ID, lc.GetName())
if err != nil {
return errors.Wrapf(err, `failed to rename container "%s" to "%s"`, tmpName, lc.GetName())
}
return nil return nil
} }

View File

@ -1,12 +1,12 @@
package docker package docker
import ( import (
"hulthe.net/lookbuilding/internal/pkg/versioning"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
d "github.com/opencontainers/go-digest"
) )
type LabeledContainer struct { type LabeledContainer struct {
Container types.Container Container types.Container
Mode versioning.Mode Mode VersioningMode
ImageDigest d.Digest
} }

View File

@ -4,4 +4,4 @@ import "github.com/sirupsen/logrus"
var ( var (
Logger logrus.Logger = *logrus.New() Logger logrus.Logger = *logrus.New()
) )

View File

@ -1,31 +1,35 @@
package registry package registry
import ( import (
"fmt"
"net/http"
l "hulthe.net/lookbuilding/internal/pkg/logging"
"hulthe.net/lookbuilding/internal/pkg/semver" "hulthe.net/lookbuilding/internal/pkg/semver"
"github.com/heroku/docker-registry-client/registry" "github.com/heroku/docker-registry-client/registry"
"github.com/opencontainers/go-digest" d "github.com/opencontainers/go-digest"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type tagListReq struct { type tagListReq struct {
repository string repository string
responder chan<- tagListResp responder chan<- tagListResp
} }
type tagListResp struct { type tagListResp struct {
Data []Tag Data []Tag
Error error Error error
} }
type digestReq struct { type digestReq struct {
repository string repository string
tag string tag string
responder chan<- digestResp responder chan<- digestResp
} }
type digestResp struct { type digestResp struct {
Data digest.Digest Data d.Digest
Error error Error error
} }
@ -33,23 +37,68 @@ type repoCache struct {
Tags []Tag Tags []Tag
// Map tags to digests // Map tags to digests
Digests map[string]digest.Digest Digests map[string]d.Digest
} }
type cache struct { type cache struct {
TagListReq chan<- tagListReq TagListReq chan<- tagListReq
DigestReq chan<- digestReq DigestReq chan<- digestReq
} }
func newCache(registry registry.Registry) cache { func newCache(registry registry.Registry) cache {
tagListReq := make(chan tagListReq) tagListReq := make(chan tagListReq)
digestReq := make(chan digestReq) digestReq := make(chan digestReq)
cache := cache{
tagListReq,
digestReq,
}
store := map[string]repoCache{} store := map[string]repoCache{}
go func() { go func() {
for { for {
select { select {
case req := <-digestReq:
repo, isPresent := store[req.repository]
if !isPresent {
req.responder <- digestResp{Error: errors.Errorf(
`repo "%s" not present in cache, can't fetch digest'`, req.repository,
)}
}
digest, isPresent := repo.Digests[req.tag]
if isPresent {
req.responder <- digestResp{Data: digest}
} else {
url := fmt.Sprintf("%s/v2/%s/manifests/%s", registry.URL, req.repository, req.tag)
l.Logger.Infof("registry.manifest.head url=%s repository=%s reference=%s", url, req.repository, req.tag)
httpReq, _ := http.NewRequest("HEAD", url, nil)
httpReq.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
resp, err := registry.Client.Do(httpReq)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
req.responder <- digestResp{Error: errors.Wrapf(
err, "failed to get digest for repo=%s tag=%s", req.repository, req.tag,
)}
}
digest, err := d.Parse(resp.Header.Get("Docker-Content-Digest"))
/*
digest, err := registry.ManifestDigest(req.repository, req.tag)
if err != nil {
req.responder <- digestResp{Error: errors.Wrapf(
err, `failed to get digest for repo=%s tag=%s`, req.repository, req.tag,
)}
}
*/
repo.Digests[req.tag] = digest
req.responder <- digestResp{Data: digest}
}
case req := <-tagListReq: case req := <-tagListReq:
repo, isPresent := store[req.repository] repo, isPresent := store[req.repository]
@ -71,50 +120,26 @@ func newCache(registry registry.Registry) cache {
var tags []Tag var tags []Tag
for _, tagName := range tagNames { for _, tagName := range tagNames {
tags = append(tags, Tag{ tags = append(tags, Tag{
Name: tagName, Name: tagName,
SemVer: semver.ParseTagAsSemVer(tagName), SemVer: semver.ParseTagAsSemVer(tagName),
repository: req.repository,
cache: cache,
}) })
} }
// store result in cache // store result in cache
store[req.repository] = repoCache{ store[req.repository] = repoCache{
Tags: tags, Tags: tags,
Digests: map[string]digest.Digest{}, Digests: map[string]d.Digest{},
} }
req.responder <- tagListResp{ req.responder <- tagListResp{
Data: tags, Data: tags,
} }
} }
case req := <-digestReq:
repo, isPresent := store[req.repository]
if !isPresent {
req.responder <- digestResp{Error: errors.Errorf(
`repo "%s" not present in cache, can't fetch digest'`, req.repository,
)}
}
digest, isPresent := repo.Digests[req.tag]
if isPresent {
req.responder <- digestResp{Data: digest}
} else {
digest, err := registry.ManifestDigest(req.repository, req.tag)
if err != nil {
req.responder <- digestResp{Error: errors.Wrapf(
err, `failed to get digest for repo=%s tag=%s`, req.repository, req.tag,
)}
}
repo.Digests[req.tag] = digest
req.responder <- digestResp{Data: digest}
}
} }
} }
}() }()
return cache { return cache
tagListReq, }
digestReq,
}
}

View File

@ -11,10 +11,10 @@ import (
) )
type Tag struct { type Tag struct {
Name string Name string
SemVer *semver.Tag SemVer *semver.Tag
repository string repository string
cache cache cache cache
} }
type Client struct { type Client struct {
@ -23,7 +23,7 @@ type Client struct {
func (tag Tag) GetDigest() (digest.Digest, error) { func (tag Tag) GetDigest() (digest.Digest, error) {
responseCh := make(chan digestResp) responseCh := make(chan digestResp)
tag.cache.DigestReq <- digestReq{ tag.repository, tag.Name, responseCh } tag.cache.DigestReq <- digestReq{tag.repository, tag.Name, responseCh}
resp := <-responseCh resp := <-responseCh
return resp.Data, resp.Error return resp.Data, resp.Error
} }
@ -34,9 +34,8 @@ func (client Client) GetRepoTags(maybeOwner *string, repository string) ([]Tag,
} }
responseCh := make(chan tagListResp) responseCh := make(chan tagListResp)
client.cache.TagListReq <- tagListReq { repository, responseCh } client.cache.TagListReq <- tagListReq{repository, responseCh}
resp := <-responseCh resp := <-responseCh
return resp.Data, resp.Error return resp.Data, resp.Error
} }

View File

@ -1,99 +0,0 @@
package versioning
import (
"sort"
l "hulthe.net/lookbuilding/internal/pkg/logging"
"hulthe.net/lookbuilding/internal/pkg/registry"
"hulthe.net/lookbuilding/internal/pkg/semver"
)
const ModeLabel = "lookbuilding.mode"
type Mode interface {
Label() string
ShouldUpdate(currentTag string, availableTags []registry.Tag) *registry.Tag
}
type SameTag struct{}
type SemVerMajor struct{}
type SemVerMinor struct{}
type SemVerPatch struct{}
var (
AllModes = [...]Mode{
SameTag{},
SemVerMajor{},
SemVerMinor{},
SemVerPatch{},
}
)
func (SameTag) Label() string { return "same_tag" }
func (SameTag) ShouldUpdate(currentTag string, availableTags []registry.Tag) *registry.Tag {
l.Logger.Errorf("Not implemented: 'same_tag' versioning mode")
return nil // TODO: implement me
}
func semVerShouldUpdate(currentTag string, availableTags []registry.Tag, isValid func(current, available semver.Tag) bool) *registry.Tag {
currentSemVer := semver.ParseTagAsSemVer(currentTag)
if currentSemVer == nil {
return nil
}
semverTags := make([]registry.Tag, 0)
for _, tag := range availableTags {
if tag.SemVer != nil && isValid(*currentSemVer, *tag.SemVer) {
semverTags = append(semverTags, tag)
}
}
if len(semverTags) == 0 {
return nil
}
sort.Slice(semverTags, func(i, j int) bool {
a := semverTags[i].SemVer.Version
b := semverTags[j].SemVer.Version
return b.LessThan(a)
})
return &semverTags[0]
}
func (SemVerMajor) Label() string { return "semver_major" }
func (SemVerMajor) ShouldUpdate(currentTag string, availableTags []registry.Tag) *registry.Tag {
return semVerShouldUpdate(currentTag, availableTags, func(current, available semver.Tag) bool {
// The new version should be greater
return current.Version.LessThan(available.Version)
})
}
func (SemVerMinor) Label() string { return "semver_minor" }
func (SemVerMinor) ShouldUpdate(currentTag string, availableTags []registry.Tag) *registry.Tag {
return semVerShouldUpdate(currentTag, availableTags, func(current, available semver.Tag) bool {
// The new version should be greater, but still the same major number
return current.Version.LessThan(available.Version) &&
current.Version.Major == available.Version.Major
})
}
func (SemVerPatch) Label() string { return "semver_patch" }
func (SemVerPatch) ShouldUpdate(currentTag string, availableTags []registry.Tag) *registry.Tag {
return semVerShouldUpdate(currentTag, availableTags, func(current, available semver.Tag) bool {
// The new version should be greater, but still the same major & minor number
return current.Version.LessThan(available.Version) &&
current.Version.Major == available.Version.Major &&
current.Version.Minor == available.Version.Minor
})
}
func ParseMode(input string) *Mode {
for _, mode := range AllModes {
if mode.Label() == input {
return &mode
}
}
return nil
}

View File

@ -64,7 +64,10 @@ func checkAndDoUpdate() {
panic(err) panic(err)
} }
labeledContainers := docker.GetLabeledContainers(cli) labeledContainers, err := docker.GetLabeledContainers(cli)
if err != nil {
panic(err)
}
l.Logger.Infof("found %d valid containers", len(labeledContainers)) l.Logger.Infof("found %d valid containers", len(labeledContainers))
@ -85,25 +88,24 @@ func checkAndDoUpdate() {
panic(err) panic(err)
} }
l.Logger.Infof(`tags in registry for "%s": %d`, name, len(repoTags)) l.Logger.Debugf(`tags in registry for "%s": %d`, name, len(repoTags))
for _, tag := range repoTags { for _, tag := range repoTags {
svt := semver.ParseTagAsSemVer(tag.Name) svt := semver.ParseTagAsSemVer(tag.Name)
l.Logger.Infof(`tag_name="%s" semver=%t`, tag.Name, svt != nil) l.Logger.Debugf(`tag_name="%s" semver=%t`, tag.Name, svt != nil)
} }
shouldUpdateTo := lc.Mode.ShouldUpdate(*tag, repoTags) shouldUpdateTo := lc.Mode.ShouldUpdate(lc, repoTags)
if shouldUpdateTo != nil { if shouldUpdateTo != nil {
l.Logger.Infof(`updating %s from %s to: %s`, name, *tag, shouldUpdateTo.Name) l.Logger.Infof(`updating %s from %s to %s`, name, *tag, shouldUpdateTo.Name)
go func() { err = lc.UpdateTo(cli, *shouldUpdateTo)
err = lc.UpdateTo(cli, *shouldUpdateTo) if err != nil {
if err != nil { l.Logger.Error(err)
l.Logger.Error(err) }
}
}()
} else { } else {
l.Logger.Infof("no update available for container %s", name) l.Logger.Infof("no update available for container %s", name)
} }
} }
l.Logger.Infof("all done") l.Logger.Infof("all done")
} }