diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f09e0ce --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.DS_Store +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/bin +**/charts +**/docker-compose* +**/compose.y*ml +**/Dockerfile* +.github +LICENSE.md +README.md +SECURITY.md +Makefile +supercronic diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5cecb63..858f797 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,17 +22,21 @@ jobs: - name: Setup Bats and bats libs id: setup-bats - uses: bats-core/bats-action@3 + uses: bats-core/bats-action@3.0.0 + - name: install govulncheck run: | go install golang.org/x/vuln/cmd/govulncheck@latest + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: checkout code uses: actions/checkout@v4 - name: run tests run: make test env: - -BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }} + BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }} - name: run vuln check run: make vulncheck diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2c8fdae --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +# syntax=docker/dockerfile:1 + +# Create a stage for building the application. +ARG GO_VERSION=1.23.2 +FROM --platform=$BUILDPLATFORM golang:${GO_VERSION} AS build +WORKDIR /src + +RUN --mount=type=cache,target=/go/pkg/mod/ \ + --mount=type=bind,source=go.sum,target=go.sum \ + --mount=type=bind,source=go.mod,target=go.mod \ + go mod download -x + +# This is the architecture you're building for, which is passed in by the builder. +# Placing it here allows the previous steps to be cached across architectures. +ARG TARGETARCH +ARG VERSION="" + +RUN --mount=type=cache,target=/go/pkg/mod/ \ + --mount=type=bind,target=. \ + CGO_ENABLED=0 GOARCH=$TARGETARCH \ + go build -ldflags "-X main.Version=${VERSION}" \ + -o /bin/supercronic . + +################################################################################ +FROM alpine:latest AS final + +RUN --mount=type=cache,target=/var/cache/apk \ + apk --update add \ + ca-certificates \ + tzdata \ + && \ + update-ca-certificates + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/go/dockerfile-user-best-practices/ +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --system \ + --no-create-home \ + --uid "${UID}" \ + supercronic +USER supercronic + +COPY --from=build /bin/supercronic /bin/ + +ENTRYPOINT [ "/bin/supercronic" ] diff --git a/Makefile b/Makefile index 57d3b55..8ff9b2c 100644 --- a/Makefile +++ b/Makefile @@ -7,9 +7,13 @@ deps: go mod vendor .PHONY: build -build: $(GOFILES) +build: go build -ldflags "-X main.Version=${VERSION}" +.PHONY: docker-build +docker-build: + docker build -t supercronic:${VERSION} --build-arg VERSION=${VERSION} . + .PHONY: unit unit: go test -v -race $$(go list ./... | grep -v /vendor/) @@ -17,8 +21,8 @@ unit: .PHONY: integration integration: VERSION=v1337 -integration: build - sudo bats integration +integration: build docker-build + bats integration .PHONY: test test: unit integration diff --git a/integration/test.bats b/integration/test.bats index 037442d..f4c0695 100755 --- a/integration/test.bats +++ b/integration/test.bats @@ -14,6 +14,8 @@ setup() { export BATS_LIB_PATH=${BATS_LIB_PATH:-"/usr/lib"} bats_load_library bats-assert bats_load_library bats-support + # mock version + export VERSION=v1337 } teardown() { @@ -33,7 +35,7 @@ wait_for() { @test "it prints the version" { run "${BATS_TEST_DIRNAME}/../supercronic" -version - assert_output 'v1337' + assert_output $VERSION } @test "it starts" { @@ -112,48 +114,31 @@ wait_for() { } @test "it run as pid 1 and reap zombie process" { - out="${WORK_DIR}/zombie-crontab-out" + run timeout 5s docker run \ + -v "${BATS_TEST_DIRNAME}/zombie.crontab":/test.crontab \ + --rm supercronic:${VERSION} /test.crontab - # run in new process namespace - timeout 10s unshare --fork --pid --mount-proc \ - ${BATS_TEST_DIRNAME}/../supercronic "${BATS_TEST_DIRNAME}/zombie.crontab" >"$out" 2>&1 & - sleep 3 + assert_equal $status 124 # timeout exit code # todo: use other method to detect zombie cleanup - run cat $out - refute_line --partial 'unshare' - wait_for grep "reaper cleanup: pid=" "$out" + assert_line --partial 'reaping dead processes' + assert_line --partial "reaper cleanup: pid=" } @test "it run as pid 1 and normal crontab no error" { - out="${WORK_DIR}/normal-crontab-out" - # sleep 30 seconds occur found bug # FIXME: other way to detect - timeout 30s unshare --fork --pid --mount-proc \ - "${BATS_TEST_DIRNAME}/../supercronic" "${BATS_TEST_DIRNAME}/normal.crontab" >"$out" 2>&1 & # https://github.com/aptible/supercronic/issues/171 + run timeout 30s docker run \ + -v "${BATS_TEST_DIRNAME}/normal.crontab":/normal.crontab \ + --rm supercronic:${VERSION} /normal.crontab - sleep 30 - run cat $out + assert_equal $status 124 # timeout exit code - refute_line --partial 'unshare' refute_line --partial 'waitid: no child processes' -} - -@test "it run as pid 1 and no not found error" { - out="${WORK_DIR}/normal-crontab-out2" - export PATH="${BATS_TEST_DIRNAME}/../:$PATH" - ( - cd / - timeout 1s unshare --fork --pid --mount-proc \ - "supercronic" "${BATS_TEST_DIRNAME}/normal.crontab" >"$out" 2>&1 & - # https://github.com/aptible/supercronic/issues/177 - ) - sleep 1 - run cat $out - - refute_line --partial 'unshare' + assert_line --partial 'reaping dead processes' + assert_line --partial 'msg=1' # normal output refute_line --partial 'failed' + # https://github.com/aptible/supercronic/issues/177 refute_line --partial 'no such file or directory' }