From f8e8eec43d23a2d6baa546b58936be4ab1217b83 Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 10 Mar 2026 12:36:19 -0700 Subject: [PATCH] refactor: use official golangci-lint image for lint stage Restructure Dockerfile to match upaas/dnswatcher pattern: - Separate lint stage using golangci/golangci-lint:v2.1.6 image - Builder stage for tests and compilation (no lint dependency) - Add fmt-check Makefile target - Decouple test from lint in Makefile (lint runs in its own stage) - Run gofmt on all files - docker build verified passing locally --- Dockerfile | 59 +++++++++--------------- Makefile | 7 ++- internal/cli/info.go | 2 +- internal/cli/init.go | 2 +- internal/cli/integration_test.go | 1 + internal/cli/secrets.go | 2 +- internal/cli/unlockers.go | 2 +- internal/cli/vault.go | 2 +- internal/cli/version.go | 2 +- internal/secret/derivation_index_test.go | 4 +- internal/secret/secret_test.go | 10 ++-- internal/secret/validation_test.go | 1 - 12 files changed, 42 insertions(+), 52 deletions(-) diff --git a/Dockerfile b/Dockerfile index 63a0210..799774e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,57 +1,44 @@ -# Build stage -FROM golang:1.24-alpine AS builder +# Lint stage — fast feedback on formatting and lint issues +# golangci/golangci-lint:v2.1.6 +FROM golangci/golangci-lint:v2.1.6 AS lint -# Install build dependencies -RUN apk add --no-cache \ - gcc \ - musl-dev \ - make \ - git \ - gnupg - -# Set working directory -WORKDIR /build - -# Copy go mod files +WORKDIR /src COPY go.mod go.sum ./ - -# Download dependencies RUN go mod download -# Copy source code COPY . . -# Install golangci-lint for checks (binary install to avoid Go version constraints) -RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.6 +RUN make fmt-check +RUN make lint -# Run all checks (lint, vet, test, build) -RUN make check +# Build stage — tests and compilation +FROM golang:1.24-alpine AS builder -# Build the final binary with version info -RUN CGO_ENABLED=1 go build -v -o secret cmd/secret/main.go +# Force BuildKit to run the lint stage +COPY --from=lint /src/go.sum /dev/null + +RUN apk add --no-cache gcc musl-dev make git gnupg + +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN make test +RUN CGO_ENABLED=1 go build -v -ldflags "-X 'git.eeqj.de/sneak/secret/internal/cli.Version=0.1.0' -X 'git.eeqj.de/sneak/secret/internal/cli.GitCommit=$(git rev-parse HEAD)'" -o secret cmd/secret/main.go # Runtime stage FROM alpine:latest -# Install runtime dependencies -RUN apk add --no-cache \ - ca-certificates \ - gnupg +RUN apk add --no-cache ca-certificates gnupg -# Create non-root user RUN adduser -D -s /bin/sh secret -# Copy binary from builder COPY --from=builder /build/secret /usr/local/bin/secret - -# Ensure binary is executable RUN chmod +x /usr/local/bin/secret -# Switch to non-root user USER secret - -# Set working directory WORKDIR /home/secret -# Set entrypoint -ENTRYPOINT ["secret"] \ No newline at end of file +ENTRYPOINT ["secret"] diff --git a/Makefile b/Makefile index 87eecc0..1c5e4d3 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ build: ./secret vet: go vet ./... -test: lint vet +test: vet go test ./... || go test -v ./... fmt: @@ -26,7 +26,7 @@ fmt: lint: golangci-lint run --timeout 5m -check: build test +check: build lint test # Build Docker container docker: @@ -42,3 +42,6 @@ clean: install: ./secret cp ./secret $(HOME)/bin/secret + +fmt-check: + @test -z "$$(gofmt -l .)" || (echo "Files need formatting:" && gofmt -l . && exit 1) diff --git a/internal/cli/info.go b/internal/cli/info.go index f62805e..e993d91 100644 --- a/internal/cli/info.go +++ b/internal/cli/info.go @@ -1,10 +1,10 @@ package cli import ( - "log" "encoding/json" "fmt" "io" + "log" "path/filepath" "runtime" "strings" diff --git a/internal/cli/init.go b/internal/cli/init.go index 1390506..14590bc 100644 --- a/internal/cli/init.go +++ b/internal/cli/init.go @@ -1,8 +1,8 @@ package cli import ( - "log" "fmt" + "log" "log/slog" "os" "path/filepath" diff --git a/internal/cli/integration_test.go b/internal/cli/integration_test.go index 16e634c..814ef25 100644 --- a/internal/cli/integration_test.go +++ b/internal/cli/integration_test.go @@ -2285,6 +2285,7 @@ func verifyFileExists(t *testing.T, path string) { } // verifyFileNotExists checks if a file does not exist at the given path +// //nolint:unused // kept for future use func verifyFileNotExists(t *testing.T, path string) { t.Helper() diff --git a/internal/cli/secrets.go b/internal/cli/secrets.go index f6f6e74..ee66aac 100644 --- a/internal/cli/secrets.go +++ b/internal/cli/secrets.go @@ -1,10 +1,10 @@ package cli import ( - "log" "encoding/json" "fmt" "io" + "log" "path/filepath" "strings" diff --git a/internal/cli/unlockers.go b/internal/cli/unlockers.go index e8026e4..d361c38 100644 --- a/internal/cli/unlockers.go +++ b/internal/cli/unlockers.go @@ -1,9 +1,9 @@ package cli import ( - "log" "encoding/json" "fmt" + "log" "os" "os/exec" "path/filepath" diff --git a/internal/cli/vault.go b/internal/cli/vault.go index 0ae5f07..dcd54e0 100644 --- a/internal/cli/vault.go +++ b/internal/cli/vault.go @@ -1,9 +1,9 @@ package cli import ( - "log" "encoding/json" "fmt" + "log" "os" "path/filepath" "strings" diff --git a/internal/cli/version.go b/internal/cli/version.go index a29fbfa..a9ded8b 100644 --- a/internal/cli/version.go +++ b/internal/cli/version.go @@ -1,8 +1,8 @@ package cli import ( - "log" "fmt" + "log" "path/filepath" "strings" "text/tabwriter" diff --git a/internal/secret/derivation_index_test.go b/internal/secret/derivation_index_test.go index 3835f7f..ccbac13 100644 --- a/internal/secret/derivation_index_test.go +++ b/internal/secret/derivation_index_test.go @@ -26,12 +26,12 @@ type realVault struct { func (v *realVault) GetDirectory() (string, error) { return filepath.Join(v.stateDir, "vaults.d", v.name), nil } -func (v *realVault) GetName() string { return v.name } +func (v *realVault) GetName() string { return v.name } func (v *realVault) GetFilesystem() afero.Fs { return v.fs } // Unused by getLongTermPrivateKey — these satisfy VaultInterface. func (v *realVault) AddSecret(string, *memguard.LockedBuffer, bool) error { panic("not used") } -func (v *realVault) GetCurrentUnlocker() (Unlocker, error) { panic("not used") } +func (v *realVault) GetCurrentUnlocker() (Unlocker, error) { panic("not used") } func (v *realVault) CreatePassphraseUnlocker(*memguard.LockedBuffer) (*PassphraseUnlocker, error) { panic("not used") } diff --git a/internal/secret/secret_test.go b/internal/secret/secret_test.go index 3639dd2..a8560a1 100644 --- a/internal/secret/secret_test.go +++ b/internal/secret/secret_test.go @@ -284,11 +284,11 @@ func TestSecretNameValidation(t *testing.T) { {"valid/path/name", true}, {"123valid", true}, {"", false}, - {"Valid-Upper-Name", true}, // uppercase allowed - {"2025-11-21-ber1app1-vaultik-test-bucket-AKI", true}, // real-world uppercase key ID - {"MixedCase/Path/Name", true}, // mixed case with path - {"invalid name", false}, // space not allowed - {"invalid@name", false}, // @ not allowed + {"Valid-Upper-Name", true}, // uppercase allowed + {"2025-11-21-ber1app1-vaultik-test-bucket-AKI", true}, // real-world uppercase key ID + {"MixedCase/Path/Name", true}, // mixed case with path + {"invalid name", false}, // space not allowed + {"invalid@name", false}, // @ not allowed } for _, test := range tests { diff --git a/internal/secret/validation_test.go b/internal/secret/validation_test.go index 4a4c301..9ab1909 100644 --- a/internal/secret/validation_test.go +++ b/internal/secret/validation_test.go @@ -154,4 +154,3 @@ func TestValidateGPGKeyID(t *testing.T) { }) } } -