From 28c9f61569b1065a76675c49ab21c81664e2d3c8 Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Tue, 10 Nov 2020 20:14:43 +0100 Subject: [PATCH] Only ever run one scan at the same time Also use a logging library --- README.md | 4 ++ constants.go | 5 +++ docker.go | 27 +++++++++----- go.mod | 1 + go.sum | 3 ++ main.go | 72 +++++++----------------------------- registry.go | 11 +++++- semver.go | 3 +- worker.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 155 insertions(+), 72 deletions(-) create mode 100644 constants.go create mode 100644 worker.go diff --git a/README.md b/README.md index bca0547..c37ce17 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,7 @@ lookbuilding.mode = semver_major lookbuilding.mode = semver_minor lookbuilding.mode = semver_patch ``` + +#### Environment variables: + +- `LOOKBUILDING_ADDR` - set the http bind address, default "0.0.0.0:8000" \ No newline at end of file diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..e28ce84 --- /dev/null +++ b/constants.go @@ -0,0 +1,5 @@ +package main + +const ( + ENV_ADDR = "LOOKBUILDING_ADDR" +) diff --git a/docker.go b/docker.go index 07cc514..ae5396d 100644 --- a/docker.go +++ b/docker.go @@ -43,6 +43,15 @@ func (lc LabeledContainer) SplitImageParts() (*string, string, *string) { return owner, repository, tag } +func (lc LabeledContainer) GetName() string { + if len(lc.Container.Names) >= 0 { + // trim prefixed "/" + return lc.Container.Names[0][1:] + } else { + return lc.Container.ID[:10] + } +} + func combineImageParts(owner *string, repository string, tag *string) string { image := repository if owner != nil { @@ -63,15 +72,15 @@ func getLabeledContainers(cli *client.Client) []LabeledContainer { panic(err) } - fmt.Println("scanning running container labels") + Logger.Infof("scanning running container labels") for _, container := range containers { - fmt.Printf("- %s %s\n", container.ID[:10], container.Image) + Logger.Debugf("checking %s %s", container.ID[:10], container.Image) for k, v := range container.Labels { - fmt.Printf(" - \"%s\": \"%s\"\n", k, v) + Logger.Debugf(` - "%s": "%s"`, k, v) if k == versioningModeLabel { mode := ParseVersioningMode(v) if mode == nil { - fmt.Printf("Failed to parse '%s' as a versioning mode\n", v) + Logger.Errorf(`Failed to parse "%s" as a versioning mode`, v) continue } @@ -95,7 +104,7 @@ func (lc LabeledContainer) UpdateTo(cli *client.Client, tag Tag) error { owner, repository, _ := lc.SplitImageParts() image := combineImageParts(owner, repository, &tag.Name) canonicalImage := fmt.Sprintf("docker.io/%s", image) - fmt.Printf("Pulling image \"%s\"\n", canonicalImage) + Logger.Infof(`pulling image "%s"`, canonicalImage) //containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{}) imageReader, err := cli.ImagePull(ctx, canonicalImage, types.ImagePullOptions{}) @@ -132,13 +141,13 @@ func (lc LabeledContainer) UpdateTo(cli *client.Client, tag Tag) error { hostConfig := oldContainer.HostConfig hostConfig.VolumesFrom = []string{tmpOldName} - fmt.Printf("Renaming container %s\n", lc.Container.ID) + Logger.Infof(`renaming container %s`, lc.Container.ID) err = cli.ContainerRename(ctx, lc.Container.ID, tmpOldName) if err != nil { return err } - fmt.Printf("Creating new container\n") + Logger.Infof("creating new container") new, err := cli.ContainerCreate(ctx, oldContainer.Config, hostConfig, &network.NetworkingConfig{ EndpointsConfig: oldContainer.NetworkSettings.Networks, }, name) @@ -147,13 +156,13 @@ func (lc LabeledContainer) UpdateTo(cli *client.Client, tag Tag) error { return err } - fmt.Printf("Starting new container id: %s\n", new.ID) + Logger.Infof("starting new container id: %s", new.ID) err = cli.ContainerStart(ctx, new.ID, types.ContainerStartOptions{}) if err != nil { return err } - fmt.Printf("Removing old container\n") + Logger.Infof("removing old container") err = cli.ContainerRemove(ctx, oldContainer.ID, types.ContainerRemoveOptions{ RemoveVolumes: false, RemoveLinks: false, diff --git a/go.mod b/go.mod index de0b11e..d97e97e 100644 --- a/go.mod +++ b/go.mod @@ -16,4 +16,5 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/sirupsen/logrus v1.4.2 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 ) diff --git a/go.sum b/go.sum index 3971ca2..a2e1571 100644 --- a/go.sum +++ b/go.sum @@ -142,7 +142,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/main.go b/main.go index cdcf51a..1b32965 100644 --- a/main.go +++ b/main.go @@ -3,78 +3,32 @@ package main import ( "fmt" "net/http" + "os" - "github.com/docker/docker/client" + "github.com/sirupsen/logrus" ) -func checkForUpdates() { - cli, err := client.NewEnvClient() - if err != nil { - panic(err) - } - - hub, err := anonymousClient() - if err != nil { - panic(err) - } - - labeledContainers := getLabeledContainers(cli) - fmt.Println() - - for _, lc := range labeledContainers { - owner, repository, tag := lc.SplitImageParts() - - fmt.Printf("container image: %s\n", combineImageParts(owner, repository, nil)) - fmt.Printf(" versioning: %+v\n", lc.Mode.Label()) - fmt.Printf(" id: %s\n", lc.Container.ImageID) - - if tag != nil { - fmt.Printf(" current tag: %s\n", *tag) - } else { - fmt.Printf(" no current tag, skipping\n") - continue - } - - repoTags, err := getDockerRepoTags(hub, owner, repository) - if err != nil { - panic(err) - } - - fmt.Println(" tags in registry:") - for _, tag := range repoTags { - fmt.Printf(" - \"%s\" %s\n", tag.Name, tag.Digest) - svt := parseTagAsSemVer(tag.Name) - if svt != nil { - fmt.Printf(" semver: true\n") - } - } - - shouldUpdateTo := lc.Mode.ShouldUpdate(*tag, repoTags) - if shouldUpdateTo != nil { - fmt.Printf(" updating to: %s\n", shouldUpdateTo.Name) - err = lc.UpdateTo(cli, *shouldUpdateTo) - if err != nil { - panic(err) - } - } else { - fmt.Println(" no update available") - } - } - fmt.Println("All done") -} +var ( + Logger logrus.Logger = *logrus.New() +) func main() { - addr := "0.0.0.0:8000" + addr, isPresent := os.LookupEnv(ENV_ADDR) + if !isPresent { + addr = "0.0.0.0:8000" + } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - go checkForUpdates() + TriggerScan() fmt.Fprintf(w, "OK") }) fs := http.FileServer(http.Dir("static/")) http.Handle("/static/", http.StripPrefix("/static/", fs)) - fmt.Printf("Listening on %s\n", addr) + Logger.Infof(`listening on %s`, addr) + + go Worker() err := http.ListenAndServe(addr, nil) if err != nil { diff --git a/registry.go b/registry.go index 79f28e7..6056be3 100644 --- a/registry.go +++ b/registry.go @@ -20,7 +20,14 @@ func anonymousClient() (*registry.Registry, error) { username := "" // anonymous password := "" // anonymous - return registry.New(url, username, password) + registry, err := registry.New(url, username, password) + if err != nil { + return nil, err + } + + registry.Logf = Logger.Infof + + return registry, nil } func getDockerRepoTags(hub *registry.Registry, maybe_owner *string, repository string) ([]Tag, error) { @@ -33,7 +40,7 @@ func getDockerRepoTags(hub *registry.Registry, maybe_owner *string, repository s return nil, err } - out := []Tag{} + var out []Tag for _, tag := range tags { digest, err := hub.ManifestDigest(repository, tag) diff --git a/semver.go b/semver.go index 8545d65..ddba329 100644 --- a/semver.go +++ b/semver.go @@ -1,7 +1,6 @@ package main import ( - //"fmt" "regexp" "github.com/coreos/go-semver/semver" @@ -16,7 +15,7 @@ type SemVerTag struct { version semver.Version } -// Return a +// Return a // Returns nil if the tag did not parse as semver func parseTagAsSemVer(tag string) *SemVerTag { var prefix string diff --git a/worker.go b/worker.go new file mode 100644 index 0000000..28c218c --- /dev/null +++ b/worker.go @@ -0,0 +1,101 @@ +package main + +import ( + "github.com/docker/docker/client" +) + +var ( + triggerCh = make(chan struct{}) +) + +func TriggerScan() { + triggerCh <- struct{}{} +} + +func Worker() { + Logger.Debugf("background worker starting") + + responseCh := make(chan struct{}) + + workerRunning := false + triggerWaiting := false + + for true { + select { + case _ = <-triggerCh: + if workerRunning { + triggerWaiting = true + } else { + workerRunning = true + go func() { + checkAndDoUpdate() + responseCh <- struct{}{} + }() + } + case _ = <-responseCh: + if triggerWaiting { + triggerWaiting = false + go func() { + checkAndDoUpdate() + responseCh <- struct{}{} + }() + } else { + workerRunning = false + } + } + } +} + +func checkAndDoUpdate() { + Logger.Infof("starting scan") + + cli, err := client.NewEnvClient() + if err != nil { + panic(err) + } + + hub, err := anonymousClient() + if err != nil { + panic(err) + } + + labeledContainers := getLabeledContainers(cli) + + Logger.Infof("found %d valid containers", len(labeledContainers)) + + for _, lc := range labeledContainers { + owner, repository, tag := lc.SplitImageParts() + name := lc.GetName() + imageName := combineImageParts(owner, repository, nil) + + if tag == nil { + Logger.Errorf(`no tag specified for container "%s", ignoring`, name) + continue + } + + Logger.Infof(`container "%s" image="%s" mode=%s tag="%s"`, name, imageName, lc.Mode.Label(), *tag) + + repoTags, err := getDockerRepoTags(hub, owner, repository) + if err != nil { + panic(err) + } + + Logger.Infof(`tags in registry for "%s": %d`, name, len(repoTags)) + for _, tag := range repoTags { + svt := parseTagAsSemVer(tag.Name) + Logger.Infof(`tag_name="%s" semver=%t digest=%s`, tag.Name, svt != nil, tag.Digest) + } + + shouldUpdateTo := lc.Mode.ShouldUpdate(*tag, repoTags) + if shouldUpdateTo != nil { + Logger.Infof(`updating %s from %s to: %s`, name, *tag, shouldUpdateTo.Name) + err = lc.UpdateTo(cli, *shouldUpdateTo) + if err != nil { + panic(err) + } + } else { + Logger.Infof("no update available for container %s", name) + } + } + Logger.Infof("all done") +}