Only ever run one scan at the same time
Also use a logging library
This commit is contained in:
@ -12,3 +12,7 @@ lookbuilding.mode = semver_major
|
|||||||
lookbuilding.mode = semver_minor
|
lookbuilding.mode = semver_minor
|
||||||
lookbuilding.mode = semver_patch
|
lookbuilding.mode = semver_patch
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Environment variables:
|
||||||
|
|
||||||
|
- `LOOKBUILDING_ADDR` - set the http bind address, default "0.0.0.0:8000"
|
||||||
5
constants.go
Normal file
5
constants.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
const (
|
||||||
|
ENV_ADDR = "LOOKBUILDING_ADDR"
|
||||||
|
)
|
||||||
27
docker.go
27
docker.go
@ -43,6 +43,15 @@ func (lc LabeledContainer) SplitImageParts() (*string, string, *string) {
|
|||||||
return owner, repository, tag
|
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 {
|
func combineImageParts(owner *string, repository string, tag *string) string {
|
||||||
image := repository
|
image := repository
|
||||||
if owner != nil {
|
if owner != nil {
|
||||||
@ -63,15 +72,15 @@ func getLabeledContainers(cli *client.Client) []LabeledContainer {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("scanning running container labels")
|
Logger.Infof("scanning running container labels")
|
||||||
for _, container := range containers {
|
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 {
|
for k, v := range container.Labels {
|
||||||
fmt.Printf(" - \"%s\": \"%s\"\n", k, v)
|
Logger.Debugf(` - "%s": "%s"`, k, v)
|
||||||
if k == versioningModeLabel {
|
if k == versioningModeLabel {
|
||||||
mode := ParseVersioningMode(v)
|
mode := ParseVersioningMode(v)
|
||||||
if mode == nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +104,7 @@ func (lc LabeledContainer) UpdateTo(cli *client.Client, tag Tag) error {
|
|||||||
owner, repository, _ := lc.SplitImageParts()
|
owner, repository, _ := lc.SplitImageParts()
|
||||||
image := combineImageParts(owner, repository, &tag.Name)
|
image := combineImageParts(owner, repository, &tag.Name)
|
||||||
canonicalImage := fmt.Sprintf("docker.io/%s", image)
|
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{})
|
//containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
|
||||||
imageReader, err := cli.ImagePull(ctx, canonicalImage, types.ImagePullOptions{})
|
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 := oldContainer.HostConfig
|
||||||
hostConfig.VolumesFrom = []string{tmpOldName}
|
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)
|
err = cli.ContainerRename(ctx, lc.Container.ID, tmpOldName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Creating new container\n")
|
Logger.Infof("creating new container")
|
||||||
new, err := cli.ContainerCreate(ctx, oldContainer.Config, hostConfig, &network.NetworkingConfig{
|
new, err := cli.ContainerCreate(ctx, oldContainer.Config, hostConfig, &network.NetworkingConfig{
|
||||||
EndpointsConfig: oldContainer.NetworkSettings.Networks,
|
EndpointsConfig: oldContainer.NetworkSettings.Networks,
|
||||||
}, name)
|
}, name)
|
||||||
@ -147,13 +156,13 @@ func (lc LabeledContainer) UpdateTo(cli *client.Client, tag Tag) error {
|
|||||||
return err
|
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{})
|
err = cli.ContainerStart(ctx, new.ID, types.ContainerStartOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Removing old container\n")
|
Logger.Infof("removing old container")
|
||||||
err = cli.ContainerRemove(ctx, oldContainer.ID, types.ContainerRemoveOptions{
|
err = cli.ContainerRemove(ctx, oldContainer.ID, types.ContainerRemoveOptions{
|
||||||
RemoveVolumes: false,
|
RemoveVolumes: false,
|
||||||
RemoveLinks: false,
|
RemoveLinks: false,
|
||||||
|
|||||||
1
go.mod
1
go.mod
@ -16,4 +16,5 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/sirupsen/logrus v1.4.2
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||||
)
|
)
|
||||||
|
|||||||
3
go.sum
3
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 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
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-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-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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|||||||
72
main.go
72
main.go
@ -3,78 +3,32 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkForUpdates() {
|
var (
|
||||||
cli, err := client.NewEnvClient()
|
Logger logrus.Logger = *logrus.New()
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
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) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
go checkForUpdates()
|
TriggerScan()
|
||||||
fmt.Fprintf(w, "OK")
|
fmt.Fprintf(w, "OK")
|
||||||
})
|
})
|
||||||
|
|
||||||
fs := http.FileServer(http.Dir("static/"))
|
fs := http.FileServer(http.Dir("static/"))
|
||||||
http.Handle("/static/", http.StripPrefix("/static/", fs))
|
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)
|
err := http.ListenAndServe(addr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
11
registry.go
11
registry.go
@ -20,7 +20,14 @@ func anonymousClient() (*registry.Registry, error) {
|
|||||||
username := "" // anonymous
|
username := "" // anonymous
|
||||||
password := "" // 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) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
out := []Tag{}
|
var out []Tag
|
||||||
|
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
digest, err := hub.ManifestDigest(repository, tag)
|
digest, err := hub.ManifestDigest(repository, tag)
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//"fmt"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/coreos/go-semver/semver"
|
"github.com/coreos/go-semver/semver"
|
||||||
@ -16,7 +15,7 @@ type SemVerTag struct {
|
|||||||
version semver.Version
|
version semver.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a
|
// Return a
|
||||||
// Returns nil if the tag did not parse as semver
|
// Returns nil if the tag did not parse as semver
|
||||||
func parseTagAsSemVer(tag string) *SemVerTag {
|
func parseTagAsSemVer(tag string) *SemVerTag {
|
||||||
var prefix string
|
var prefix string
|
||||||
|
|||||||
101
worker.go
Normal file
101
worker.go
Normal file
@ -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")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user