Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
IamTheFij | 54207b4f51 |
|
@ -1 +0,0 @@
|
||||||
tags
|
|
129
.drone.yml
129
.drone.yml
|
@ -1,129 +0,0 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: test
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# - name: test
|
|
||||||
# image: golang:1.15
|
|
||||||
# commands:
|
|
||||||
# - make test
|
|
||||||
|
|
||||||
- name: check
|
|
||||||
image: iamthefij/drone-pre-commit:personal
|
|
||||||
commands:
|
|
||||||
- make check
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: publish
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- tag
|
|
||||||
refs:
|
|
||||||
- refs/heads/master
|
|
||||||
- refs/tags/v*
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: golang:1.17
|
|
||||||
environment:
|
|
||||||
VERSION: ${DRONE_TAG:-${DRONE_COMMIT}}
|
|
||||||
commands:
|
|
||||||
- make build-all-static
|
|
||||||
|
|
||||||
- name: gitea release
|
|
||||||
image: plugins/gitea-release
|
|
||||||
settings:
|
|
||||||
title: ${DRONE_TAG}
|
|
||||||
files: dist/*
|
|
||||||
checksum:
|
|
||||||
- md5
|
|
||||||
- sha1
|
|
||||||
- sha256
|
|
||||||
- sha512
|
|
||||||
base_url:
|
|
||||||
from_secret: gitea_base_url
|
|
||||||
api_key:
|
|
||||||
from_secret: gitea_token
|
|
||||||
when:
|
|
||||||
event: tag
|
|
||||||
|
|
||||||
- name: push image - arm
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: iamthefij/tag-checker
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: linux-arm
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
build_args:
|
|
||||||
- ARCH=arm
|
|
||||||
- REPO=arm32v7
|
|
||||||
|
|
||||||
- name: push image - arm64
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: iamthefij/tag-checker
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: linux-arm64
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
build_args:
|
|
||||||
- ARCH=arm64
|
|
||||||
- REPO=arm64v8
|
|
||||||
|
|
||||||
- name: push image - amd64
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: iamthefij/tag-checker
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: linux-amd64
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish manifest
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
spec: manifest.tmpl
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
username:
|
|
||||||
from_secret: docker_username # pragma: whitelist secret
|
|
||||||
password:
|
|
||||||
from_secret: docker_password # pragma: whitelist secret
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: notify
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
- publish
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
status:
|
|
||||||
- failure
|
|
||||||
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- name: notify
|
|
||||||
image: drillster/drone-email
|
|
||||||
settings:
|
|
||||||
host:
|
|
||||||
from_secret: SMTP_HOST # pragma: whitelist secret
|
|
||||||
username:
|
|
||||||
from_secret: SMTP_USER # pragma: whitelist secret
|
|
||||||
password:
|
|
||||||
from_secret: SMTP_PASS # pragma: whitelist secret
|
|
||||||
from: drone@iamthefij.com
|
|
|
@ -14,4 +14,6 @@
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
dist/
|
|
||||||
|
__pycache__/
|
||||||
|
venv/
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
repos:
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v2.4.0
|
|
||||||
hooks:
|
|
||||||
- id: check-added-large-files
|
|
||||||
- id: trailing-whitespace
|
|
||||||
- id: end-of-file-fixer
|
|
||||||
- id: check-merge-conflict
|
|
||||||
- repo: https://github.com/dnephin/pre-commit-golang
|
|
||||||
rev: v0.3.5
|
|
||||||
hooks:
|
|
||||||
- id: go-fmt
|
|
||||||
- id: go-imports
|
|
||||||
# - id: gometalinter
|
|
||||||
# - id: golangci-lint
|
|
||||||
- repo: https://github.com/IamTheFij/docker-pre-commit
|
|
||||||
rev: v2.0.0
|
|
||||||
hooks:
|
|
||||||
- id: hadolint-system
|
|
16
Dockerfile
16
Dockerfile
|
@ -1,12 +1,10 @@
|
||||||
# hadolint ignore=DL3007
|
FROM python:3
|
||||||
FROM alpine:latest as certs
|
|
||||||
# hadolint ignore=DL3018
|
|
||||||
RUN apk --no-cache add ca-certificates
|
|
||||||
|
|
||||||
FROM scratch
|
RUN mkdir -p /app
|
||||||
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
WORKDIR /app
|
||||||
|
|
||||||
ARG ARCH=amd64
|
COPY ./requirements.txt /app/
|
||||||
COPY dist/tag-checker-linux-${ARCH} /tag-checker
|
RUN pip install -r ./requirements.txt
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
ENTRYPOINT [ "/tag-checker" ]
|
CMD ["python", "./main.py"]
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
ARG REPO=library
|
|
||||||
FROM golang:1.17-alpine AS builder
|
|
||||||
|
|
||||||
# hadolint ignore=DL3018
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
# hadolint ignore=DL3059
|
|
||||||
RUN mkdir /app
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY ./go.mod ./go.sum /app/
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
COPY ./main.go /app/
|
|
||||||
|
|
||||||
ARG ARCH=amd64
|
|
||||||
ARG VERSION=dev
|
|
||||||
ENV CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH}
|
|
||||||
RUN go build -ldflags "-X main.version=${VERSION}" -a -installsuffix nocgo -o tag-checker .
|
|
||||||
|
|
||||||
# hadolint ignore=DL3007
|
|
||||||
FROM alpine:latest as certs
|
|
||||||
# hadolint ignore=DL3018
|
|
||||||
RUN apk --no-cache add ca-certificates
|
|
||||||
|
|
||||||
FROM scratch
|
|
||||||
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
|
||||||
COPY --from=builder /app/tag-checker /
|
|
||||||
|
|
||||||
ENTRYPOINT [ "/tag-checker" ]
|
|
132
Makefile
132
Makefile
|
@ -1,132 +0,0 @@
|
||||||
NAME ?= tag-checker
|
|
||||||
OUTPUT ?= dist/$(NAME)
|
|
||||||
DOCKER_TAG ?= $(NAME)-dev-${USER}
|
|
||||||
GIT_TAG_NAME := $(shell git tag -l --contains HEAD)
|
|
||||||
GIT_SHA := $(shell git rev-parse HEAD)
|
|
||||||
VERSION ?= $(if $(GIT_TAG_NAME),$(GIT_TAG_NAME),$(GIT_SHA))
|
|
||||||
|
|
||||||
GOFILES = *.go go.mod go.sum
|
|
||||||
|
|
||||||
.PHONY: default
|
|
||||||
default: build
|
|
||||||
|
|
||||||
.PHONY: all
|
|
||||||
all: check test itest
|
|
||||||
|
|
||||||
# Downloads dependencies into vendor directory
|
|
||||||
vendor: $(GOFILES)
|
|
||||||
go mod vendor
|
|
||||||
|
|
||||||
# Runs the application, useful while developing
|
|
||||||
.PHONY: run
|
|
||||||
run:
|
|
||||||
go run .
|
|
||||||
|
|
||||||
.PHONY: test
|
|
||||||
test:
|
|
||||||
go test -coverprofile=coverage.out
|
|
||||||
go tool cover -func=coverage.out
|
|
||||||
@go tool cover -func=coverage.out | awk -v target=75.0% \
|
|
||||||
'/^total:/ { print "Total coverage: " $$3 " Minimum coverage: " target; if ($$3+0.0 >= target+0.0) print "ok"; else { print "fail"; exit 1; } }'
|
|
||||||
|
|
||||||
# Installs pre-commit hooks
|
|
||||||
.PHONY: install-hooks
|
|
||||||
install-hooks:
|
|
||||||
pre-commit install --install-hooks
|
|
||||||
|
|
||||||
# Runs pre-commit checks on files
|
|
||||||
.PHONY: check
|
|
||||||
check:
|
|
||||||
pre-commit run --all-files
|
|
||||||
|
|
||||||
# Output target
|
|
||||||
$(OUTPUT): $(GOFILES)
|
|
||||||
@echo Version: $(VERSION)
|
|
||||||
go build -ldflags '-X "main.version=$(VERSION)"' -o $(OUTPUT)
|
|
||||||
|
|
||||||
# Alias for building
|
|
||||||
.PHONY: build
|
|
||||||
build: $(OUTPUT)
|
|
||||||
|
|
||||||
$(OUTPUT)-darwin-amd64: $(GOFILES)
|
|
||||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 \
|
|
||||||
go build -ldflags '-X "main.version=$(VERSION)"' -a -installsuffix nocgo \
|
|
||||||
-o $(OUTPUT)-darwin-amd64
|
|
||||||
|
|
||||||
$(OUTPUT)-linux-amd64: $(GOFILES)
|
|
||||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
|
|
||||||
go build -ldflags '-X "main.version=$(VERSION)"' -a -installsuffix nocgo \
|
|
||||||
-o $(OUTPUT)-linux-amd64
|
|
||||||
|
|
||||||
$(OUTPUT)-linux-arm: $(GOFILES)
|
|
||||||
GOOS=linux GOARCH=arm CGO_ENABLED=0 \
|
|
||||||
go build -ldflags '-X "main.version=$(VERSION)"' -a -installsuffix nocgo \
|
|
||||||
-o $(OUTPUT)-linux-arm
|
|
||||||
|
|
||||||
$(OUTPUT)-linux-arm64: $(GOFILES)
|
|
||||||
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 \
|
|
||||||
go build -ldflags '-X "main.version=$(VERSION)"' -a -installsuffix nocgo \
|
|
||||||
-o $(OUTPUT)-linux-arm64
|
|
||||||
|
|
||||||
.PHONY: build-linux-static
|
|
||||||
build-linux-static: $(OUTPUT)-linux-amd64 $(OUTPUT)-linux-arm $(OUTPUT)-linux-arm64
|
|
||||||
|
|
||||||
.PHONY: build-all-static
|
|
||||||
build-all-static: $(OUTPUT)-darwin-amd64 build-linux-static
|
|
||||||
|
|
||||||
# Cleans all build artifacts
|
|
||||||
.PHONY: clean
|
|
||||||
clean:
|
|
||||||
rm -f $(OUTPUT)
|
|
||||||
rm -f $(OUTPUT)-linux-*
|
|
||||||
|
|
||||||
# Cleans vendor directory
|
|
||||||
.PHONY: clean-vendor
|
|
||||||
clean-vendor:
|
|
||||||
rm -fr ./vendor
|
|
||||||
|
|
||||||
.PHONY: docker-build
|
|
||||||
docker-build: $(OUTPUT)-linux-amd64
|
|
||||||
docker build . -t $(DOCKER_TAG)-linux-amd64
|
|
||||||
|
|
||||||
# Cross build for arm architechtures
|
|
||||||
.PHONY: docker-build-arm
|
|
||||||
docker-build-arm: $(OUTPUT)-linux-arm
|
|
||||||
docker build --build-arg REPO=arm32v7 --build-arg ARCH=arm . -t $(DOCKER_TAG)-linux-arm
|
|
||||||
|
|
||||||
.PHONY: docker-build-arm
|
|
||||||
docker-build-arm64: $(OUTPUT)-linux-arm64
|
|
||||||
docker build --build-arg REPO=arm64v8 --build-arg ARCH=arm64 . -t $(DOCKER_TAG)-linux-arm64
|
|
||||||
|
|
||||||
.PHONY: docker-run
|
|
||||||
docker-run: docker-build
|
|
||||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --name $(DOCKER_TAG)-run $(DOCKER_TAG)-linux-amd64
|
|
||||||
|
|
||||||
# Cross run on host architechture
|
|
||||||
.PHONY: docker-run-arm
|
|
||||||
docker-run-arm: docker-build-arm
|
|
||||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --name $(DOCKER_TAG)-run $(DOCKER_TAG)-linux-arm
|
|
||||||
|
|
||||||
.PHONY: docker-run-arm64
|
|
||||||
docker-run-arm64: docker-build-arm64
|
|
||||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --name $(DOCKER_TAG)-run $(DOCKER_TAG)-linux-arm64
|
|
||||||
|
|
||||||
# Multi stage builds
|
|
||||||
.PHONY: docker-staged-build
|
|
||||||
docker-staged-build:
|
|
||||||
docker build --build-arg VERSION=$(VERSION) \
|
|
||||||
-t $(DOCKER_TAG)-linux-amd64 \
|
|
||||||
-f Dockerfile.multi-stage .
|
|
||||||
|
|
||||||
# Cross build for arm architechtures
|
|
||||||
.PHONY: docker-staged-build-arm
|
|
||||||
docker-staged-build-arm:
|
|
||||||
docker build --build-arg VERSION=$(VERSION) \
|
|
||||||
--build-arg REPO=arm32v7 --build-arg ARCH=arm -t $(DOCKER_TAG)-linux-arm \
|
|
||||||
-f Dockerfile.multi-stage .
|
|
||||||
|
|
||||||
.PHONY: docker-staged-build-arm
|
|
||||||
docker-staged-build-arm64:
|
|
||||||
docker build --build-arg VERSION=$(VERSION) \
|
|
||||||
--build-arg REPO=arm64v8 --build-arg ARCH=arm64 -t $(DOCKER_TAG)-linux-arm64 \
|
|
||||||
-f Dockerfile.multi-stage .
|
|
17
README.md
17
README.md
|
@ -1,19 +1,8 @@
|
||||||
# tag-checker
|
# docker-check-version-updates
|
||||||
|
|
||||||
Checks current running containers for newer tags according to semver
|
Checks current running containers for newer tags according to semver
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
tag-checker -h
|
python -m venv venv
|
||||||
|
./venv/bin/python main.py
|
||||||
-max-pages int
|
|
||||||
max number of pages to retrieve from registry (default 10)
|
|
||||||
-registry-url string
|
|
||||||
base url of the registry you want to check against (default "https://registry.hub.docker.com")
|
|
||||||
-version
|
|
||||||
display the version of dockron and exit
|
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
You can either install the binary from the Releases tab or use the image hosted on Docker Hub at `iamthefij/tag-checker`.
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
version: "3.4"
|
|
||||||
|
|
||||||
services:
|
|
||||||
main:
|
|
||||||
build: .
|
|
||||||
volumes:
|
|
||||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
|
32
go.mod
32
go.mod
|
@ -1,32 +0,0 @@
|
||||||
module github.com/iamthefij/tag-checker
|
|
||||||
|
|
||||||
go 1.17
|
|
||||||
|
|
||||||
require (
|
|
||||||
git.iamthefij.com/iamthefij/slog v1.3.0
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
|
||||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
|
||||||
github.com/containerd/containerd v1.4.3 // indirect
|
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible
|
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
|
||||||
github.com/gogo/protobuf v1.3.1 // indirect
|
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
|
||||||
google.golang.org/grpc v1.33.2 // indirect
|
|
||||||
gotest.tools v2.2.0+incompatible // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/golang/protobuf v1.4.1 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
|
|
||||||
google.golang.org/protobuf v1.25.0 // indirect
|
|
||||||
)
|
|
122
go.sum
122
go.sum
|
@ -1,122 +0,0 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
git.iamthefij.com/iamthefij/slog v1.3.0 h1:4Hu5PQvDrW5e3FrTS3q2iIXW0iPvhNY/9qJsqDR3K3I=
|
|
||||||
git.iamthefij.com/iamthefij/slog v1.3.0/go.mod h1:1RUj4hcCompZkAxXCRfUX786tb3cM/Zpkn97dGfUfbg=
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
|
|
||||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/containerd/containerd v1.4.3 h1:ijQT13JedHSHrQGWFcGEwzcNKrAGIiZ+jSD5QQG07SY=
|
|
||||||
github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible h1:SiUATuP//KecDjpOK2tvZJgeScYAklvyjfK8JZlU6fo=
|
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
|
||||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
|
||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
|
||||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
|
||||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
|
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
247
main.go
247
main.go
|
@ -1,247 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.iamthefij.com/iamthefij/slog"
|
|
||||||
dockerTypes "github.com/docker/docker/api/types"
|
|
||||||
dockerClient "github.com/docker/docker/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// defaultRegistryBaseURL is the base URL of the docker registry
|
|
||||||
defaultRegistryBaseURL = "https://hub.docker.com"
|
|
||||||
// maxPages is the max number of pages to fetch from docker registry results
|
|
||||||
maxPages = 10
|
|
||||||
|
|
||||||
// Regexp used to extract tag information
|
|
||||||
// The general format is "(registry/namespace/image):v(version)-(description)@(sha)"
|
|
||||||
tagRegexp = regexp.MustCompile(`([a-zA-Z0-9-_/.]+):[vV]{0,1}([0-9.]+)(-([a-zA-Z0-9_-]*)){0,1}(@([:0-9a-z]+)){0,1}`)
|
|
||||||
// version of tag checker
|
|
||||||
version = "dev"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ImageTag is wraps an image and tag values for a container
|
|
||||||
type ImageTag struct {
|
|
||||||
ImageTag string
|
|
||||||
Registry string
|
|
||||||
Image string
|
|
||||||
TagDesc string
|
|
||||||
TagSha string
|
|
||||||
Version string
|
|
||||||
VersionParts []int
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsComparable will return true if to images share the same base, description, and version resolution
|
|
||||||
func (thisTag ImageTag) IsComparable(otherTag ImageTag) bool {
|
|
||||||
return thisTag.Image == otherTag.Image && thisTag.TagDesc == otherTag.TagDesc && len(thisTag.VersionParts) == len(otherTag.VersionParts)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNewerThan will return true if two tags are comparable and this tag is newer than the one passed in
|
|
||||||
func (thisTag ImageTag) IsNewerThan(otherTag ImageTag) bool {
|
|
||||||
return thisTag.CompareTo(otherTag) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompareTo compares two ImageTags. It will return 0 if they are not-comparable or equal 1 if newer and -1 if less
|
|
||||||
func (thisTag ImageTag) CompareTo(otherTag ImageTag) int {
|
|
||||||
if !thisTag.IsComparable(otherTag) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range thisTag.VersionParts {
|
|
||||||
if thisTag.VersionParts[i] > otherTag.VersionParts[i] {
|
|
||||||
return 1
|
|
||||||
} else if thisTag.VersionParts[i] < otherTag.VersionParts[i] {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything must be equal
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseImageTag parses an image and tag name to a struct
|
|
||||||
func ParseImageTag(imageTag string) (ImageTag, error) {
|
|
||||||
results := tagRegexp.FindStringSubmatch(imageTag)
|
|
||||||
if results == nil || results[0] == "" {
|
|
||||||
return ImageTag{}, fmt.Errorf("could not recognize versions in %s", imageTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract image name with repo
|
|
||||||
image := results[1]
|
|
||||||
registry := ""
|
|
||||||
switch strings.Count(image, "/") {
|
|
||||||
case 0:
|
|
||||||
image = "library/" + image
|
|
||||||
case 2:
|
|
||||||
parts := strings.Split(image, "/")
|
|
||||||
if parts[0] != "docker.io" {
|
|
||||||
registry = parts[0]
|
|
||||||
}
|
|
||||||
image = strings.Join(parts[1:], "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract version number
|
|
||||||
version := results[2]
|
|
||||||
versionParts := []int{}
|
|
||||||
var verPart int
|
|
||||||
var err error
|
|
||||||
for _, v := range strings.Split(version, ".") {
|
|
||||||
verPart, err = strconv.Atoi(v)
|
|
||||||
if err != nil {
|
|
||||||
return ImageTag{}, err
|
|
||||||
}
|
|
||||||
versionParts = append(versionParts, verPart)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ImageTag{
|
|
||||||
ImageTag: imageTag,
|
|
||||||
Image: image,
|
|
||||||
Registry: registry,
|
|
||||||
Version: version,
|
|
||||||
VersionParts: versionParts,
|
|
||||||
TagDesc: results[4],
|
|
||||||
TagSha: results[6],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getJSON(url string, response interface{}) error {
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// defer func() { _ = resp.Body.Close() }()
|
|
||||||
defer resp.Body.Close()
|
|
||||||
decoder := json.NewDecoder(resp.Body)
|
|
||||||
err = decoder.Decode(response)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func listTags(current ImageTag) ([]ImageTag, error) {
|
|
||||||
var err error
|
|
||||||
results := []ImageTag{}
|
|
||||||
|
|
||||||
type tagsResponse struct {
|
|
||||||
Count int
|
|
||||||
Next string
|
|
||||||
Previous string
|
|
||||||
Results []struct {
|
|
||||||
ID int
|
|
||||||
LastUpdated string `json:"last_updated"`
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registryBaseURL := defaultRegistryBaseURL
|
|
||||||
if current.Registry != "" {
|
|
||||||
registryBaseURL = fmt.Sprintf("https://%s", current.Registry)
|
|
||||||
}
|
|
||||||
url := fmt.Sprintf("%s/v2/repositories/%s/tags", registryBaseURL, current.Image)
|
|
||||||
pageCount := 0
|
|
||||||
var response tagsResponse
|
|
||||||
var newTag ImageTag
|
|
||||||
for url != "" && pageCount <= maxPages {
|
|
||||||
err = getJSON(url, &response)
|
|
||||||
if err != nil {
|
|
||||||
slog.Errorf("[%s] Could not get JSON response from url %s: %v", current.ImageTag, url, err)
|
|
||||||
return results, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tag := range response.Results {
|
|
||||||
newTag, err = ParseImageTag(fmt.Sprintf("%s:%s", current.Image, tag.Name))
|
|
||||||
if err == nil {
|
|
||||||
results = append(results, newTag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url = response.Next
|
|
||||||
pageCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNewerTags(current ImageTag) ([]ImageTag, error) {
|
|
||||||
newerTags := []ImageTag{}
|
|
||||||
tags, err := listTags(current)
|
|
||||||
if err != nil {
|
|
||||||
slog.Errorf("[%s] Could not list tags: %v", current.ImageTag, err)
|
|
||||||
return newerTags, err
|
|
||||||
}
|
|
||||||
for _, tag := range tags {
|
|
||||||
if tag.IsNewerThan(current) {
|
|
||||||
newerTags = append(newerTags, tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sort tags with newest first
|
|
||||||
sort.Slice(newerTags, func(i, j int) bool { return newerTags[i].CompareTo(newerTags[j]) == 1 })
|
|
||||||
return newerTags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.StringVar(&defaultRegistryBaseURL, "registry-url", defaultRegistryBaseURL, "base url of the registry you want to check against")
|
|
||||||
flag.IntVar(&maxPages, "max-pages", maxPages, "max number of pages to retrieve from registry")
|
|
||||||
var showVersion = flag.Bool("version", false, "display the version and exit")
|
|
||||||
flag.BoolVar(&slog.DebugLevel, "debug", false, "show debug logs")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// Print version if asked
|
|
||||||
if *showVersion {
|
|
||||||
fmt.Println("version:", version)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
dockerClient, err := dockerClient.NewClientWithOpts(dockerClient.FromEnv)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Could not initialize docker client")
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
containers, err := dockerClient.ContainerList(context.Background(), dockerTypes.ContainerListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Could list container from docker client")
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasUpdate := false
|
|
||||||
images := map[string]bool{}
|
|
||||||
for _, container := range containers {
|
|
||||||
images[container.Image] = true
|
|
||||||
}
|
|
||||||
for image := range images {
|
|
||||||
slog.Debugf("[%s] Checking for updates...", image)
|
|
||||||
it, err := ParseImageTag(image)
|
|
||||||
if err != nil {
|
|
||||||
slog.Debugf("[%s] Can't parse tag: %v", image, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newerTags, err := getNewerTags(it)
|
|
||||||
slog.OnErrPanicf(err, "[%s] failed getting new tags", image)
|
|
||||||
if len(newerTags) == 0 {
|
|
||||||
slog.Debugf("[%s] No newer versions found", image)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hasUpdate = true
|
|
||||||
if slog.DebugLevel {
|
|
||||||
slog.Infof("[%s] Newer version found! Recommended update to %s", image, newerTags[0].ImageTag)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("[%s] Newer version found! Recommended update to %s\n", image, newerTags[0].ImageTag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasUpdate {
|
|
||||||
os.Exit(10)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
"""
|
||||||
|
Checks to see if newer tagged versions of running images exist
|
||||||
|
|
||||||
|
When a newer tag based on semver is found, the tags will be printed and the script will exit with
|
||||||
|
a non-zero code.
|
||||||
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Generator
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import docker # type: ignore
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
class NotComparableException(ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ImageTag(object):
|
||||||
|
image_tag: str
|
||||||
|
image: str
|
||||||
|
full_tag: str
|
||||||
|
version: str
|
||||||
|
tag_desc: str
|
||||||
|
version_parts: List[int]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(cls, image_tag):
|
||||||
|
image, _, full_tag = image_tag.partition(":")
|
||||||
|
version, _, tag_desc = full_tag.partition("-")
|
||||||
|
# Remove leading v
|
||||||
|
if version[0].lower() == "v":
|
||||||
|
version = version[1:]
|
||||||
|
try:
|
||||||
|
version_parts = [int(p) for p in version.split(".")]
|
||||||
|
except ValueError:
|
||||||
|
version_parts = []
|
||||||
|
return ImageTag(
|
||||||
|
image_tag=image_tag,
|
||||||
|
image=image,
|
||||||
|
full_tag=full_tag,
|
||||||
|
version=version,
|
||||||
|
tag_desc=tag_desc,
|
||||||
|
version_parts=version_parts,
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_same_image(self, other):
|
||||||
|
return self.image == other.image
|
||||||
|
|
||||||
|
def is_same_type(self, other):
|
||||||
|
return self.tag_desc == other.tag_desc
|
||||||
|
|
||||||
|
def is_same_grain(self, other):
|
||||||
|
return len(self.version_parts) == len(other.version_parts)
|
||||||
|
|
||||||
|
def is_comparable(self, other):
|
||||||
|
return all(
|
||||||
|
(
|
||||||
|
self.is_same_image(other),
|
||||||
|
self.is_same_type(other),
|
||||||
|
self.is_same_grain(other),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not self.is_comparable(other):
|
||||||
|
raise NotComparableException()
|
||||||
|
|
||||||
|
return self.version_parts == other.version_parts
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if not self.is_comparable(other):
|
||||||
|
raise NotComparableException()
|
||||||
|
|
||||||
|
for s, o in zip(self.version_parts, other.version_parts):
|
||||||
|
if s < o:
|
||||||
|
return True
|
||||||
|
elif s > o:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_newer_than(self, other):
|
||||||
|
return self.is_comparable(other) and self > other
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_tags(image_name: str) -> Generator[ImageTag, None, None]:
|
||||||
|
"""Generates all tags for a given image"""
|
||||||
|
if "/" not in image_name:
|
||||||
|
image_name = f"library/{image_name}"
|
||||||
|
url = "https://registry.hub.docker.com/v2/repositories/{}/tags".format(
|
||||||
|
image_name,
|
||||||
|
)
|
||||||
|
page_count = 0
|
||||||
|
max_pages = 1000
|
||||||
|
while url and page_count <= max_pages:
|
||||||
|
data = requests.get(url).json()
|
||||||
|
for tag in data["results"]:
|
||||||
|
try:
|
||||||
|
yield ImageTag.from_str(f"{image_name}:{tag['name']}")
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
url = data["next"]
|
||||||
|
page_count += 1
|
||||||
|
|
||||||
|
|
||||||
|
def generate_message(current: ImageTag, newer_tags: List[ImageTag]) -> str:
|
||||||
|
if not current.version_parts:
|
||||||
|
return f"[{current.image_tag}] No numeric version recognized"
|
||||||
|
if not newer_tags:
|
||||||
|
return f"[{current.image_tag}] No newer tags found for image tag"
|
||||||
|
else:
|
||||||
|
newer_tags = list(reversed(sorted(newer_tags)))
|
||||||
|
tags_list = ", ".join(tag.version for tag in newer_tags)
|
||||||
|
latest_tag = newer_tags[0].image_tag
|
||||||
|
return f"[{current.image_tag}] New versions found {tags_list}. Recommended update to {latest_tag}"
|
||||||
|
|
||||||
|
|
||||||
|
def run() -> int:
|
||||||
|
client = docker.from_env()
|
||||||
|
running_images = {container.image.tags[0]
|
||||||
|
for container in client.containers.list()}
|
||||||
|
|
||||||
|
has_update = False
|
||||||
|
for image_name in running_images:
|
||||||
|
current = ImageTag.from_str(image_name)
|
||||||
|
|
||||||
|
newer_tags: List[ImageTag] = []
|
||||||
|
if current.version_parts:
|
||||||
|
newer_tags = [
|
||||||
|
tag for tag in get_all_tags(current.image) if tag.is_newer_than(current)
|
||||||
|
]
|
||||||
|
has_update |= bool(newer_tags)
|
||||||
|
print(generate_message(current, newer_tags))
|
||||||
|
|
||||||
|
if has_update:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
exit(run())
|
132
main_test.go
132
main_test.go
|
@ -1,132 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTagParsing(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
tag string
|
|
||||||
expected ImageTag
|
|
||||||
expectsError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"image",
|
|
||||||
ImageTag{},
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"image:latest",
|
|
||||||
ImageTag{},
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"user/image:v1",
|
|
||||||
ImageTag{
|
|
||||||
ImageTag: "user/image:v1",
|
|
||||||
Image: "user/image",
|
|
||||||
Version: "1",
|
|
||||||
VersionParts: []int{1},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"image:v1",
|
|
||||||
ImageTag{
|
|
||||||
ImageTag: "image:v1",
|
|
||||||
Image: "library/image",
|
|
||||||
Version: "1",
|
|
||||||
VersionParts: []int{1},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"image:v1.0",
|
|
||||||
ImageTag{
|
|
||||||
ImageTag: "image:v1.0",
|
|
||||||
Image: "library/image",
|
|
||||||
Version: "1.0",
|
|
||||||
VersionParts: []int{1, 0},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"image:v1.0.0",
|
|
||||||
ImageTag{
|
|
||||||
ImageTag: "image:v1.0.0",
|
|
||||||
Image: "library/image",
|
|
||||||
Version: "1.0.0",
|
|
||||||
VersionParts: []int{1, 0, 0},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"image:v1.0.0-desc",
|
|
||||||
ImageTag{
|
|
||||||
ImageTag: "image:v1.0.0-desc",
|
|
||||||
Image: "library/image",
|
|
||||||
TagDesc: "desc",
|
|
||||||
Version: "1.0.0",
|
|
||||||
VersionParts: []int{1, 0, 0},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"image:v1.0.0-desc@sha256:123abc",
|
|
||||||
ImageTag{
|
|
||||||
ImageTag: "image:v1.0.0-desc@sha256:123abc",
|
|
||||||
Image: "library/image",
|
|
||||||
TagDesc: "desc",
|
|
||||||
TagSha: "sha256:123abc",
|
|
||||||
Version: "1.0.0",
|
|
||||||
VersionParts: []int{1, 0, 0},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"image:v1.0.0@sha256:123abc",
|
|
||||||
ImageTag{
|
|
||||||
ImageTag: "image:v1.0.0@sha256:123abc",
|
|
||||||
Image: "library/image",
|
|
||||||
TagSha: "sha256:123abc",
|
|
||||||
Version: "1.0.0",
|
|
||||||
VersionParts: []int{1, 0, 0},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
// Real world example that had caused issues
|
|
||||||
{
|
|
||||||
"matrixdotorg/synapse:v1.41.0@sha256:75f2b3c35c047693f4f4334d4ceb97951de1ba12bd3c6182c8a2ee624d7c5ab7",
|
|
||||||
ImageTag{
|
|
||||||
ImageTag: "matrixdotorg/synapse:v1.41.0@sha256:75f2b3c35c047693f4f4334d4ceb97951de1ba12bd3c6182c8a2ee624d7c5ab7",
|
|
||||||
Image: "matrixdotorg/synapse",
|
|
||||||
TagSha: "sha256:75f2b3c35c047693f4f4334d4ceb97951de1ba12bd3c6182c8a2ee624d7c5ab7",
|
|
||||||
Version: "1.41.0",
|
|
||||||
VersionParts: []int{1, 41, 0},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
result, err := ParseImageTag(c.tag)
|
|
||||||
if c.expectsError {
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("ParseImageTag(%s): expected erro but didn't get one", c.tag)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("ParseImageTag(%s): unexpected error: %v", c.tag, err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(result, c.expected) {
|
|
||||||
t.Errorf(
|
|
||||||
"ParseImageTag(%s): unexpected result. Actual: %+v Expected: %+v",
|
|
||||||
c.tag,
|
|
||||||
result,
|
|
||||||
c.expected,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
image: iamthefij/tag-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
-
|
|
||||||
image: iamthefij/tag-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
-
|
|
||||||
image: iamthefij/tag-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
-
|
|
||||||
image: iamthefij/tag-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
pyls
|
||||||
|
pyls-black
|
||||||
|
ipython
|
||||||
|
ipdb
|
||||||
|
mypy
|
|
@ -0,0 +1,2 @@
|
||||||
|
requests
|
||||||
|
docker
|
Loading…
Reference in New Issue