Update golangci-lint to v2.12.2 with canonical config (#187)
All checks were successful
Check / check (push) Successful in 4s

Bumps golangci-lint from v2.10.1 to v2.12.2 everywhere it is pinned and installs the canonical `.golangci.yml`, then fixes every finding the new linter surfaces so `make check` is green.

## Version pins

- `Dockerfile` lint stage: `golangci/golangci-lint:v2.12.2` (Debian-based), tag plus digest pin
- `script/bootstrap`: `GOLANGCI_LINT_VERSION=2.12.2` with updated `linux-amd64`/`linux-arm64` release-archive sha256 pins

## Config

`.golangci.yml` replaced with the canonical config. Material change: the old file declared `version: "2"` but kept settings under the legacy top-level `linters-settings` key, which golangci-lint v2 ignores — so the intended thresholds (`lll` 88, `funlen` 80/50, `cyclop` 15, `dupl` 100) were not being applied. The canonical file moves them under `linters.settings` and drops `issues.exclude-use-default`.

## Lint fixes (216 findings)

- `lll` (96): wrapped lines to the 88-column limit
- `noctx` (46): `httptest.NewRequestWithContext` with `t.Context()` throughout the tests
- `goconst` (24): shared constants for template/JSON keys in `internal/handlers` and repeated test literals
- `gosec` (23): app-page redirects now go through a `redirectToApp` helper that path-escapes the app ID (G710 open redirect); `http.ServeFile` of the internally derived deployment log path annotated like the adjacent `os.Stat` (G703)
- `dupl` (22): extracted a generic `findAllByAppID` in `internal/models`, a `deleteAppResource` helper in `internal/handlers`, a shared `parsePush` in `internal/service/webhook`, and table-driven/helper-based dedup in tests
- `nolintlint` (5): removed `//nolint:funlen` directives made obsolete by the new limits (plus one more that became obsolete after refactoring)
- `nilerr` (3, surfaced during fixing): resource-delete lookups now propagate the find error to the caller

No behavior changes intended; all tests pass and `make check` is green.

Note: golangci-lint v2.12 warns that `gomodguard` is deprecated in favor of `gomodguard_v2` — a future canonical-config update should address this centrally.
Co-authored-by: sneak <sneak@sneak.berlin>
Reviewed-on: #187
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
This commit was merged in pull request #187.
This commit is contained in:
2026-08-07 22:21:42 +02:00
committed by Jeffrey Paul
parent 291f85f3ed
commit 7a34fc999c
41 changed files with 1172 additions and 797 deletions

View File

@@ -41,7 +41,8 @@ const stopTimeoutSeconds = 10
// gitImage is the Docker image used for git operations.
// alpine/git v2.47.2 - pulled 2025-12-30
const gitImage = "alpine/git@sha256:d86f367afb53d022acc4377741e7334bc20add161bb10234272b91b459b4b7d8"
const gitImage = "alpine/git@sha256:" +
"d86f367afb53d022acc4377741e7334bc20add161bb10234272b91b459b4b7d8"
// ErrNotConnected is returned when Docker client is not connected.
var ErrNotConnected = errors.New("docker client not connected")
@@ -145,7 +146,7 @@ type CreateContainerOptions struct {
Volumes []VolumeMount
Ports []PortMapping
Network string
CPULimit float64 // CPU cores (e.g. 0.5 = half a core, 2.0 = two cores). 0 means unlimited.
CPULimit float64 // CPU cores (0.5 = half a core). 0 means unlimited.
MemoryLimit int64 // Memory in bytes. 0 means unlimited.
}
@@ -303,7 +304,11 @@ func (c *Client) StopContainer(ctx context.Context, containerID ContainerID) err
timeout := stopTimeoutSeconds
err := c.docker.ContainerStop(ctx, containerID.String(), container.StopOptions{Timeout: &timeout})
err := c.docker.ContainerStop(
ctx,
containerID.String(),
container.StopOptions{Timeout: &timeout},
)
if err != nil {
return fmt.Errorf("failed to stop container: %w", err)
}
@@ -323,7 +328,11 @@ func (c *Client) RemoveContainer(
c.log.Info("removing container", "id", containerID, "force", force)
err := c.docker.ContainerRemove(ctx, containerID.String(), container.RemoveOptions{Force: force})
err := c.docker.ContainerRemove(
ctx,
containerID.String(),
container.RemoveOptions{Force: force},
)
if err != nil {
return fmt.Errorf("failed to remove container: %w", err)
}
@@ -469,7 +478,8 @@ type CloneResult struct {
CommitSHA string // The HEAD commit SHA after clone/checkout
}
// CloneRepo clones a git repository using SSH and optionally checks out a specific commit.
// CloneRepo clones a git repository using SSH and optionally checks out a
// specific commit.
// containerDir is the path inside the upaas container (for writing files).
// hostDir is the corresponding path on the Docker host (for bind mounts).
// If commitSHA is provided, that specific commit will be checked out.
@@ -584,11 +594,13 @@ func (c *Client) performBuild(
// scannerInitialBufferSize is the initial buffer size for the build log scanner.
const scannerInitialBufferSize = 64 * 1024 // 64KB
// scannerMaxBufferSize is the max buffer size for build log lines (base64 layers can be large).
// scannerMaxBufferSize is the max buffer size for build log lines
// (base64 layers can be large).
const scannerMaxBufferSize = 1024 * 1024 // 1MB
// streamBuildOutput reads Docker build output line by line and writes to stdout and optional log writer.
// Docker sends newline-delimited JSON, so reading line by line ensures each log entry is written immediately.
// streamBuildOutput reads Docker build output line by line and writes to
// stdout and optional log writer. Docker sends newline-delimited JSON, so
// reading line by line ensures each log entry is written immediately.
func (c *Client) streamBuildOutput(body io.Reader, logWriter io.Writer) error {
scanner := bufio.NewScanner(body)
buf := make([]byte, 0, scannerInitialBufferSize)
@@ -616,7 +628,10 @@ func (c *Client) streamBuildOutput(body io.Reader, logWriter io.Writer) error {
return nil
}
func (c *Client) performClone(ctx context.Context, cfg *cloneConfig) (*CloneResult, error) {
func (c *Client) performClone(
ctx context.Context,
cfg *cloneConfig,
) (*CloneResult, error) {
// Create work directory for clone destination
err := os.MkdirAll(cfg.containerDir, workDirPermissions)
if err != nil {
@@ -642,7 +657,11 @@ func (c *Client) performClone(ctx context.Context, cfg *cloneConfig) (*CloneResu
}
defer func() {
_ = c.docker.ContainerRemove(ctx, gitContainerID.String(), container.RemoveOptions{Force: true})
_ = c.docker.ContainerRemove(
ctx,
gitContainerID.String(),
container.RemoveOptions{Force: true},
)
}()
return c.runGitClone(ctx, gitContainerID)
@@ -680,7 +699,8 @@ func (c *Client) createGitContainer(
entrypoint := []string{}
cmd := []string{"sh", "-c", script}
// Use host paths for Docker bind mounts (Docker runs on the host, not in our container)
// Use host paths for Docker bind mounts
// (Docker runs on the host, not in our container)
resp, err := c.docker.ContainerCreate(ctx,
&container.Config{
Image: gitImage,
@@ -711,13 +731,20 @@ func (c *Client) createGitContainer(
return ContainerID(resp.ID), nil
}
func (c *Client) runGitClone(ctx context.Context, containerID ContainerID) (*CloneResult, error) {
func (c *Client) runGitClone(
ctx context.Context,
containerID ContainerID,
) (*CloneResult, error) {
err := c.docker.ContainerStart(ctx, containerID.String(), container.StartOptions{})
if err != nil {
return nil, fmt.Errorf("failed to start git container: %w", err)
}
statusCh, errCh := c.docker.ContainerWait(ctx, containerID.String(), container.WaitConditionNotRunning)
statusCh, errCh := c.docker.ContainerWait(
ctx,
containerID.String(),
container.WaitConditionNotRunning,
)
select {
case err := <-errCh: