Guidelines for AI coding agents operating in this repository.
Gordon is a self-hosted container deployment platform: Docker registry + reverse proxy + auto-deploy.
Module: github.com/bnema/gordon. Go 1.25+. Single binary, no external database.
# Build
go build ./...
# Test — full suite (~1800 tests, 48 packages)
go test ./...
# Test — single package
go test ./internal/usecase/container/...
# Test — single test function
go test ./internal/usecase/container/... -run TestService_ListNetworks -v
# Test — by layer
go test ./internal/adapters/... # adapters only
go test ./internal/usecase/... # use cases only
# Lint (MUST pass before any commit)
golangci-lint run ./...
# Generate mocks (after changing boundaries/in or boundaries/out interfaces)
mockeryLint config is in .golangci.yml (v2): errcheck, gocyclo (max 15), gosec, govet, staticcheck.
Errcheck is relaxed for Close, Write, Publish, Stop, RemoveContainer, and zerowrap log methods.
Test files are exempt from gocyclo, errcheck, and gosec.
internal/
domain/ # Entities, value objects, sentinel errors, labels
boundaries/ # Port interfaces (in = driving, out = driven)
in/ # Service interfaces (ConfigService, ContainerService, etc.)
out/ # Infrastructure interfaces (ContainerRuntime, BlobStorage, etc.)
usecase/ # Business logic (container/, config/, registry/, proxy/, etc.)
adapters/ # Implementations
in/ # Driving adapters
http/admin/ # Admin HTTP API (handler.go)
cli/ # Cobra CLI commands
remote/ # HTTP client for remote Gordon instances
ui/ # Terminal UI components (tables, spinners, styles)
out/ # Driven adapters (docker/, filesystem/, sqlite/)
dto/ # Data transfer objects (request/response structs)
app/ # Application bootstrap (Kernel wiring)
Dependencies flow inward: adapters → boundaries → domain. Never import adapters from domain/usecase.
Three groups separated by blank lines: stdlib, external, internal.
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/bnema/gordon/internal/domain"
)- Sentinel errors in
internal/domain/errors.go— useerrors.New, check witherrors.Is. - Wrap errors with context:
fmt.Errorf("failed to list routes: %w", err). - Usecase/service methods return
error. Handlers translate to HTTP status codes. - CLI commands return
errorfromRunE— cobra prints it.
Use zerowrap (structured zerolog wrapper) in usecase and adapter layers:
ctx = zerowrap.CtxWithFields(ctx, map[string]any{
zerowrap.FieldLayer: "usecase",
zerowrap.FieldUseCase: "AddRoute",
"domain": route.Domain,
})
log := zerowrap.FromCtx(ctx)
log.Info().Str("image", route.Image).Msg("route added")CLI commands do NOT use zerowrap — they use cliWriteLine/cliWritef for output.
- Each command lives in its own file under
internal/adapters/in/cli/. - Use
cmd.OutOrStdout()for output (testable), never rawfmt.Println. - Use helpers:
cliWriteLine(out, msg),cliWritef(out, fmt, args...),cliRenderTitle,cliRenderMeta,cliRenderMuted,cliRenderSuccess,cliRenderWarning. - All list/show commands support
--jsonviawriteJSON(out, v). - Use
cmd.Context()for context (notcontext.Background()). - Commands register in
root.goundergroupServer,groupManage, orgroupClient. - Local/remote dispatch via
resolveControlPlane(configPath)orGetRemoteClient().
ControlPlane interface (controlplane.go) abstracts local vs remote operations.
controlplane_remote.go— delegates toremote.ClientHTTP methods.controlplane_local.go— calls service interfaces directly.- Test fakes in
push_test.go— update when adding interface methods.
- Single
Handlerstruct inhandler.gowithmatchRoutedispatcher. - Permission checks:
HasAccess(ctx, resource, action)at the top of each handler. - Response via
h.sendJSON(w, status, payload)orh.sendError(w, status, msg). - DTOs in
internal/adapters/dto/with JSON tags.
Generated by mockery (config in .mockery.yaml). Output: internal/boundaries/in/mocks/.
Only boundary interfaces are mocked. Run mockery after modifying any interface in boundaries/.
- Framework:
testing+testify(assert/require). - Test files:
*_test.goin the same package. - Use
httptest.NewServerfor HTTP handler tests. - Use mockery-generated mocks for boundary interfaces.
- Hand-rolled fakes for CLI
ControlPlanein test files.
- Files:
snake_case.go. - Types:
PascalCase. Interfaces describe capability (ConfigService,ContainerRuntime). - Functions:
PascalCaseexported,camelCaseunexported. - CLI constructors:
newXxxCmd()returns*cobra.Command. - Runner functions:
runXxx(ctx, ..., out, jsonOut). - Domain labels: constants in
domain/labels.goprefixed withLabel.
- CLI docs in
docs/cli/. Every command page has## Relatedlinks at the bottom. - Docs follow the pattern: header, subcommands table, usage examples, flags table, JSON output example.
- Run
golangci-lint run ./...before committing — CI enforces it. - gocyclo max 15 — extract helpers to reduce complexity.
- errcheck enabled — always handle all error returns gracefully by wrapping with context (fmt.Errorf) and logging critical errors with zerowrap.