Compare commits

...

24 Commits

Author SHA1 Message Date
IamTheFij ee3bc789ea Fix linting
continuous-integration/drone/push Build is passing Details
2022-04-07 13:26:31 -07:00
IamTheFij 4ff321933b Fix pre-commit url
continuous-integration/drone/push Build is failing Details
2022-04-07 10:15:27 -07:00
IamTheFij 5ba2fc721b fix typo
continuous-integration/drone/push Build is failing Details
2022-04-05 22:00:52 -07:00
IamTheFij 620640dada Update go and Slog versions
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2021-09-16 14:19:11 -07:00
IamTheFij 2ad1a94f4f Fix sha issue fully 2021-09-16 14:13:45 -07:00
IamTheFij 1f8782a8f9 Fixing readme
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2021-09-15 10:04:48 -07:00
IamTheFij 17258861ba Add parsing of image sha
continuous-integration/drone/push Build was killed Details
Not yet using it in comparison
2021-09-15 10:00:01 -07:00
IamTheFij 2cfe9429b7 update README.md
Give simple installation instructions
2021-07-28 06:26:50 -07:00
IamTheFij ba702d2ab9 Update to 1.0.0
continuous-integration/drone/tag Build is passing Details
2021-01-07 14:36:37 -05:00
IamTheFij 8f9c5b6a91 Update test drone pipeline 2020-12-04 13:19:25 -08:00
IamTheFij 4a173f4cb2 Update project name 2020-12-04 13:18:23 -08:00
IamTheFij fb2262c83e Update binary path in dockerfile
continuous-integration/drone/push Build is passing Details
2020-12-02 17:29:40 -08:00
IamTheFij e7ee16231b Treat docker.io as hub.docker.com
continuous-integration/drone/push Build is failing Details
continuous-integration/drone/tag Build is failing Details
2020-12-01 20:37:57 -08:00
IamTheFij 50c46b1b6e Add slog and less verbose logging by default 2020-12-01 20:27:50 -08:00
IamTheFij a8d7407093 Parse registry urls from image names 2020-12-01 20:27:27 -08:00
IamTheFij 016129b5ed Fix version tagging and remove release uploads
continuous-integration/drone/push Build is failing Details
continuous-integration/drone/tag Build is failing Details
Binaries are being rejected for now. Need up update my Gitea
2020-12-01 19:41:15 -08:00
IamTheFij 533f4963be Move dist files to subdirectory
continuous-integration/drone/push Build was killed Details
continuous-integration/drone/tag Build is failing Details
2020-12-01 19:15:07 -08:00
IamTheFij bdb7a06c19 Add release title
continuous-integration/drone/push Build was killed Details
continuous-integration/drone/tag Build is passing Details
2020-12-01 19:04:48 -08:00
IamTheFij 13a2a599dc Update Dockerfiles to ignore linting for cert installation
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is failing Details
2020-12-01 18:42:25 -08:00
IamTheFij c6855a1b73 Add drone builds
continuous-integration/drone/tag Build is failing Details
2020-12-01 18:32:35 -08:00
IamTheFij 1a89933feb Add Makefile and Dockerfiles for distribution 2020-12-01 18:20:26 -08:00
IamTheFij 7ee081f9f0 Add some command line flags 2020-12-01 17:25:40 -08:00
IamTheFij e08a3cf14a Remove sort interface 2020-12-01 17:11:30 -08:00
IamTheFij cb0ffa3497 Initial working version 2020-12-01 17:07:06 -08:00
14 changed files with 908 additions and 3 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
tags

129
.drone.yml Normal file
View File

@ -0,0 +1,129 @@
---
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

2
.gitignore vendored
View File

@ -14,4 +14,4 @@
# Dependency directories (remove the comment below to include it)
# vendor/
dist/

20
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,20 @@
---
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

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
# 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/
ARG ARCH=amd64
COPY dist/tag-checker-linux-${ARCH} /tag-checker
ENTRYPOINT [ "/tag-checker" ]

30
Dockerfile.multi-stage Normal file
View File

@ -0,0 +1,30 @@
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 Normal file
View File

@ -0,0 +1,132 @@
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 .

View File

@ -1,3 +1,19 @@
# docker-check-version-updates
# tag-checker
Checks current running containers for newer tags according to semver
Checks current running containers for newer tags according to semver
Usage:
tag-checker -h
-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`.

7
docker-compose.yml Normal file
View File

@ -0,0 +1,7 @@
version: "3.4"
services:
main:
build: .
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"

32
go.mod Normal file
View File

@ -0,0 +1,32 @@
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 Normal file
View File

@ -0,0 +1,122 @@
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 Normal file
View File

@ -0,0 +1,247 @@
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)
}
}

132
main_test.go Normal file
View File

@ -0,0 +1,132 @@
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,
)
}
}
}

25
manifest.tmpl Normal file
View File

@ -0,0 +1,25 @@
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