Compare commits
36 Commits
multi-arch
...
master
Author | SHA1 | Date | |
---|---|---|---|
0aafe9a389 | |||
98cbd29dc9 | |||
4e8a9cf929 | |||
15625a05fb | |||
f651d55786 | |||
5698f9bccd | |||
2a03ef21ad | |||
a60ff562c7 | |||
82dc4b82e7 | |||
a823590368 | |||
eb7cdb0d1f | |||
2808b07b09 | |||
a5f9b0866f | |||
e83d5b6784 | |||
9913442526 | |||
5c5fda3ddf | |||
916e518f5b | |||
f5b4867f68 | |||
7b231be5b1 | |||
050465b0aa | |||
bda0ce4b1f | |||
607d364d29 | |||
e9e555e5a2 | |||
6e324795d4 | |||
9632615a91 | |||
8bee376540 | |||
bb6c7f903a | |||
c89d1e314c | |||
a6b46bfca2 | |||
c9374f5e83 | |||
7af52c54b1 | |||
0e44c6e2cf | |||
33c945536a | |||
aa35a8271c | |||
6e74d3b93f | |||
44a54f1c7f |
87
.drone.yml
87
.drone.yml
|
@ -1,15 +1,25 @@
|
||||||
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: test
|
name: test
|
||||||
|
|
||||||
workspace:
|
|
||||||
base: /go/src/dockron
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build
|
- name: test
|
||||||
image: golang:1.11
|
image: golang:1.15
|
||||||
commands:
|
commands:
|
||||||
- go get -u github.com/golang/dep/cmd/dep
|
- make test
|
||||||
- make build
|
|
||||||
|
- name: check
|
||||||
|
image: iamthefij/drone-pre-commit:personal
|
||||||
|
environment:
|
||||||
|
SKIP: docker-compose-check
|
||||||
|
|
||||||
|
# - name: itest
|
||||||
|
# image: docker/compose:alpine-1.26.2
|
||||||
|
# environment:
|
||||||
|
# VERSION: ${DRONE_TAG:-${DRONE_COMMIT}}
|
||||||
|
# commands:
|
||||||
|
# - apk add make bash
|
||||||
|
# - make itest
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
|
@ -26,26 +36,13 @@ trigger:
|
||||||
- refs/heads/master
|
- refs/heads/master
|
||||||
- refs/tags/v*
|
- refs/tags/v*
|
||||||
|
|
||||||
workspace:
|
|
||||||
base: /go/src/dockron
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build
|
- name: build
|
||||||
image: golang:1.11
|
image: golang:1.15
|
||||||
|
environment:
|
||||||
|
VERSION: ${DRONE_TAG:-${DRONE_COMMIT}}
|
||||||
commands:
|
commands:
|
||||||
- go get -u github.com/golang/dep/cmd/dep
|
- make build-linux-static
|
||||||
- make build-all-static
|
|
||||||
|
|
||||||
- name: push image - amd64
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
repo: iamthefij/dockron
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: linux-amd64
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: push image - arm
|
- name: push image - arm
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
|
@ -59,7 +56,7 @@ steps:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
build_args:
|
build_args:
|
||||||
- ARCH=arm
|
- ARCH=arm
|
||||||
- REPO=arm32v6
|
- REPO=arm32v7
|
||||||
|
|
||||||
- name: push image - arm64
|
- name: push image - arm64
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
|
@ -72,8 +69,19 @@ steps:
|
||||||
password:
|
password:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
build_args:
|
build_args:
|
||||||
- ARCH=arm
|
- ARCH=arm64
|
||||||
- REPO=arm32v6
|
- REPO=arm64v8
|
||||||
|
|
||||||
|
- name: push image - amd64
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
repo: iamthefij/dockron
|
||||||
|
auto_tag: true
|
||||||
|
auto_tag_suffix: linux-amd64
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
|
||||||
- name: publish manifest
|
- name: publish manifest
|
||||||
image: plugins/manifest
|
image: plugins/manifest
|
||||||
|
@ -85,3 +93,28 @@ steps:
|
||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
password:
|
password:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
|
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -25,7 +25,11 @@ _testmain.go
|
||||||
*.prof
|
*.prof
|
||||||
|
|
||||||
# Output
|
# Output
|
||||||
|
coverage.out
|
||||||
dockron
|
dockron
|
||||||
dockron-linux-*
|
dockron-*
|
||||||
# deps
|
# deps
|
||||||
vendor/
|
vendor/
|
||||||
|
|
||||||
|
# Test output
|
||||||
|
itest/*_result.txt
|
||||||
|
|
23
.pre-commit-config.yaml
Normal file
23
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v3.4.0
|
||||||
|
hooks:
|
||||||
|
- id: check-added-large-files
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- repo: git://github.com/dnephin/pre-commit-golang
|
||||||
|
rev: v0.4.0
|
||||||
|
hooks:
|
||||||
|
- id: go-fmt
|
||||||
|
- id: go-imports
|
||||||
|
- id: golangci-lint
|
||||||
|
- repo: https://github.com/IamTheFij/docker-pre-commit
|
||||||
|
rev: v2.0.0
|
||||||
|
hooks:
|
||||||
|
- id: docker-compose-check
|
||||||
|
- repo: https://github.com/hadolint/hadolint
|
||||||
|
rev: v2.4.0
|
||||||
|
hooks:
|
||||||
|
- id: hadolint
|
21
Dockerfile
21
Dockerfile
|
@ -1,21 +1,6 @@
|
||||||
ARG REPO=library
|
FROM scratch
|
||||||
# FROM golang:1.11-alpine AS builder
|
|
||||||
#
|
|
||||||
# RUN apk add --no-cache git
|
|
||||||
# RUN go get -u github.com/golang/dep/cmd/dep
|
|
||||||
#
|
|
||||||
# WORKDIR /go/src/app/
|
|
||||||
# COPY ./Gopkg.* /go/src/app/
|
|
||||||
# RUN dep ensure --vendor-only
|
|
||||||
#
|
|
||||||
# COPY ./main.go /go/src/app/
|
|
||||||
#
|
|
||||||
# RUN CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} go build -a -installsuffix nocgo -o dockron .
|
|
||||||
|
|
||||||
FROM ${REPO}/busybox:latest
|
|
||||||
WORKDIR /root/
|
|
||||||
# COPY --from=builder /go/src/app/dockron .
|
|
||||||
ARG ARCH=amd64
|
ARG ARCH=amd64
|
||||||
COPY ./dockron-linux-${ARCH} ./dockron
|
COPY ./dockron-linux-${ARCH} /dockron
|
||||||
|
|
||||||
CMD [ "./dockron" ]
|
ENTRYPOINT [ "/dockron" ]
|
||||||
|
|
23
Dockerfile.multi-stage
Normal file
23
Dockerfile.multi-stage
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
ARG REPO=library
|
||||||
|
FROM golang:1.15-alpine AS builder
|
||||||
|
|
||||||
|
# hadolint ignore=DL3018
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
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 dockron .
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=builder /app/dockron /
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/dockron" ]
|
63
Gopkg.lock
generated
63
Gopkg.lock
generated
|
@ -1,63 +0,0 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/Microsoft/go-winio"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "a6d595ae73cf27a1b8fc32930668708f45ce1c85"
|
|
||||||
version = "v0.4.9"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/docker/distribution"
|
|
||||||
packages = ["digest","reference"]
|
|
||||||
revision = "48294d928ced5dd9b378f7fd7c6f5da3ff3f2c89"
|
|
||||||
version = "v2.6.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/docker/docker"
|
|
||||||
packages = ["api/types","api/types/blkiodev","api/types/container","api/types/events","api/types/filters","api/types/mount","api/types/network","api/types/reference","api/types/registry","api/types/strslice","api/types/swarm","api/types/time","api/types/versions","api/types/volume","client","pkg/tlsconfig"]
|
|
||||||
revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
|
|
||||||
version = "v1.13.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/docker/go-connections"
|
|
||||||
packages = ["nat","sockets","tlsconfig"]
|
|
||||||
revision = "7395e3f8aa162843a74ed6d48e79627d9792ac55"
|
|
||||||
version = "v0.4.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/docker/go-units"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "47565b4f722fb6ceae66b95f853feed578a4a51c"
|
|
||||||
version = "v0.3.3"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pkg/errors"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
|
||||||
version = "v0.8.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/robfig/cron"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "b41be1df696709bb6395fe435af20370037c0b4c"
|
|
||||||
version = "v1.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/net"
|
|
||||||
packages = ["context","context/ctxhttp","internal/socks","proxy"]
|
|
||||||
revision = "f4c29de78a2a91c00474a2e689954305c350adf9"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = ["windows"]
|
|
||||||
revision = "0ffbfd41fbef8ffcf9b62b0b0aa3a5873ed7a4fe"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "dd521cf26a7594f53c78967b3d38f8e0de25745c662c03f6e73effaf2b59347d"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
33
Gopkg.toml
33
Gopkg.toml
|
@ -1,33 +0,0 @@
|
||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/docker/docker"
|
|
||||||
version = "1.13.1"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/robfig/cron"
|
|
||||||
version = "1.1.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/net"
|
|
137
Makefile
137
Makefile
|
@ -1,74 +1,141 @@
|
||||||
DOCKER_TAG ?= dockron-dev-${USER}
|
OUTPUT ?= dockron
|
||||||
|
DOCKER_TAG ?= $(OUTPUT)-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
|
.PHONY: default
|
||||||
default: build
|
default: build
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: check test itest
|
||||||
|
|
||||||
# Downloads dependencies into vendor directory
|
# Downloads dependencies into vendor directory
|
||||||
vendor:
|
vendor: $(GOFILES)
|
||||||
dep ensure
|
go mod vendor
|
||||||
|
|
||||||
# Runs the application, useful while developing
|
# Runs the application, useful while developing
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run: vendor
|
run:
|
||||||
go run *.go
|
go run . -watch 10s -debug
|
||||||
|
|
||||||
|
.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; } }'
|
||||||
|
|
||||||
|
.PHONY: itest
|
||||||
|
itest:
|
||||||
|
./itest/itest.sh
|
||||||
|
|
||||||
|
# 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 target
|
||||||
dockron: vendor
|
$(OUTPUT): $(GOFILES)
|
||||||
go build -o dockron
|
@echo Version: $(VERSION)
|
||||||
|
go build -ldflags '-X "main.version=$(VERSION)"' -o $(OUTPUT)
|
||||||
|
|
||||||
# Alias for building
|
# Alias for building
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: dockron
|
build: $(OUTPUT)
|
||||||
|
|
||||||
dockron-linux-amd64: vendor
|
$(OUTPUT)-darwin-amd64: $(GOFILES)
|
||||||
GOARCH=amd64 CGO_ENABLED=0 GOOS=linux go build -a -installsuffix nocgo -o dockron-linux-amd64
|
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 \
|
||||||
|
go build -ldflags '-X "main.version=$(VERSION)"' -a -installsuffix nocgo \
|
||||||
|
-o $(OUTPUT)-darwin-amd64
|
||||||
|
|
||||||
dockron-linux-arm: vendor
|
$(OUTPUT)-linux-amd64: $(GOFILES)
|
||||||
GOARCH=arm CGO_ENABLED=0 GOOS=linux go build -a -installsuffix nocgo -o dockron-linux-arm
|
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
|
||||||
|
go build -ldflags '-X "main.version=$(VERSION)"' -a -installsuffix nocgo \
|
||||||
|
-o $(OUTPUT)-linux-amd64
|
||||||
|
|
||||||
dockron-linux-arm64: vendor
|
$(OUTPUT)-linux-arm: $(GOFILES)
|
||||||
GOARCH=arm64 CGO_ENABLED=0 GOOS=linux go build -a -installsuffix nocgo -o dockron-linux-arm64
|
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
|
.PHONY: build-all-static
|
||||||
build-all-static: dockron-linux-amd64 dockron-linux-arm dockron-linux-arm64
|
build-all-static: $(OUTPUT)-darwin-amd64 build-linux-static
|
||||||
|
|
||||||
# Cleans all build artifacts
|
# Cleans all build artifacts
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm dockron
|
rm -f $(OUTPUT)
|
||||||
|
rm -f $(OUTPUT)-linux-*
|
||||||
|
|
||||||
# Cleans vendor directory
|
# Cleans vendor directory
|
||||||
.PHONY: clean-vendor
|
.PHONY: clean-vendor
|
||||||
clean-vendor:
|
clean-vendor:
|
||||||
rm -fr ./vendor
|
rm -fr ./vendor
|
||||||
|
|
||||||
# Attempts to update dependencies
|
|
||||||
.PHONY: dep-update
|
|
||||||
dep-update:
|
|
||||||
dep ensure -update
|
|
||||||
|
|
||||||
.PHONY: docker-build
|
.PHONY: docker-build
|
||||||
docker-build:
|
docker-build: $(OUTPUT)-linux-amd64
|
||||||
docker build . -t ${DOCKER_TAG}-linux-amd64
|
docker build . -t $(DOCKER_TAG)-linux-amd64
|
||||||
|
|
||||||
# Cross build for arm architechtures
|
# Cross build for arm architechtures
|
||||||
.PHONY: docker-cross-build-arm
|
.PHONY: docker-build-arm
|
||||||
docker-cross-build-arm:
|
docker-build-arm: $(OUTPUT)-linux-arm
|
||||||
docker build --build-arg REPO=arm32v6 --build-arg ARCH=arm . -t ${DOCKER_TAG}-linux-arm
|
docker build --build-arg REPO=arm32v7 --build-arg ARCH=arm . -t $(DOCKER_TAG)-linux-arm
|
||||||
|
|
||||||
.PHONY: docker-cross-build-arm
|
.PHONY: docker-build-arm
|
||||||
docker-cross-build-arm64:
|
docker-build-arm64: $(OUTPUT)-linux-arm64
|
||||||
docker build --build-arg REPO=arm64v8 --build-arg ARCH=arm64 . -t ${DOCKER_TAG}-linux-arm64
|
docker build --build-arg REPO=arm64v8 --build-arg ARCH=arm64 . -t $(DOCKER_TAG)-linux-arm64
|
||||||
|
|
||||||
.PHONY: docker-run
|
.PHONY: docker-run
|
||||||
docker-run: docker-build
|
docker-run: docker-build
|
||||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --name $(DOCKER_TAG)-run $(DOCKER_TAG)-linux-amd64
|
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
|
# Cross run on host architechture
|
||||||
.PHONY: docker-cross-run-arm
|
.PHONY: docker-run-arm
|
||||||
docker-cross-run-arm: docker-cross-build-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
|
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --name $(DOCKER_TAG)-run $(DOCKER_TAG)-linux-arm
|
||||||
|
|
||||||
.PHONY: docker-cross-run-arm64
|
.PHONY: docker-run-arm64
|
||||||
docker-cross-run-arm64: docker-cross-build-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
|
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 .
|
||||||
|
|
||||||
|
.PHONY: docker-example
|
||||||
|
docker-example:
|
||||||
|
# Uses multistage
|
||||||
|
docker-compose build
|
||||||
|
docker-compose up
|
||||||
|
|
30
README.md
30
README.md
|
@ -16,14 +16,32 @@ It will then run in the foreground, periodically checking Docker for containers
|
||||||
|
|
||||||
By default, Dockron will periodically poll Docker for new containers or schedule changes every minute. You can specify an interval by using the `-watch` flag.
|
By default, Dockron will periodically poll Docker for new containers or schedule changes every minute. You can specify an interval by using the `-watch` flag.
|
||||||
|
|
||||||
|
### Running with Docker
|
||||||
|
|
||||||
|
Dockron is also available as a Docker image. The multi-arch repo can be found at [IamTheFij/dockron](https://hub.docker.com/r/iamthefij/dockron)
|
||||||
|
|
||||||
|
From either an `amd64`, `arm`, or `arm64` machine, you can run Dockron using:
|
||||||
|
|
||||||
|
docker run -v /var/run/docker.sock:/var/run/docker.sock:ro iamthefij/dockron -watch
|
||||||
|
|
||||||
### Scheduling a container
|
### Scheduling a container
|
||||||
|
|
||||||
First, be sure your container is something that is not long running and will actually exit when complete. This is for batch runs and not keeping a service running. Docker should be able to do that on it's own with a restart policy.
|
First, be sure your container is something that is not long running and will actually exit when complete. This is for batch runs and not keeping a service running. Docker should be able to do that on it's own with a restart policy.
|
||||||
|
|
||||||
Create your container and add a label in the form `dockron.schedule="* * * * *"`, where the value is a valid cron expression (See the section [Cron Expression Formatting](#cron-expression-formatting)).
|
Create your container and add a label in the form `'dockron.schedule=* * * * *'`, where the value is a valid cron expression (See the section [Cron Expression Formatting](#cron-expression-formatting)).
|
||||||
|
|
||||||
Dockron will now start that container peridically on the schedule.
|
Dockron will now start that container peridically on the schedule.
|
||||||
|
|
||||||
|
If you have a long running container that you'd like to schedule an exec command inside of, you can do so with labels as well. Add your job in the form `dockron.<job>.schedule=* * * * *` and `dockeron.<job>.command=echo hello`. Both labels are required to create an exec job.
|
||||||
|
|
||||||
|
Eg.
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- "dockron.dates.schedule=* * * * *"
|
||||||
|
- "dockron.dates.command=date"
|
||||||
|
|
||||||
|
_Note: Exec jobs will not log their output anywhere. Not to the host container or to Dockron. It's up to you to deal with this for now. There is also currently no way to health check these._
|
||||||
|
|
||||||
### Cron Expression Formatting
|
### Cron Expression Formatting
|
||||||
|
|
||||||
For more information on the cron expression parsing, see the docs for [robfig/cron](https://godoc.org/github.com/robfig/cron).
|
For more information on the cron expression parsing, see the docs for [robfig/cron](https://godoc.org/github.com/robfig/cron).
|
||||||
|
@ -41,3 +59,13 @@ I intend to keep it simple as well. It will likely never:
|
||||||
* Handle job dependencies
|
* Handle job dependencies
|
||||||
|
|
||||||
Either use a separate tool in conjunction with Dockron, or use a more robust scheduler like Tron, or Chronos.
|
Either use a separate tool in conjunction with Dockron, or use a more robust scheduler like Tron, or Chronos.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
If you have go on your machine, you can simply use `make build` or `make run` to build and test Dockron. If you don't have go but you do have Docker, you can still build docker images using the provide multi-stage Dockerfile! You can kick that off with `make docker-staged-build`
|
||||||
|
|
||||||
|
There is also an example `docker-compose.yml` that will use the multi-stage build to ensure an easy sample. This can be run with `make docker-example`.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
There are now some basic tests as well as linting and integration tests. You can run all of these by executing `make all`.
|
||||||
|
|
26
docker-compose.yml
Normal file
26
docker-compose.yml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
dockron:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile.multi-stage
|
||||||
|
command: ["-watch", "10s", "-debug"]
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
|
start_echoer:
|
||||||
|
image: busybox:latest
|
||||||
|
command: ["date"]
|
||||||
|
labels:
|
||||||
|
# Execute every minute
|
||||||
|
- 'dockron.schedule=* * * * *'
|
||||||
|
|
||||||
|
exec_echoer:
|
||||||
|
image: busybox:latest
|
||||||
|
command: sh -c "date > /out && tail -f /out"
|
||||||
|
labels:
|
||||||
|
# Execute every minute
|
||||||
|
- 'dockron.date.schedule=* * * * *'
|
||||||
|
- 'dockron.date.command=date >> /out'
|
27
go.mod
Normal file
27
go.mod
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
module github.com/iamthefij/dockron
|
||||||
|
|
||||||
|
go 1.15
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.iamthefij.com/iamthefij/slog v1.3.0
|
||||||
|
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||||
|
github.com/containerd/containerd v1.4.4 // indirect
|
||||||
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
|
github.com/docker/docker v20.10.6+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.2 // indirect
|
||||||
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
|
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // 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
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
|
||||||
|
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20210427215850-f767ed18ee4d // indirect
|
||||||
|
google.golang.org/grpc v1.37.0 // indirect
|
||||||
|
gotest.tools/v3 v3.0.3 // indirect
|
||||||
|
)
|
186
go.sum
Normal file
186
go.sum
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
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-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
|
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-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/containerd/containerd v1.4.4 h1:rtRG4N6Ct7GNssATwgpvMGfnjnwfjnu/Zs9W3Ikzq+M=
|
||||||
|
github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
|
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
||||||
|
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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 v20.10.6+incompatible h1:oXI3Vas8TI8Eu/EjH4srKHJBVqraSzJybhxY7Om9faQ=
|
||||||
|
github.com/docker/docker v20.10.6+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.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
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/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/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.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
|
||||||
|
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
|
||||||
|
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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
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/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
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/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
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/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs=
|
||||||
|
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
||||||
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20210427215850-f767ed18ee4d h1:XkK62R+tLAM4pyhJdBEvtb5B/hM1uTxsVIOjJm6wras=
|
||||||
|
google.golang.org/genproto v0.0.0-20210427215850-f767ed18ee4d/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||||
|
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.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
|
||||||
|
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||||
|
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.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/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
|
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||||
|
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||||
|
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=
|
30
itest/docker-compose.yml
Normal file
30
itest/docker-compose.yml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
dockron:
|
||||||
|
build:
|
||||||
|
context: ../
|
||||||
|
dockerfile: ./Dockerfile.multi-stage
|
||||||
|
command: ["-watch", "10s", "-debug"]
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
|
start_echoer:
|
||||||
|
image: busybox:latest
|
||||||
|
command: sh -c "echo ok | tee -a /result.txt"
|
||||||
|
volumes:
|
||||||
|
- "./start_result.txt:/result.txt"
|
||||||
|
labels:
|
||||||
|
# Execute every minute
|
||||||
|
- 'dockron.schedule=* * * * *'
|
||||||
|
|
||||||
|
exec_echoer:
|
||||||
|
image: busybox:latest
|
||||||
|
command: sh -c "tail -f /result.txt"
|
||||||
|
volumes:
|
||||||
|
- "./exec_result.txt:/result.txt"
|
||||||
|
labels:
|
||||||
|
# Execute every minute
|
||||||
|
- 'dockron.test.schedule=* * * * *'
|
||||||
|
- 'dockron.test.command=echo ok >> /result.txt'
|
36
itest/itest.sh
Executable file
36
itest/itest.sh
Executable file
|
@ -0,0 +1,36 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Change to itest dir
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
function check_results() {
|
||||||
|
local f=$1
|
||||||
|
local min=$2
|
||||||
|
awk "/ok/ { count=count+1 } END { print \"$f: Run count\", count; if (count < $min) { print \"Expected > $min\"; exit 1 } }" "$f"
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
# Clear and create result files
|
||||||
|
echo "start" > ./start_result.txt
|
||||||
|
echo "start" > ./exec_result.txt
|
||||||
|
|
||||||
|
# Clean old containers
|
||||||
|
docker-compose down || true
|
||||||
|
# Start containers
|
||||||
|
echo "Starting containers"
|
||||||
|
docker-compose up -d --build
|
||||||
|
echo "Containers started. Sleeping for 70s to let schedules run"
|
||||||
|
# Schedules run on the shortest interval of a minute. This should allow time
|
||||||
|
# for the containers to start and execute once
|
||||||
|
sleep 70
|
||||||
|
echo "Stopping containers"
|
||||||
|
docker-compose stop
|
||||||
|
|
||||||
|
# Validate result shows minimum amount of executions
|
||||||
|
check_results ./start_result.txt 2
|
||||||
|
check_results ./exec_result.txt 1
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
332
main.go
332
main.go
|
@ -3,104 +3,340 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/docker/docker/api/types"
|
"os"
|
||||||
"github.com/docker/docker/client"
|
"regexp"
|
||||||
"github.com/robfig/cron"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.iamthefij.com/iamthefij/slog"
|
||||||
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
|
dockerClient "github.com/docker/docker/client"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WatchInterval is the duration we should sleep until polling Docker
|
var (
|
||||||
var DefaultWatchInterval = (1 * time.Minute)
|
// defaultWatchInterval is the duration we should sleep until polling Docker
|
||||||
|
defaultWatchInterval = (1 * time.Minute)
|
||||||
|
|
||||||
// SchedLabel is the string label to search for cron expressions
|
// schedLabel is the string label to search for cron expressions
|
||||||
var SchedLabel = "dockron.schedule"
|
schedLabel = "dockron.schedule"
|
||||||
|
// execLabelRegex is will capture labels for an exec job
|
||||||
|
execLabelRegexp = regexp.MustCompile(`dockron\.([a-zA-Z0-9_-]+)\.(schedule|command)`)
|
||||||
|
|
||||||
|
// version of dockron being run
|
||||||
|
version = "dev"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContainerClient provides an interface for interracting with Docker
|
||||||
|
type ContainerClient interface {
|
||||||
|
ContainerExecCreate(ctx context.Context, container string, config dockerTypes.ExecConfig) (dockerTypes.IDResponse, error)
|
||||||
|
ContainerExecInspect(ctx context.Context, execID string) (dockerTypes.ContainerExecInspect, error)
|
||||||
|
ContainerExecStart(ctx context.Context, execID string, config dockerTypes.ExecStartCheck) error
|
||||||
|
ContainerInspect(ctx context.Context, containerID string) (dockerTypes.ContainerJSON, error)
|
||||||
|
ContainerList(context context.Context, options dockerTypes.ContainerListOptions) ([]dockerTypes.Container, error)
|
||||||
|
ContainerStart(context context.Context, containerID string, options dockerTypes.ContainerStartOptions) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerCronJob is an interface of a job to run on containers
|
||||||
|
type ContainerCronJob interface {
|
||||||
|
Run()
|
||||||
|
Name() string
|
||||||
|
UniqueName() string
|
||||||
|
Schedule() string
|
||||||
|
}
|
||||||
|
|
||||||
// ContainerStartJob represents a scheduled container task
|
// ContainerStartJob represents a scheduled container task
|
||||||
// It contains a reference to a client, the schedule to run on, and the
|
// It contains a reference to a client, the schedule to run on, and the
|
||||||
// ID of that container that should be started
|
// ID of that container that should be started
|
||||||
type ContainerStartJob struct {
|
type ContainerStartJob struct {
|
||||||
Client *client.Client
|
client ContainerClient
|
||||||
ContainerID string
|
context context.Context
|
||||||
Context context.Context
|
name string
|
||||||
Name string
|
containerID string
|
||||||
Schedule string
|
schedule string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run is executed based on the ContainerStartJob Schedule and starts the
|
// Run is executed based on the ContainerStartJob Schedule and starts the
|
||||||
// container
|
// container
|
||||||
func (job ContainerStartJob) Run() {
|
func (job ContainerStartJob) Run() {
|
||||||
fmt.Println("Starting:", job.Name)
|
slog.Infof("Starting: %s", job.name)
|
||||||
err := job.Client.ContainerStart(job.Context, job.ContainerID, types.ContainerStartOptions{})
|
|
||||||
|
// Check if container is already running
|
||||||
|
containerJSON, err := job.client.ContainerInspect(
|
||||||
|
job.context,
|
||||||
|
job.containerID,
|
||||||
|
)
|
||||||
|
slog.OnErrPanicf(err, "Could not get container details for job %s", job.name)
|
||||||
|
|
||||||
|
if containerJSON.State.Running {
|
||||||
|
slog.Warningf("Container is already running. Skipping %s", job.name)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start job
|
||||||
|
err = job.client.ContainerStart(
|
||||||
|
job.context,
|
||||||
|
job.containerID,
|
||||||
|
dockerTypes.ContainerStartOptions{},
|
||||||
|
)
|
||||||
|
slog.OnErrPanicf(err, "Could not start container for job %s", job.name)
|
||||||
|
|
||||||
|
// Check results of job
|
||||||
|
for check := true; check; check = containerJSON.State.Running {
|
||||||
|
slog.Debugf("Still running %s", job.name)
|
||||||
|
|
||||||
|
containerJSON, err = job.client.ContainerInspect(
|
||||||
|
job.context,
|
||||||
|
job.containerID,
|
||||||
|
)
|
||||||
|
slog.OnErrPanicf(err, "Could not get container details for job %s", job.name)
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
slog.Debugf("Done execing %s. %+v", job.name, containerJSON.State)
|
||||||
|
// Log exit code if failed
|
||||||
|
if containerJSON.State.ExitCode != 0 {
|
||||||
|
slog.Errorf(
|
||||||
|
"Exec job %s existed with code %d",
|
||||||
|
job.name,
|
||||||
|
containerJSON.State.ExitCode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the job
|
||||||
|
func (job ContainerStartJob) Name() string {
|
||||||
|
return job.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule returns the schedule of the job
|
||||||
|
func (job ContainerStartJob) Schedule() string {
|
||||||
|
return job.schedule
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueName returns a unique identifier for a container start job
|
||||||
|
func (job ContainerStartJob) UniqueName() string {
|
||||||
|
// ContainerID should be unique as a change in label will result in
|
||||||
|
// a new container as they are immutable
|
||||||
|
return job.name + "/" + job.containerID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerExecJob is a scheduled job to be executed in a running container
|
||||||
|
type ContainerExecJob struct {
|
||||||
|
ContainerStartJob
|
||||||
|
shellCommand string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run is executed based on the ContainerStartJob Schedule and starts the
|
||||||
|
// container
|
||||||
|
func (job ContainerExecJob) Run() {
|
||||||
|
slog.Infof("Execing: %s", job.name)
|
||||||
|
containerJSON, err := job.client.ContainerInspect(
|
||||||
|
job.context,
|
||||||
|
job.containerID,
|
||||||
|
)
|
||||||
|
slog.OnErrPanicf(err, "Could not get container details for job %s", job.name)
|
||||||
|
|
||||||
|
if !containerJSON.State.Running {
|
||||||
|
slog.Warningf("Container not running. Skipping %s", job.name)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
execID, err := job.client.ContainerExecCreate(
|
||||||
|
job.context,
|
||||||
|
job.containerID,
|
||||||
|
dockerTypes.ExecConfig{
|
||||||
|
Cmd: []string{"sh", "-c", strings.TrimSpace(job.shellCommand)},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
slog.OnErrPanicf(err, "Could not create container exec job for %s", job.name)
|
||||||
|
|
||||||
|
err = job.client.ContainerExecStart(
|
||||||
|
job.context,
|
||||||
|
execID.ID,
|
||||||
|
dockerTypes.ExecStartCheck{},
|
||||||
|
)
|
||||||
|
slog.OnErrPanicf(err, "Could not start container exec job for %s", job.name)
|
||||||
|
|
||||||
|
// Wait for job results
|
||||||
|
execInfo := dockerTypes.ContainerExecInspect{Running: true}
|
||||||
|
for execInfo.Running {
|
||||||
|
slog.Debugf("Still execing %s", job.name)
|
||||||
|
execInfo, err = job.client.ContainerExecInspect(
|
||||||
|
job.context,
|
||||||
|
execID.ID,
|
||||||
|
)
|
||||||
|
slog.Debugf("Exec info: %+v", execInfo)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
// Nothing we can do if we got an error here, so let's go
|
||||||
|
slog.OnErrWarnf(err, "Could not get status for exec job %s", job.name)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
slog.Debugf("Done execing %s. %+v", job.name, execInfo)
|
||||||
|
// Log exit code if failed
|
||||||
|
if execInfo.ExitCode != 0 {
|
||||||
|
slog.Errorf("Exec job %s existed with code %d", job.name, execInfo.ExitCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryScheduledJobs queries Docker for all containers with a schedule and
|
// QueryScheduledJobs queries Docker for all containers with a schedule and
|
||||||
// returns a list of ContainerStartJob records to be scheduled
|
// returns a list of ContainerCronJob records to be scheduled
|
||||||
func QueryScheduledJobs(cli *client.Client) (jobs []ContainerStartJob) {
|
func QueryScheduledJobs(client ContainerClient) (jobs []ContainerCronJob) {
|
||||||
fmt.Println("Scanning containers for new schedules...")
|
slog.Debugf("Scanning containers for new schedules...")
|
||||||
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
|
||||||
if err != nil {
|
containers, err := client.ContainerList(
|
||||||
panic(err)
|
context.Background(),
|
||||||
}
|
dockerTypes.ContainerListOptions{All: true},
|
||||||
|
)
|
||||||
|
slog.OnErrPanicf(err, "Failure querying docker containers")
|
||||||
|
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
if val, ok := container.Labels[SchedLabel]; ok {
|
// Add start job
|
||||||
|
if val, ok := container.Labels[schedLabel]; ok {
|
||||||
jobName := strings.Join(container.Names, "/")
|
jobName := strings.Join(container.Names, "/")
|
||||||
|
|
||||||
jobs = append(jobs, ContainerStartJob{
|
jobs = append(jobs, ContainerStartJob{
|
||||||
Schedule: val,
|
client: client,
|
||||||
Client: cli,
|
containerID: container.ID,
|
||||||
ContainerID: container.ID,
|
context: context.Background(),
|
||||||
Context: context.Background(),
|
schedule: val,
|
||||||
Name: jobName,
|
name: jobName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add exec jobs
|
||||||
|
execJobs := map[string]map[string]string{}
|
||||||
|
|
||||||
|
for label, value := range container.Labels {
|
||||||
|
results := execLabelRegexp.FindStringSubmatch(label)
|
||||||
|
expectedLabelParts := 3
|
||||||
|
|
||||||
|
if len(results) == expectedLabelParts {
|
||||||
|
// We've got part of a new job
|
||||||
|
jobName, jobField := results[1], results[2]
|
||||||
|
if partJob, ok := execJobs[jobName]; ok {
|
||||||
|
// Partial exists, add the other value
|
||||||
|
partJob[jobField] = value
|
||||||
|
} else {
|
||||||
|
// No partial exists, add this part
|
||||||
|
execJobs[jobName] = map[string]string{
|
||||||
|
jobField: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for jobName, jobConfig := range execJobs {
|
||||||
|
schedule, ok := jobConfig["schedule"]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
shellCommand, ok := jobConfig["command"]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
jobs = append(jobs, ContainerExecJob{
|
||||||
|
ContainerStartJob: ContainerStartJob{
|
||||||
|
client: client,
|
||||||
|
containerID: container.ID,
|
||||||
|
context: context.Background(),
|
||||||
|
schedule: schedule,
|
||||||
|
name: strings.Join(append(container.Names, jobName), "/"),
|
||||||
|
},
|
||||||
|
shellCommand: shellCommand,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return jobs
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScheduleJobs accepts a Cron instance and a list of jobs to schedule.
|
// ScheduleJobs accepts a Cron instance and a list of jobs to schedule.
|
||||||
// It then schedules the provided jobs
|
// It then schedules the provided jobs
|
||||||
func ScheduleJobs(c *cron.Cron, jobs []ContainerStartJob) {
|
func ScheduleJobs(c *cron.Cron, jobs []ContainerCronJob) {
|
||||||
|
// Fetch existing jobs from the cron
|
||||||
|
existingJobs := map[string]cron.EntryID{}
|
||||||
|
for _, entry := range c.Entries() {
|
||||||
|
// This should be safe since ContainerCronJob is the only type of job we use
|
||||||
|
existingJobs[entry.Job.(ContainerCronJob).UniqueName()] = entry.ID
|
||||||
|
}
|
||||||
|
|
||||||
for _, job := range jobs {
|
for _, job := range jobs {
|
||||||
fmt.Printf("Scheduling %s (%s) with schedule '%s'\n", job.Name, job.ContainerID[:10], job.Schedule)
|
if _, ok := existingJobs[job.UniqueName()]; ok {
|
||||||
c.AddJob(job.Schedule, job)
|
// Job already exists, remove it from existing jobs so we don't
|
||||||
|
// unschedule it later
|
||||||
|
slog.Debugf("Job %s is already scheduled. Skipping", job.Name())
|
||||||
|
delete(existingJobs, job.UniqueName())
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Job doesn't exist yet, schedule it
|
||||||
|
_, err := c.AddJob(job.Schedule(), job)
|
||||||
|
if err == nil {
|
||||||
|
slog.Infof(
|
||||||
|
"Scheduled %s (%s) with schedule '%s'\n",
|
||||||
|
job.Name(),
|
||||||
|
job.UniqueName(),
|
||||||
|
job.Schedule(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// TODO: Track something for a healthcheck here
|
||||||
|
slog.Errorf(
|
||||||
|
"Could not schedule %s (%s) with schedule '%s'. %v\n",
|
||||||
|
job.Name(),
|
||||||
|
job.UniqueName(),
|
||||||
|
job.Schedule(),
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove remaining scheduled jobs that weren't in the new list
|
||||||
|
for _, entryID := range existingJobs {
|
||||||
|
c.Remove(entryID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Get a Docker Client
|
// Get a Docker Client
|
||||||
cli, err := client.NewEnvClient()
|
client, err := dockerClient.NewClientWithOpts(dockerClient.FromEnv)
|
||||||
if err != nil {
|
slog.OnErrPanicf(err, "Could not create Docker client")
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read interval for polling Docker
|
// Read interval for polling Docker
|
||||||
var watchInterval time.Duration
|
var watchInterval time.Duration
|
||||||
flag.DurationVar(&watchInterval, "watch", DefaultWatchInterval, "Interval used to poll Docker for changes")
|
|
||||||
|
showVersion := flag.Bool("version", false, "Display the version of dockron and exit")
|
||||||
|
|
||||||
|
flag.DurationVar(&watchInterval, "watch", defaultWatchInterval, "Interval used to poll Docker for changes")
|
||||||
|
flag.BoolVar(&slog.DebugLevel, "debug", false, "Show debug logs")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// Print version if asked
|
||||||
|
if *showVersion {
|
||||||
|
fmt.Println("Dockron version:", version)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
// Create a Cron
|
// Create a Cron
|
||||||
c := cron.New()
|
c := cron.New()
|
||||||
|
c.Start()
|
||||||
|
|
||||||
// Start the loop
|
// Start the loop
|
||||||
for {
|
for {
|
||||||
// HACK: This is risky as it could fall on the same interval as a task and that task would get skipped
|
|
||||||
// It would be best to manage a ContainerID to Job mapping and then remove entries that are missing
|
|
||||||
// in the new list and add new entries. However, cron does not support this yet.
|
|
||||||
|
|
||||||
// Stop and create a new cron
|
|
||||||
c.Stop()
|
|
||||||
c = cron.New()
|
|
||||||
|
|
||||||
// Schedule jobs again
|
// Schedule jobs again
|
||||||
jobs := QueryScheduledJobs(cli)
|
jobs := QueryScheduledJobs(client)
|
||||||
ScheduleJobs(c, jobs)
|
ScheduleJobs(c, jobs)
|
||||||
c.Start()
|
|
||||||
|
|
||||||
// Sleep until the next query time
|
// Sleep until the next query time
|
||||||
time.Sleep(watchInterval)
|
time.Sleep(watchInterval)
|
||||||
|
|
1000
main_test.go
Normal file
1000
main_test.go
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user