Skip to content

Latest commit

 

History

History
158 lines (114 loc) · 5.65 KB

File metadata and controls

158 lines (114 loc) · 5.65 KB

AGENTS.md — Gordon

Guidelines for AI coding agents operating in this repository.

Project

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 & Test Commands

# 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)
mockery

Lint 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.

Architecture — Hexagonal

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.

Code Style

Imports

Three groups separated by blank lines: stdlib, external, internal.

import (
    "context"
    "fmt"

    "github.com/spf13/cobra"

    "github.com/bnema/gordon/internal/domain"
)

Error Handling

  • Sentinel errors in internal/domain/errors.go — use errors.New, check with errors.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 error from RunE — cobra prints it.

Logging

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.

CLI Commands (Cobra)

  • Each command lives in its own file under internal/adapters/in/cli/.
  • Use cmd.OutOrStdout() for output (testable), never raw fmt.Println.
  • Use helpers: cliWriteLine(out, msg), cliWritef(out, fmt, args...), cliRenderTitle, cliRenderMeta, cliRenderMuted, cliRenderSuccess, cliRenderWarning.
  • All list/show commands support --json via writeJSON(out, v).
  • Use cmd.Context() for context (not context.Background()).
  • Commands register in root.go under groupServer, groupManage, or groupClient.
  • Local/remote dispatch via resolveControlPlane(configPath) or GetRemoteClient().

ControlPlane Pattern

ControlPlane interface (controlplane.go) abstracts local vs remote operations.

  • controlplane_remote.go — delegates to remote.Client HTTP methods.
  • controlplane_local.go — calls service interfaces directly.
  • Test fakes in push_test.go — update when adding interface methods.

HTTP Admin Handlers

  • Single Handler struct in handler.go with matchRoute dispatcher.
  • Permission checks: HasAccess(ctx, resource, action) at the top of each handler.
  • Response via h.sendJSON(w, status, payload) or h.sendError(w, status, msg).
  • DTOs in internal/adapters/dto/ with JSON tags.

Mocks

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/.

Testing

  • Framework: testing + testify (assert/require).
  • Test files: *_test.go in the same package.
  • Use httptest.NewServer for HTTP handler tests.
  • Use mockery-generated mocks for boundary interfaces.
  • Hand-rolled fakes for CLI ControlPlane in test files.

Naming

  • Files: snake_case.go.
  • Types: PascalCase. Interfaces describe capability (ConfigService, ContainerRuntime).
  • Functions: PascalCase exported, camelCase unexported.
  • CLI constructors: newXxxCmd() returns *cobra.Command.
  • Runner functions: runXxx(ctx, ..., out, jsonOut).
  • Domain labels: constants in domain/labels.go prefixed with Label.

Documentation

  • CLI docs in docs/cli/. Every command page has ## Related links at the bottom.
  • Docs follow the pattern: header, subcommands table, usage examples, flags table, JSON output example.

Key Rules

  1. Run golangci-lint run ./... before committing — CI enforces it.
  2. gocyclo max 15 — extract helpers to reduce complexity.
  3. errcheck enabled — always handle all error returns gracefully by wrapping with context (fmt.Errorf) and logging critical errors with zerowrap.