Files
upaas/internal/models/env_var.go
clawbot 7a34fc999c
All checks were successful
Check / check (push) Successful in 4s
Update golangci-lint to v2.12.2 with canonical config (#187)
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>
2026-08-07 22:21:42 +02:00

189 lines
4.0 KiB
Go

package models
import (
"context"
"database/sql"
"errors"
"fmt"
"sneak.berlin/go/upaas/internal/database"
)
// EnvVar represents an environment variable for an app.
type EnvVar struct {
db *database.Database
ID int64
AppID string
Key string
Value string
}
// NewEnvVar creates a new EnvVar with a database reference.
func NewEnvVar(db *database.Database) *EnvVar {
return &EnvVar{db: db}
}
// Save inserts or updates the env var in the database.
func (e *EnvVar) Save(ctx context.Context) error {
if e.ID == 0 {
return e.insert(ctx)
}
return e.update(ctx)
}
// Delete removes the env var from the database.
func (e *EnvVar) Delete(ctx context.Context) error {
_, err := e.db.Exec(ctx, "DELETE FROM app_env_vars WHERE id = ?", e.ID)
return err
}
func (e *EnvVar) insert(ctx context.Context) error {
query := "INSERT INTO app_env_vars (app_id, key, value) VALUES (?, ?, ?)"
result, err := e.db.Exec(ctx, query, e.AppID, e.Key, e.Value)
if err != nil {
return err
}
id, err := result.LastInsertId()
if err != nil {
return err
}
e.ID = id
return nil
}
func (e *EnvVar) update(ctx context.Context) error {
query := "UPDATE app_env_vars SET key = ?, value = ? WHERE id = ?"
_, err := e.db.Exec(ctx, query, e.Key, e.Value, e.ID)
return err
}
// FindEnvVar finds an env var by ID.
//
//nolint:nilnil // returning nil,nil is idiomatic for "not found" in Active Record
func FindEnvVar(
ctx context.Context,
db *database.Database,
id int64,
) (*EnvVar, error) {
envVar := NewEnvVar(db)
row := db.QueryRow(ctx,
"SELECT id, app_id, key, value FROM app_env_vars WHERE id = ?",
id,
)
err := row.Scan(&envVar.ID, &envVar.AppID, &envVar.Key, &envVar.Value)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, fmt.Errorf("scanning env var: %w", err)
}
return envVar, nil
}
// findAllByAppID loads all rows for an app, scanning each row into a
// new model created by newFn. entity names the model in error messages.
func findAllByAppID[T interface{ scanDest() []any }](
ctx context.Context,
db *database.Database,
query, appID, entity string,
newFn func(*database.Database) T,
) ([]T, error) {
rows, err := db.Query(ctx, query, appID)
if err != nil {
return nil, fmt.Errorf("querying %s by app: %w", entity, err)
}
defer func() { _ = rows.Close() }()
var items []T
for rows.Next() {
item := newFn(db)
scanErr := rows.Scan(item.scanDest()...)
if scanErr != nil {
return nil, scanErr
}
items = append(items, item)
}
return items, rows.Err()
}
func (e *EnvVar) scanDest() []any {
return []any{&e.ID, &e.AppID, &e.Key, &e.Value}
}
// FindEnvVarsByAppID finds all env vars for an app.
func FindEnvVarsByAppID(
ctx context.Context,
db *database.Database,
appID string,
) ([]*EnvVar, error) {
query := `
SELECT id, app_id, key, value FROM app_env_vars
WHERE app_id = ? ORDER BY key`
return findAllByAppID(ctx, db, query, appID, "env vars", NewEnvVar)
}
// EnvVarPair is a key-value pair for bulk env var operations.
type EnvVarPair struct {
Key string
Value string
}
// ReplaceEnvVarsByAppID atomically replaces all env vars for an app
// within a single database transaction. It deletes all existing env
// vars and inserts the provided pairs. If any operation fails, the
// entire transaction is rolled back.
func ReplaceEnvVarsByAppID(
ctx context.Context,
db *database.Database,
appID string,
pairs []EnvVarPair,
) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("beginning transaction: %w", err)
}
defer func() { _ = tx.Rollback() }()
_, err = tx.ExecContext(ctx, "DELETE FROM app_env_vars WHERE app_id = ?", appID)
if err != nil {
return fmt.Errorf("deleting env vars: %w", err)
}
for _, p := range pairs {
_, err = tx.ExecContext(ctx,
"INSERT INTO app_env_vars (app_id, key, value) VALUES (?, ?, ?)",
appID, p.Key, p.Value,
)
if err != nil {
return fmt.Errorf("inserting env var %q: %w", p.Key, err)
}
}
err = tx.Commit()
if err != nil {
return fmt.Errorf("committing transaction: %w", err)
}
return nil
}