chore(docker): The docker env re-written

This commit is contained in:
Paramtamtam 2024-06-20 23:09:32 +04:00
parent e6f49f622d
commit 15d1bcf9c7
No known key found for this signature in database
GPG Key ID: 366371698FAD0A2B
8 changed files with 226 additions and 178 deletions

View File

@ -1,26 +0,0 @@
# Docs: <https://docs.codecov.io/docs/commit-status>
coverage:
# coverage lower than 50 is red, higher than 90 green
range: 30..80
status:
project:
default:
# Choose a minimum coverage ratio that the commit must meet to be considered a success.
#
# `auto` will use the coverage from the base commit (pull request base or parent commit) coverage to compare
# against.
target: auto
# Allow the coverage to drop by X%, and posting a success status.
threshold: 5%
# Resulting status will pass no matter what the coverage is or what other settings are specified.
informational: true
patch:
default:
target: auto
threshold: 5%
informational: true

View File

@ -10,5 +10,9 @@ indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[{Makefile, go.mod, *.go}]
[{*.yml,*.yaml}]
ij_any_spaces_within_braces = false
ij_any_spaces_within_brackets = false
[{Makefile,go.mod,*.go}]
indent_style = tab

9
.gitattributes vendored
View File

@ -1,9 +0,0 @@
# Text files have auto line endings
* text=auto
# Go source files always have LF line endings
*.go text eol=lf
# Disable next extensions in project "used languages" list
*.lua linguist-detectable=false
*.html linguist-detectable=false

6
.gitignore vendored
View File

@ -8,9 +8,13 @@
## Temp dirs & trash
/temp
/tmp
*.env
/cmd/test*
.DS_Store
/go.work*
*.cache
*.out
*.env
/out
/gen
/cover*.*
/report.xml

View File

@ -1,26 +1,32 @@
# Documentation: <https://github.com/golangci/golangci-lint#config-file>
# yaml-language-server: $schema=https://golangci-lint.run/jsonschema/golangci.jsonschema.json
# docs: https://github.com/golangci/golangci-lint#config-file
run:
timeout: 1m
skip-dirs:
- .github
- .git
- tmp
- temp
timeout: 2m
modules-download-mode: readonly
allow-parallel-runners: true
output:
format: colored-line-number # colored-line-number|line-number|json|tab|checkstyle|code-climate
formats: [{format: colored-line-number}] # colored-line-number|line-number|json|tab|checkstyle|code-climate
linters-settings:
gci:
sections:
- standard
- default
- prefix(gh.tarampamp.am/error-pages)
gofmt:
simplify: false
rewrite-rules:
- { pattern: 'interface{}', replacement: 'any' }
govet:
check-shadowing: true
enable:
- shadow
gocyclo:
min-complexity: 15
godot:
scope: declarations
capital: true
capital: false
dupl:
threshold: 100
goconst:
@ -28,15 +34,28 @@ linters-settings:
min-occurrences: 3
misspell:
locale: US
ignore-words: [cancelled]
lll:
line-length: 120
forbidigo:
forbid:
- '^(fmt\.Print(|f|ln)|print(|ln))(# it looks like a forgotten debugging printing call)?$'
depguard:
rules:
logger:
deny:
- pkg: log
desc: 'logging is allowed only by zaplog'
prealloc:
simple: true
range-loops: true
for-loops: true
nolintlint:
allow-leading-space: false
require-specific: true
nakedret:
# Make an issue if func has more lines of code than this setting, and it has naked returns.
# Default: 30
max-func-lines: 100
linters: # All available linters list: <https://golangci-lint.run/usage/linters/>
disable-all: true
@ -50,40 +69,66 @@ linters: # All available linters list: <https://golangci-lint.run/usage/linters/
- exhaustive # check exhaustiveness of enum switch statements
- exportloopref # checks for pointers to enclosing loop variables
- funlen # Tool for detection of long functions
- gci # Gci control golang package import order and make it always deterministic
- godot # Check if comments end in a period
- gochecknoglobals # Checks that no globals are present in Go code
- gochecknoinits # Checks that no init functions are present in Go code
- gocognit # Computes and checks the cognitive complexity of functions
- goconst # Finds repeated strings that could be replaced by a constant
- gocritic # The most opinionated Go source code linter
- gocyclo # Computes and checks the cyclomatic complexity of functions
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
- gomnd # An analyzer to detect magic numbers
- gofmt # Gofmt checks whether code was gofmt-ed. By default, this tool runs with -s option to check for code simplification
- goimports # Goimports does everything that gofmt does. Additionally, it checks unused imports
- mnd # An analyzer to detect magic numbers
- goprintffuncname # Checks that printf-like functions are named with `f` at the end
- gosec # Inspects source code for security problems
- gosimple # Linter for Go source code that specializes in simplifying a code
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
- ineffassign # Detects when assignments to existing variables are not used
- lll # Reports long lines
- forbidigo # Forbids identifiers
- misspell # Finds commonly misspelled English words in comments
- nakedret # Finds naked returns in functions greater than a specified function length
- nestif # Reports deeply nested if statements
- nlreturn # checks for a new line before return and branch statements to increase code clarity
- nolintlint # Reports ill-formed or insufficient nolint directives
- prealloc # Finds slice declarations that could potentially be preallocated
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
- stylecheck # Stylecheck is a replacement for golint
- promlinter # Check Prometheus metrics naming via promlint.
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
- unconvert # Remove unnecessary type conversions
- unused # Checks Go code for unused constants, variables, functions and types
- whitespace # Tool for detection of leading and trailing whitespace
- wsl # Whitespace Linter - Forces you to use empty lines!
- unused # Checks Go code for unused constants, variables, functions and types
- gosimple # Linter for Go source code that specializes in simplifying code
- staticcheck # It's a set of rules from staticcheck
- asasalint # Check for pass []any as any in variadic func(...any)
- bodyclose # Checks whether HTTP response body is closed successfully
- contextcheck # Check whether the function uses a non-inherited context
- decorder # Check declaration order and count of types, constants, variables and functions
- dupword # Checks for duplicate words in the source code
- durationcheck # Check for two durations multiplied together
- errchkjson # Checks types passed to the json encoding functions
- errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error
- depguard # Go linter that checks if package imports are in a list of acceptable packages.
issues:
exclude-dirs:
- .github
- .git
- tmp
- temp
- testdata
exclude-rules:
- {path: flags\.go, linters: [gochecknoglobals, lll, mnd, dupl]}
- {path: env\.go, linters: [lll, gosec]}
- path: _test\.go
linters:
- dupl
- dupword
- lll
- nolintlint
- funlen
- scopelint
- gocognit
- noctx
- goconst
- nlreturn
- gochecknoglobals

View File

@ -1,48 +1,64 @@
# syntax=docker/dockerfile:1
# this stage is used to build the application
FROM docker.io/library/golang:1.22-bookworm AS builder
# -✂- this stage is used to develop and build the application locally -------------------------------------------------
FROM docker.io/library/golang:1.22-bookworm AS develop
COPY ./go.* /src/
# use the /var/tmp as the GOPATH to reuse the modules cache
ENV GOPATH="/var/tmp"
RUN set -x \
# renovate: source=github-releases name=abice/go-enum
&& ABICE_GOENUM_VERSION="0.6.0" \
&& GOBIN=/bin go install "github.com/abice/go-enum@v${ABICE_GOENUM_VERSION}" \
&& GOBIN=/bin go install golang.org/x/tools/cmd/goimports@latest \
&& GOBIN=/bin go install gotest.tools/gotestsum@latest \
&& go clean -cache -modcache \
# renovate: source=github-releases name=golangci/golangci-lint
&& GOLANGCI_LINT_VERSION="1.59.1" \
&& wget -O- -nv "https://cdn.jsdelivr.net/gh/golangci/golangci-lint@v${GOLANGCI_LINT_VERSION}/install.sh" \
| sh -s -- -b /bin "v${GOLANGCI_LINT_VERSION}"
RUN set -x \
# customize the shell prompt (for the bash)
&& echo "PS1='\[\033[1;36m\][go] \[\033[1;34m\]\w\[\033[0;35m\] \[\033[1;36m\]# \[\033[0m\]'" >> /etc/bash.bashrc
WORKDIR /src
# burn the modules cache
RUN go mod download
RUN \
--mount=type=bind,source=go.mod,target=/src/go.mod \
--mount=type=bind,source=go.sum,target=/src/go.sum \
go mod download -x
# this stage is used to compile the application
FROM builder AS compiler
# -✂- this stage is used to compile the application -------------------------------------------------------------------
FROM develop AS compile
# can be passed with any prefix (like `v1.2.3@GITHASH`), e.g.: `docker build --build-arg "APP_VERSION=v1.2.3@GITHASH" .`
# can be passed with any prefix (like `v1.2.3@GITHASH`), e.g.: `docker build --build-arg "APP_VERSION=v1.2.3" .`
ARG APP_VERSION="undefined@docker"
WORKDIR /src
RUN --mount=type=bind,source=.,target=/src set -x \
&& go generate ./... \
&& CGO_ENABLED=0 LDFLAGS="-s -w -X gh.tarampamp.am/error-pages/internal/version.version=${APP_VERSION}" \
go build -trimpath -ldflags "${LDFLAGS}" -o /tmp/error-pages ./cmd/error-pages/ \
&& /tmp/error-pages --version \
&& /tmp/error-pages -h
COPY . .
# arguments to pass on each go tool link invocation
ENV LDFLAGS="-s -w -X gh.tarampamp.am/error-pages/internal/version.version=$APP_VERSION"
# build the application
RUN set -x \
&& CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o ./error-pages ./cmd/error-pages/ \
&& ./error-pages --version \
&& ./error-pages -h
# -✂- this stage is used to prepare the runtime fs --------------------------------------------------------------------
FROM docker.io/library/alpine:3.20 AS rootfs
WORKDIR /tmp/rootfs
# prepare rootfs for runtime
RUN set -x \
&& mkdir -p \
./etc \
./bin \
./opt/html \
RUN --mount=type=bind,source=.,target=/src set -x \
&& mkdir -p ./etc ./bin ./opt/html \
&& echo 'appuser:x:10001:10001::/nonexistent:/sbin/nologin' > ./etc/passwd \
&& echo 'appuser:x:10001:' > ./etc/group \
&& mv /src/error-pages ./bin/error-pages \
&& mv /src/templates ./opt/templates \
&& rm ./opt/templates/*.md \
&& mv /src/error-pages.yml ./opt/error-pages.yml
&& cp -rv /src/templates ./opt/templates \
&& rm -v ./opt/templates/*.md \
&& cp -rv /src/error-pages.yml ./opt/error-pages.yml
# take the binary from the compile stage
COPY --from=compile /tmp/error-pages ./bin/error-pages
WORKDIR /tmp/rootfs/opt
@ -51,13 +67,13 @@ RUN set -x \
&& ./../bin/error-pages --verbose build --config-file ./error-pages.yml --index ./html \
&& ls -l ./html
# use empty filesystem
# -✂- and this is the final stage (an empty filesystem is used) -------------------------------------------------------
FROM scratch AS runtime
ARG APP_VERSION="undefined@docker"
LABEL \
# Docs: <https://github.com/opencontainers/image-spec/blob/master/annotations.md>
# docs: https://github.com/opencontainers/image-spec/blob/master/annotations.md
org.opencontainers.image.title="error-pages" \
org.opencontainers.image.description="Static server error pages in the docker image" \
org.opencontainers.image.url="https://github.com/tarampampam/error-pages" \
@ -66,10 +82,10 @@ LABEL \
org.opencontainers.version="$APP_VERSION" \
org.opencontainers.image.licenses="MIT"
# Import from builder
COPY --from=compiler /tmp/rootfs /
# import from builder
COPY --from=rootfs /tmp/rootfs /
# Use an unprivileged user
# use an unprivileged user
USER 10001:10001
WORKDIR /opt
@ -82,8 +98,10 @@ ENV LISTEN_PORT="8080" \
DISABLE_L10N="false" \
READ_BUFFER_SIZE="2048"
# Docs: <https://docs.docker.com/engine/reference/builder/#healthcheck>
HEALTHCHECK --interval=7s --timeout=2s CMD ["/bin/error-pages", "--log-json", "healthcheck"]
# docs: https://docs.docker.com/reference/dockerfile/#healthcheck
HEALTHCHECK --interval=10s --start-interval=1s --start-period=5s --timeout=2s CMD [\
"/bin/error-pages", "--log-json", "healthcheck" \
]
ENTRYPOINT ["/bin/error-pages"]

132
Makefile
View File

@ -1,64 +1,102 @@
#!/usr/bin/make
# Makefile readme (ru): <http://linux.yaroslavl.ru/docs/prog/gnu_make_3-79_russian_manual.html>
# Makefile readme (en): <https://www.gnu.org/software/make/manual/html_node/index.html#SEC_Contents>
SHELL = /bin/sh
LDFLAGS = "-s -w -X gh.tarampamp.am/error-pages/internal/version.version=$(shell git rev-parse HEAD)"
DC_RUN_ARGS = --rm --user "$(shell id -u):$(shell id -g)"
APP_NAME = $(notdir $(CURDIR))
.PHONY : help \
image dive build fmt lint gotest int-test test shell \
up down restart \
clean
.DEFAULT_GOAL : help
.SILENT : lint gotest
# This will output the help for each task. thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
help: ## Show this help
@printf "\033[33m%s:\033[0m\n" 'Available commands'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[32m%-11s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
image: ## Build docker image with app
docker build -f ./Dockerfile -t $(APP_NAME):local .
docker run --rm $(APP_NAME):local version
@printf "\n \e[30;42m %s \033[0m\n\n" 'Now you can use image like `docker run --rm -p "8080:8080/tcp" $(APP_NAME):local ...`';
.PHONY: up
up: ## Start the application in watch mode
docker compose kill web --remove-orphans 2>/dev/null || true
docker compose up --detach --wait web
$$SHELL -c "\
trap 'docker compose down --remove-orphans --timeout 30' EXIT; \
docker compose watch --no-up web \
"
dive: image ## Explore the docker image
docker run --rm -it -v "/var/run/docker.sock:/var/run/docker.sock:ro" wagoodman/dive:latest $(APP_NAME):local
.PHONY: down
down: ## Stop the application
docker compose down --remove-orphans
build: ## Build app binary file
docker-compose run $(DC_RUN_ARGS) -e "CGO_ENABLED=0" --no-deps app go build -trimpath -ldflags $(LDFLAGS) -o ./error-pages ./cmd/error-pages/
.PHONY: shell
shell: ## Start shell into development environment
docker compose run -ti $(DC_RUN_ARGS) develop bash
fmt: ## Run source code formatter tools
docker-compose run $(DC_RUN_ARGS) -e "GO111MODULE=off" --no-deps app sh -c 'go get golang.org/x/tools/cmd/goimports && $$GOPATH/bin/goimports -d -w .'
docker-compose run $(DC_RUN_ARGS) --no-deps app gofmt -s -w -d .
docker-compose run $(DC_RUN_ARGS) --no-deps app go mod tidy
.PHONY: test
test: ## Run tests
docker compose run $(DC_RUN_ARGS) develop gotestsum --format pkgname -- -race -timeout 2m ./...
lint: ## Run app linters
docker-compose run --rm --no-deps golint golangci-lint run
.PHONY: lint
lint: ## Run linters
docker compose run $(DC_RUN_ARGS) develop golangci-lint run
gotest: ## Run app tests
docker-compose run $(DC_RUN_ARGS) --no-deps app go test -v -race -timeout 10s ./...
.PHONY: gen
gen: ## Generate code
docker compose run $(DC_RUN_ARGS) develop go generate ./...
int-test: ## Run integration tests (docs: https://hurl.dev/docs/man-page.html#options)
docker-compose run --rm hurl --color --test --fail-at-end --variable host=web --variable port=8080 ./test/hurl/*.hurl
hurl: ## Run integration tests using hurl
docker compose run $(DC_RUN_ARGS) hurl --color --test --fail-at-end --variable host=web --variable port=8080 ./test/hurl/*.hurl
test: lint gotest int-test ## Run app tests and linters
shell: ## Start shell into container with golang
docker-compose run $(DC_RUN_ARGS) app bash
up: ## Create and start containers
docker-compose up --detach web
@printf "\n \e[30;42m %s \033[0m\n\n" 'Navigate your browser to ⇒ http://127.0.0.1:8080';
down: ## Stop all services
docker-compose down -t 5
restart: down up ## Restart all containers
clean: ## Make clean
docker-compose down -v -t 1
-docker rmi $(APP_NAME):local -f
#SHELL = /bin/sh
#LDFLAGS = "-s -w -X gh.tarampamp.am/error-pages/internal/version.version=$(shell git rev-parse HEAD)"
#
#DC_RUN_ARGS = --rm --user "$(shell id -u):$(shell id -g)"
#APP_NAME = $(notdir $(CURDIR))
#
#.PHONY : help \
# image dive build fmt lint gotest int-test test shell \
# up down restart \
# clean
#.DEFAULT_GOAL : help
#.SILENT : lint gotest
#
## This will output the help for each task. thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
#help: ## Show this help
# @printf "\033[33m%s:\033[0m\n" 'Available commands'
# @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[32m%-11s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
#
#image: ## Build docker image with app
# docker build -f ./Dockerfile -t $(APP_NAME):local .
# docker run --rm $(APP_NAME):local version
# @printf "\n \e[30;42m %s \033[0m\n\n" 'Now you can use image like `docker run --rm -p "8080:8080/tcp" $(APP_NAME):local ...`';
#
#dive: image ## Explore the docker image
# docker run --rm -it -v "/var/run/docker.sock:/var/run/docker.sock:ro" wagoodman/dive:latest $(APP_NAME):local
#
#build: ## Build app binary file
# docker-compose run $(DC_RUN_ARGS) -e "CGO_ENABLED=0" --no-deps app go build -trimpath -ldflags $(LDFLAGS) -o ./error-pages ./cmd/error-pages/
#
#fmt: ## Run source code formatter tools
# docker-compose run $(DC_RUN_ARGS) -e "GO111MODULE=off" --no-deps app sh -c 'go get golang.org/x/tools/cmd/goimports && $$GOPATH/bin/goimports -d -w .'
# docker-compose run $(DC_RUN_ARGS) --no-deps app gofmt -s -w -d .
# docker-compose run $(DC_RUN_ARGS) --no-deps app go mod tidy
#
#lint: ## Run app linters
# docker-compose run --rm --no-deps golint golangci-lint run
#
#gotest: ## Run app tests
# docker-compose run $(DC_RUN_ARGS) --no-deps app go test -v -race -timeout 10s ./...
#
#int-test: ## Run integration tests (docs: https://hurl.dev/docs/man-page.html#options)
# docker-compose run --rm hurl --color --test --fail-at-end --variable host=web --variable port=8080 ./test/hurl/*.hurl
#
#test: lint gotest int-test ## Run app tests and linters
#
#shell: ## Start shell into container with golang
# docker-compose run $(DC_RUN_ARGS) app bash
#
#up: ## Create and start containers
# docker-compose up --detach web
# @printf "\n \e[30;42m %s \033[0m\n\n" 'Navigate your browser to ⇒ http://127.0.0.1:8080';
#
#down: ## Stop all services
# docker-compose down -t 5
#
#restart: down up ## Restart all containers
#
#clean: ## Make clean
# docker-compose down -v -t 1
# -docker rmi $(APP_NAME):local -f

View File

@ -1,52 +1,26 @@
# Docker-compose file is used only for local development. This is not production-ready example.
version: '3.8'
volumes:
tmp-data: {}
golint-go: {}
golint-cache: {}
# yaml-language-server: $schema=https://cdn.jsdelivr.net/gh/compose-spec/compose-spec@master/schema/compose-spec.json
services:
app: &go
build: {target: builder}
environment:
HOME: /tmp
GOPATH: /tmp
volumes:
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
- .:/src:rw
- tmp-data:/tmp:rw
develop:
build: {target: develop}
environment: {HOME: /tmp}
volumes: [.:/src:rw, tmp-data:/tmp:rw]
security_opt: [no-new-privileges:true]
web:
<<: *go
ports:
- "8080:8080/tcp" # Open <http://127.0.0.1:8080>
command: sh -c "go build -buildvcs=false -o /tmp/app ./cmd/error-pages && /tmp/app serve --show-details --proxy-headers=X-Foo,Bar,Baz_blah --catch-all"
healthcheck:
test: ['CMD', '/tmp/app', '--log-json', 'healthcheck']
interval: 4s
start_period: 5s
retries: 5
golint:
image: golangci/golangci-lint:v1.59-alpine # Image page: <https://hub.docker.com/r/golangci/golangci-lint>
environment:
GOLANGCI_LINT_CACHE: /tmp/golint # <https://github.com/golangci/golangci-lint/blob/v1.42.0/internal/cache/default.go#L68>
volumes:
- golint-go:/go:rw # go dependencies will be downloaded on each run without this
- golint-cache:/tmp/golint:rw
- .:/src:ro
working_dir: /src
build: {target: runtime}
ports: ['8080:8080/tcp'] # open http://127.0.0.1:8080
command: serve --show-details --proxy-headers=X-Foo,Bar,Baz_blah --catch-all
develop: # available since docker compose v2.22, https://docs.docker.com/compose/file-watch/
watch: [{action: rebuild, path: .}]
security_opt: [no-new-privileges:true]
hurl:
image: ghcr.io/orange-opensource/hurl:4.3.0
volumes:
- .:/src:ro
volumes: [.:/src:ro]
working_dir: /src
depends_on:
web: {condition: service_healthy}
depends_on: {web: {condition: service_healthy}}
security_opt: [no-new-privileges:true]
volumes:
tmp-data: {}