From 6d6185fd794b6eadd27e24b194467ca78a27556b Mon Sep 17 00:00:00 2001 From: Paramtamtam <7326800+tarampampam@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:07:45 +0400 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20Simplify=20Doc?= =?UTF-8?q?kerfile=20and=20Makefile,=20improve=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactored Dockerfile for clarity and efficiency, using multi-stage builds and Alpine base image. Updated Makefile for streamlined commands. Improved CLI by deferring `Gosched` and simplifying proxy header handling. --- .devcontainer/devcontainer.json | 18 ------- Dockerfile | 53 +++++-------------- Makefile | 40 ++++---------- cmd/error-pages/main.go | 11 ++-- compose.yml | 19 ------- internal/cli/app.go | 2 +- internal/cli/serve/command.go | 2 +- .../cli/{app_generate.go => update_readme.go} | 0 8 files changed, 33 insertions(+), 112 deletions(-) delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 compose.yml rename internal/cli/{app_generate.go => update_readme.go} (100%) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 599c86a4..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.base.schema.json", - "name": "default", - "image": "golang:1.24-bookworm", - "features": { - "ghcr.io/guiyomh/features/golangci-lint:0": {}, - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/sshd:1": {} - }, - "customizations": { - "vscode": { - "extensions": [ - "streetsidesoftware.code-spell-checker" - ] - } - }, - "postCreateCommand": "go mod download" -} diff --git a/Dockerfile b/Dockerfile index 263e5fa3..e2bff432 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,7 @@ # syntax=docker/dockerfile:1 -# -✂- this stage is used to develop and build the application locally ------------------------------------------------- -FROM docker.io/library/golang:1.24-bookworm AS develop - -# use the /var/tmp/go as the GOPATH to reuse the modules cache -ENV GOPATH="/var/tmp/go" - -RUN set -x \ - # renovate: source=github-releases name=golangci/golangci-lint - && GOLANGCI_LINT_VERSION="2.3.0" \ - && 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 \ - --mount=type=bind,source=go.mod,target=/src/go.mod \ - --mount=type=bind,source=go.sum,target=/src/go.sum \ - go mod download -x \ - && find "${GOPATH}" -type d -exec chmod 0777 {} \; \ - && find "${GOPATH}" -type f -exec chmod 0666 {} \; - # -✂- this stage is used to compile the application ------------------------------------------------------------------- -FROM develop AS compile +FROM docker.io/library/golang:1.24-alpine AS compile # 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" @@ -35,16 +9,18 @@ ARG APP_VERSION="undefined@docker" # copy the source code COPY . /src +WORKDIR /src + RUN set -x \ - && go generate ./... \ - && CGO_ENABLED=0 LDFLAGS="-s -w -X gh.tarampamp.am/error-pages/internal/appmeta.version=${APP_VERSION}" \ - go build -trimpath -ldflags "${LDFLAGS}" -o /tmp/error-pages ./cmd/error-pages/ \ + && go generate -skip readme ./... \ + && CGO_ENABLED=0 go build \ + -trimpath \ + -ldflags "-s -w -X gh.tarampamp.am/error-pages/internal/appmeta.version=${APP_VERSION}" \ + -o /tmp/error-pages \ + ./cmd/error-pages/ \ && /tmp/error-pages --version \ && /tmp/error-pages -h -# -✂- this stage is used to prepare the runtime fs -------------------------------------------------------------------- -FROM docker.io/library/alpine:3.22 AS rootfs - WORKDIR /tmp/rootfs # prepare rootfs for runtime @@ -52,10 +28,9 @@ RUN set -x \ && mkdir -p ./etc/ssl/certs ./bin \ && echo 'appuser:x:10001:10001::/nonexistent:/sbin/nologin' > ./etc/passwd \ && echo 'appuser:x:10001:' > ./etc/group \ - && cp /etc/ssl/certs/ca-certificates.crt ./etc/ssl/certs/ - -# take the binary from the compile stage -COPY --from=compile /tmp/error-pages ./bin/error-pages + && cp /etc/ssl/certs/ca-certificates.crt ./etc/ssl/certs/ \ + && mv /tmp/error-pages ./bin/error-pages \ + && chmod 755 ./bin/error-pages WORKDIR /tmp/rootfs/opt @@ -81,7 +56,7 @@ LABEL \ org.opencontainers.image.licenses="MIT" # import from builder -COPY --from=rootfs /tmp/rootfs / +COPY --from=compile /tmp/rootfs / # use an unprivileged user USER 10001:10001 @@ -95,7 +70,7 @@ ENV LOG_LEVEL="warn" \ LOG_FORMAT="json" # docs: https://docs.docker.com/reference/dockerfile/#healthcheck -HEALTHCHECK --interval=10s --start-interval=1s --start-period=5s --timeout=2s CMD ["/bin/error-pages", "healthcheck"] +HEALTHCHECK --interval=10s --start-interval=1s --start-period=2s --timeout=1s CMD ["/bin/error-pages", "healthcheck"] ENTRYPOINT ["/bin/error-pages"] diff --git a/Makefile b/Makefile index 69ac3bdb..7f77bf12 100644 --- a/Makefile +++ b/Makefile @@ -1,38 +1,18 @@ #!/usr/bin/make -DC_RUN_ARGS = --rm --user "$(shell id -u):$(shell id -g)" +.DEFAULT_GOAL : build -.DEFAULT_GOAL : help - -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) - -.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 \ - " - -.PHONY: down -down: ## Stop the application - docker compose down --remove-orphans +gen: ## Generate code + go generate ./... -.PHONY: shell -shell: ## Start shell into development environment - docker compose run -ti $(DC_RUN_ARGS) develop bash +build: gen ## Build the application + CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o ./error-pages ./cmd/error-pages/ -.PHONY: test test: ## Run tests - docker compose run $(DC_RUN_ARGS) develop gotestsum --format pkgname -- -race -timeout 2m ./... + go test -race ./... -.PHONY: lint -lint: ## Run linters - docker compose run $(DC_RUN_ARGS) develop golangci-lint run +lint: ## Run linters (requires https://github.com/golangci/golangci-lint installed) + golangci-lint run -.PHONY: gen -gen: ## Generate code - docker compose run $(DC_RUN_ARGS) develop go generate ./... +up: build ## Start the application at http://localhost:8080 + ./error-pages --log-level debug serve --show-details --proxy-headers=X-Foo,Bar,Baz_blah diff --git a/cmd/error-pages/main.go b/cmd/error-pages/main.go index 0b9a42d3..e4060a09 100644 --- a/cmd/error-pages/main.go +++ b/cmd/error-pages/main.go @@ -6,6 +6,7 @@ import ( "os" "os/signal" "path/filepath" + "runtime" "syscall" "go.uber.org/automaxprocs/maxprocs" @@ -13,11 +14,11 @@ import ( "gh.tarampamp.am/error-pages/internal/cli" ) +// set GOMAXPROCS to match Linux container CPU quota. +var _, _ = maxprocs.Set(maxprocs.Min(1), maxprocs.Logger(func(string, ...any) {})) + // main CLI application entrypoint. func main() { - // automatically set GOMAXPROCS to match Linux container CPU quota - _, _ = maxprocs.Set(maxprocs.Min(1), maxprocs.Logger(func(_ string, _ ...any) {})) - if err := run(); err != nil { _, _ = fmt.Fprintln(os.Stderr, err.Error()) @@ -27,9 +28,11 @@ func main() { // run this CLI application. func run() error { + defer runtime.Gosched() // increase the chance of running deferred functions before exiting + // create a context that is canceled when the user interrupts the program var ctx, cancel = signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() - return (cli.NewApp(filepath.Base(os.Args[0]))).Run(ctx, os.Args) + return cli.NewApp(filepath.Base(os.Args[0])).Run(ctx, os.Args) } diff --git a/compose.yml b/compose.yml deleted file mode 100644 index b7753c41..00000000 --- a/compose.yml +++ /dev/null @@ -1,19 +0,0 @@ -# yaml-language-server: $schema=https://cdn.jsdelivr.net/gh/compose-spec/compose-spec@master/schema/compose-spec.json - -services: - develop: - build: {target: develop} - environment: {HOME: /tmp} - volumes: [.:/src:rw, tmp-data:/tmp:rw] - security_opt: [no-new-privileges:true] - - web: - build: {target: runtime} - ports: ['8080:8080/tcp'] # open http://127.0.0.1:8080 - command: --log-level debug serve --show-details --proxy-headers=X-Foo,Bar,Baz_blah - develop: # available since docker compose v2.22, https://docs.docker.com/compose/file-watch/ - watch: [{action: rebuild, path: .}] - security_opt: [no-new-privileges:true] - -volumes: - tmp-data: {} diff --git a/internal/cli/app.go b/internal/cli/app.go index 2facbac5..f24a045e 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -16,7 +16,7 @@ import ( "gh.tarampamp.am/error-pages/internal/logger" ) -//go:generate go run app_generate.go +//go:generate go run update_readme.go // NewApp creates a new console application. func NewApp(appName string) *cli.Command { diff --git a/internal/cli/serve/command.go b/internal/cli/serve/command.go index d85665e4..921ca8ad 100644 --- a/internal/cli/serve/command.go +++ b/internal/cli/serve/command.go @@ -220,7 +220,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy m[http.CanonicalHeaderKey(strings.TrimSpace(header))] = struct{}{} } - clear(cfg.ProxyHeaders) // clear the list before adding new headers + cfg.ProxyHeaders = make([]string, 0, len(m)) // clear the list before adding new headers for header := range m { cfg.ProxyHeaders = append(cfg.ProxyHeaders, header) diff --git a/internal/cli/app_generate.go b/internal/cli/update_readme.go similarity index 100% rename from internal/cli/app_generate.go rename to internal/cli/update_readme.go