Compare commits
11 Commits
master
...
multi-arch
Author | SHA1 | Date | |
---|---|---|---|
4597842ccf | |||
64b28038e9 | |||
726748852e | |||
758180407f | |||
b1be36afdf | |||
02899944a9 | |||
d2807c74b1 | |||
12f48e2c6d | |||
7b94fb20f5 | |||
6d8a666213 | |||
7c6e536ec3 |
87
.drone.yml
87
.drone.yml
@ -1,25 +1,15 @@
|
|||||||
---
|
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: test
|
name: test
|
||||||
|
|
||||||
|
workspace:
|
||||||
|
base: /go/src/dockron
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: test
|
- name: build
|
||||||
image: golang:1.22
|
image: golang:1.11
|
||||||
commands:
|
commands:
|
||||||
- make test
|
- go get -u github.com/golang/dep/cmd/dep
|
||||||
|
- 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
|
||||||
@ -36,13 +26,26 @@ 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.22
|
image: golang:1.11
|
||||||
environment:
|
|
||||||
VERSION: ${DRONE_TAG:-${DRONE_COMMIT}}
|
|
||||||
commands:
|
commands:
|
||||||
- make build-linux-static
|
- go get -u github.com/golang/dep/cmd/dep
|
||||||
|
- 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
|
||||||
@ -56,7 +59,7 @@ steps:
|
|||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
build_args:
|
build_args:
|
||||||
- ARCH=arm
|
- ARCH=arm
|
||||||
- REPO=arm32v7
|
- REPO=arm32v6
|
||||||
|
|
||||||
- name: push image - arm64
|
- name: push image - arm64
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
@ -69,19 +72,8 @@ steps:
|
|||||||
password:
|
password:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
build_args:
|
build_args:
|
||||||
- ARCH=arm64
|
- ARCH=arm
|
||||||
- REPO=arm64v8
|
- REPO=arm32v6
|
||||||
|
|
||||||
- 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
|
||||||
@ -93,28 +85,3 @@ 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,11 +25,7 @@ _testmain.go
|
|||||||
*.prof
|
*.prof
|
||||||
|
|
||||||
# Output
|
# Output
|
||||||
coverage.out
|
|
||||||
dockron
|
dockron
|
||||||
dockron-*
|
dockron-linux-*
|
||||||
# deps
|
# deps
|
||||||
vendor/
|
vendor/
|
||||||
|
|
||||||
# Test output
|
|
||||||
itest/*_result.txt
|
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- errname
|
|
||||||
- errorlint
|
|
||||||
- exhaustive
|
|
||||||
- gofumpt
|
|
||||||
- goimports
|
|
||||||
- goprintffuncname
|
|
||||||
- misspell
|
|
||||||
- gomnd
|
|
||||||
- tagliatelle
|
|
||||||
- tenv
|
|
||||||
- testpackage
|
|
||||||
- thelper
|
|
||||||
- tparallel
|
|
||||||
- unconvert
|
|
||||||
- wrapcheck
|
|
||||||
- wsl
|
|
||||||
disable:
|
|
||||||
- gochecknoglobals
|
|
||||||
|
|
||||||
linters-settings:
|
|
||||||
gosec:
|
|
||||||
excludes:
|
|
||||||
- G204
|
|
||||||
tagliatelle:
|
|
||||||
case:
|
|
||||||
rules:
|
|
||||||
yaml: snake
|
|
||||||
|
|
||||||
issues:
|
|
||||||
exclude-rules:
|
|
||||||
- path: _test\.go
|
|
||||||
linters:
|
|
||||||
- gosec
|
|
@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
repos:
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v4.6.0
|
|
||||||
hooks:
|
|
||||||
- id: check-added-large-files
|
|
||||||
- id: trailing-whitespace
|
|
||||||
- id: end-of-file-fixer
|
|
||||||
- id: check-merge-conflict
|
|
||||||
- repo: https://github.com/golangci/golangci-lint
|
|
||||||
rev: v1.52.2
|
|
||||||
hooks:
|
|
||||||
- id: golangci-lint
|
|
||||||
args: ["--timeout=5m"]
|
|
||||||
- repo: https://github.com/IamTheFij/docker-pre-commit
|
|
||||||
rev: v3.0.1
|
|
||||||
hooks:
|
|
||||||
- id: docker-compose-check
|
|
||||||
- repo: https://github.com/hadolint/hadolint
|
|
||||||
rev: v2.12.0
|
|
||||||
hooks:
|
|
||||||
- id: hadolint
|
|
21
Dockerfile
21
Dockerfile
@ -1,6 +1,21 @@
|
|||||||
FROM scratch
|
ARG REPO=library
|
||||||
|
# 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
|
||||||
|
|
||||||
ENTRYPOINT [ "/dockron" ]
|
CMD [ "./dockron" ]
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
ARG REPO=library
|
|
||||||
FROM golang:1.22-alpine AS builder
|
|
||||||
|
|
||||||
# hadolint ignore=DL3018
|
|
||||||
RUN apk add --no-cache git && \
|
|
||||||
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
Normal file
63
Gopkg.lock
generated
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# 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
Normal file
33
Gopkg.toml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# 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"
|
16
LICENSE
16
LICENSE
@ -1,5 +1,5 @@
|
|||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
http://www.apache.org/licenses/
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
@ -59,14 +59,14 @@ To apply the Apache License to your work, attach the following boilerplate notic
|
|||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
137
Makefile
137
Makefile
@ -1,141 +1,74 @@
|
|||||||
OUTPUT ?= dockron
|
DOCKER_TAG ?= dockron-dev-${USER}
|
||||||
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: $(GOFILES)
|
vendor:
|
||||||
go mod vendor
|
dep ensure
|
||||||
|
|
||||||
# Runs the application, useful while developing
|
# Runs the application, useful while developing
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run:
|
run: vendor
|
||||||
go run . -watch 10s -debug
|
go run *.go
|
||||||
|
|
||||||
.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
|
||||||
$(OUTPUT): $(GOFILES)
|
dockron: vendor
|
||||||
@echo Version: $(VERSION)
|
go build -o dockron
|
||||||
go build -ldflags '-X "main.version=$(VERSION)"' -o $(OUTPUT)
|
|
||||||
|
|
||||||
# Alias for building
|
# Alias for building
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: $(OUTPUT)
|
build: dockron
|
||||||
|
|
||||||
$(OUTPUT)-darwin-amd64: $(GOFILES)
|
dockron-linux-amd64: vendor
|
||||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 \
|
GOARCH=amd64 CGO_ENABLED=0 GOOS=linux go build -a -installsuffix nocgo -o dockron-linux-amd64
|
||||||
go build -ldflags '-X "main.version=$(VERSION)"' -a -installsuffix nocgo \
|
|
||||||
-o $(OUTPUT)-darwin-amd64
|
|
||||||
|
|
||||||
$(OUTPUT)-linux-amd64: $(GOFILES)
|
dockron-linux-arm: vendor
|
||||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
|
GOARCH=arm CGO_ENABLED=0 GOOS=linux go build -a -installsuffix nocgo -o dockron-linux-arm
|
||||||
go build -ldflags '-X "main.version=$(VERSION)"' -a -installsuffix nocgo \
|
|
||||||
-o $(OUTPUT)-linux-amd64
|
|
||||||
|
|
||||||
$(OUTPUT)-linux-arm: $(GOFILES)
|
dockron-linux-arm64: vendor
|
||||||
GOOS=linux GOARCH=arm CGO_ENABLED=0 \
|
GOARCH=arm64 CGO_ENABLED=0 GOOS=linux go build -a -installsuffix nocgo -o dockron-linux-arm64
|
||||||
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: $(OUTPUT)-darwin-amd64 build-linux-static
|
build-all-static: dockron-linux-amd64 dockron-linux-arm dockron-linux-arm64
|
||||||
|
|
||||||
# Cleans all build artifacts
|
# Cleans all build artifacts
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OUTPUT)
|
rm dockron
|
||||||
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: $(OUTPUT)-linux-amd64
|
docker-build:
|
||||||
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-build-arm
|
.PHONY: docker-cross-build-arm
|
||||||
docker-build-arm: $(OUTPUT)-linux-arm
|
docker-cross-build-arm:
|
||||||
docker build --build-arg REPO=arm32v7 --build-arg ARCH=arm . -t $(DOCKER_TAG)-linux-arm
|
docker build --build-arg REPO=arm32v6 --build-arg ARCH=arm . -t ${DOCKER_TAG}-linux-arm
|
||||||
|
|
||||||
.PHONY: docker-build-arm
|
.PHONY: docker-cross-build-arm
|
||||||
docker-build-arm64: $(OUTPUT)-linux-arm64
|
docker-cross-build-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-run-arm
|
.PHONY: docker-cross-run-arm
|
||||||
docker-run-arm: docker-build-arm
|
docker-cross-run-arm: docker-cross-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-run-arm64
|
.PHONY: docker-cross-run-arm64
|
||||||
docker-run-arm64: docker-build-arm64
|
docker-cross-run-arm64: docker-cross-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
|
|
||||||
|
40
README.md
40
README.md
@ -16,42 +16,14 @@ 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
|
|
||||||
|
|
||||||
### Getting a Docker API Version error?
|
|
||||||
|
|
||||||
You might see something like the following error when Dockron connects to the Docker API
|
|
||||||
|
|
||||||
```
|
|
||||||
Error response from daemon: client version 1.47 is too new. Maximum supported API version is 1.45
|
|
||||||
```
|
|
||||||
|
|
||||||
This is because the API client library is newer than the version of the Docker API on your host. You can tell the Dockron API Client to use a compatible version by specifying `DOCKER_API_VERSION=1.45`, where the version you specify matches the API version shown when you run `docker version`. If you are running Dockron in Docker, make sure you add this to your compose environment or otherwise pass it to the container.
|
|
||||||
|
|
||||||
### 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 log their output to Dockron. 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).
|
||||||
@ -69,13 +41,3 @@ 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`.
|
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
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'
|
|
39
go.mod
39
go.mod
@ -1,39 +0,0 @@
|
|||||||
module github.com/iamthefij/dockron
|
|
||||||
|
|
||||||
go 1.22
|
|
||||||
|
|
||||||
toolchain go1.22.7
|
|
||||||
|
|
||||||
require (
|
|
||||||
git.iamthefij.com/iamthefij/slog v1.3.0
|
|
||||||
github.com/docker/docker v27.3.1+incompatible
|
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
|
||||||
golang.org/x/net v0.29.0
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
|
||||||
github.com/moby/term v0.5.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.1.0 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect
|
|
||||||
go.opentelemetry.io/otel v1.30.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/metric v1.30.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/sdk v1.30.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/trace v1.30.0 // indirect
|
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
|
||||||
golang.org/x/time v0.6.0 // indirect
|
|
||||||
gotest.tools/v3 v3.5.1 // indirect
|
|
||||||
)
|
|
127
go.sum
127
go.sum
@ -1,127 +0,0 @@
|
|||||||
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/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
|
||||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
|
||||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
|
||||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
|
||||||
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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
|
||||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
|
||||||
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
|
|
||||||
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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
|
||||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
|
||||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
|
||||||
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.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
|
||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
|
||||||
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/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.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI=
|
|
||||||
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
|
|
||||||
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8=
|
|
||||||
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
|
|
||||||
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
|
|
||||||
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
|
|
||||||
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
|
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
|
||||||
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/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-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.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
|
||||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
|
||||||
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-20180905080454-ebe1bf3edb33/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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
|
||||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
|
||||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
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/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
|
||||||
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
|
|
||||||
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
|
||||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
|
@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
dockron:
|
|
||||||
build:
|
|
||||||
context: ../
|
|
||||||
dockerfile: ./Dockerfile.multi-stage
|
|
||||||
command: ["-watch", "10s", "-debug"]
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
environment:
|
|
||||||
DOCKER_API_VERSION: 1.45
|
|
||||||
|
|
||||||
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 | tee /result.txt && echo "Yay!"'
|
|
@ -1,47 +0,0 @@
|
|||||||
#! /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
|
|
||||||
# Schedules run on the shortest interval of a minute. This should allow time
|
|
||||||
# for the containers to start and execute once
|
|
||||||
local seconds=$((65 - $(date +"%S")))
|
|
||||||
echo "Containers started. Sleeping for ${seconds}s to let schedules run"
|
|
||||||
sleep $seconds
|
|
||||||
|
|
||||||
echo "Stopping containers"
|
|
||||||
docker compose stop
|
|
||||||
|
|
||||||
# Print logs
|
|
||||||
docker compose logs
|
|
||||||
|
|
||||||
# Validate result shows minimum amount of executions
|
|
||||||
check_results ./start_result.txt 2
|
|
||||||
check_results ./exec_result.txt 1
|
|
||||||
|
|
||||||
# Check for exec output
|
|
||||||
if ! (docker compose logs | grep -q "Yay!"); then
|
|
||||||
echo "Exec output 'Yay!' not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
main
|
|
365
main.go
365
main.go
@ -1,373 +1,106 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"github.com/docker/docker/api/types"
|
||||||
"regexp"
|
"github.com/docker/docker/client"
|
||||||
|
"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"
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
dockerClient "github.com/docker/docker/client"
|
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// WatchInterval is the duration we should sleep until polling Docker
|
||||||
// defaultWatchInterval is the duration we should sleep until polling Docker
|
var DefaultWatchInterval = (1 * time.Minute)
|
||||||
defaultWatchInterval = (1 * time.Minute)
|
|
||||||
|
|
||||||
// schedLabel is the string label to search for cron expressions
|
// SchedLabel is the string label to search for cron expressions
|
||||||
schedLabel = "dockron.schedule"
|
var 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. Makes it possible to mock in tests
|
|
||||||
type ContainerClient interface {
|
|
||||||
ContainerExecCreate(ctx context.Context, container string, config container.ExecOptions) (dockerTypes.IDResponse, error)
|
|
||||||
ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error)
|
|
||||||
ContainerExecStart(ctx context.Context, execID string, config container.ExecStartOptions) error
|
|
||||||
ContainerExecAttach(ctx context.Context, execID string, options container.ExecAttachOptions) (dockerTypes.HijackedResponse, error)
|
|
||||||
ContainerInspect(ctx context.Context, containerID string) (dockerTypes.ContainerJSON, error)
|
|
||||||
ContainerList(context context.Context, options container.ListOptions) ([]dockerTypes.Container, error)
|
|
||||||
ContainerStart(context context.Context, containerID string, options container.StartOptions) 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 ContainerClient
|
Client *client.Client
|
||||||
context context.Context
|
ContainerID string
|
||||||
name string
|
Context context.Context
|
||||||
containerID string
|
Name 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() {
|
||||||
slog.Infof("Starting: %s", job.name)
|
fmt.Println("Starting:", job.Name)
|
||||||
|
err := job.Client.ContainerStart(job.Context, job.ContainerID, types.ContainerStartOptions{})
|
||||||
// Check if container is already running
|
if err != nil {
|
||||||
containerJSON, err := job.client.ContainerInspect(
|
panic(err)
|
||||||
job.context,
|
|
||||||
job.containerID,
|
|
||||||
)
|
|
||||||
slog.OnErrPanicf(err, "Could not get container details for job %s", job.name)
|
|
||||||
|
|
||||||
if containerJSON.State.Running {
|
|
||||||
slog.Warningf("%s: Container is already running. Skipping start.", job.name)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start job
|
|
||||||
err = job.client.ContainerStart(
|
|
||||||
job.context,
|
|
||||||
job.containerID,
|
|
||||||
container.StartOptions{},
|
|
||||||
)
|
|
||||||
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("%s: Still running", 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("%s: Done running. %+v", job.name, containerJSON.State)
|
|
||||||
|
|
||||||
// Log exit code if failed
|
|
||||||
if containerJSON.State.ExitCode != 0 {
|
|
||||||
slog.Errorf(
|
|
||||||
"%s: Exec job exited 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("%s: Container not running. Skipping exec.", job.name)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
execID, err := job.client.ContainerExecCreate(
|
|
||||||
job.context,
|
|
||||||
job.containerID,
|
|
||||||
container.ExecOptions{
|
|
||||||
AttachStdout: true,
|
|
||||||
AttachStderr: true,
|
|
||||||
Cmd: []string{"sh", "-c", strings.TrimSpace(job.shellCommand)},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
slog.OnErrPanicf(err, "Could not create container exec job for %s", job.name)
|
|
||||||
|
|
||||||
hj, err := job.client.ContainerExecAttach(job.context, execID.ID, container.ExecAttachOptions{})
|
|
||||||
slog.OnErrWarnf(err, "%s: Error attaching to exec: %s", job.name, err)
|
|
||||||
defer hj.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(hj.Reader)
|
|
||||||
|
|
||||||
err = job.client.ContainerExecStart(
|
|
||||||
job.context,
|
|
||||||
execID.ID,
|
|
||||||
container.ExecStartOptions{},
|
|
||||||
)
|
|
||||||
slog.OnErrPanicf(err, "Could not start container exec job for %s", job.name)
|
|
||||||
|
|
||||||
// Wait for job results
|
|
||||||
execInfo := container.ExecInspect{Running: true}
|
|
||||||
for execInfo.Running {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
|
|
||||||
slog.Debugf("Still execing %s", job.name)
|
|
||||||
execInfo, err = job.client.ContainerExecInspect(
|
|
||||||
job.context,
|
|
||||||
execID.ID,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Maybe print output
|
|
||||||
if hj.Reader != nil {
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if len(line) > 0 {
|
|
||||||
slog.Infof("%s: Exec output: %s", job.name, line)
|
|
||||||
} else {
|
|
||||||
slog.Debugf("%s: Empty exec output", job.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
slog.OnErrWarnf(err, "%s: Error reading from exec", job.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
slog.Debugf("%s: No exec reader", job.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Debugf("%s: Exec info: %+v", job.name, execInfo)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// Nothing we can do if we got an error here, so let's go
|
|
||||||
slog.OnErrWarnf(err, "%s: Could not get status for exec job", job.name)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
slog.Debugf("%s: Done execing. %+v", job.name, execInfo)
|
|
||||||
// Log exit code if failed
|
|
||||||
if execInfo.ExitCode != 0 {
|
|
||||||
slog.Errorf("%s: Exec job 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 ContainerCronJob records to be scheduled
|
// returns a list of ContainerStartJob records to be scheduled
|
||||||
func QueryScheduledJobs(client ContainerClient) (jobs []ContainerCronJob) {
|
func QueryScheduledJobs(cli *client.Client) (jobs []ContainerStartJob) {
|
||||||
slog.Debugf("Scanning containers for new schedules...")
|
fmt.Println("Scanning containers for new schedules...")
|
||||||
|
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
||||||
containers, err := client.ContainerList(
|
if err != nil {
|
||||||
context.Background(),
|
panic(err)
|
||||||
container.ListOptions{All: true},
|
}
|
||||||
)
|
|
||||||
slog.OnErrPanicf(err, "Failure querying docker containers")
|
|
||||||
|
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
// Add start job
|
if val, ok := container.Labels[SchedLabel]; ok {
|
||||||
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{
|
||||||
client: client,
|
Schedule: val,
|
||||||
containerID: container.ID,
|
Client: cli,
|
||||||
context: context.Background(),
|
ContainerID: container.ID,
|
||||||
schedule: val,
|
Context: context.Background(),
|
||||||
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 jobs
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 []ContainerCronJob) {
|
func ScheduleJobs(c *cron.Cron, jobs []ContainerStartJob) {
|
||||||
// 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 {
|
||||||
if _, ok := existingJobs[job.UniqueName()]; ok {
|
fmt.Printf("Scheduling %s (%s) with schedule '%s'\n", job.Name, job.ContainerID[:10], job.Schedule)
|
||||||
// Job already exists, remove it from existing jobs so we don't
|
c.AddJob(job.Schedule, job)
|
||||||
// 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
|
||||||
client, err := dockerClient.NewClientWithOpts(dockerClient.FromEnv)
|
cli, err := client.NewEnvClient()
|
||||||
slog.OnErrPanicf(err, "Could not create Docker client")
|
if err != nil {
|
||||||
|
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(client)
|
jobs := QueryScheduledJobs(cli)
|
||||||
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)
|
||||||
|
1028
main_test.go
1028
main_test.go
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user