Skip to content

Latest commit

 

History

History
114 lines (85 loc) · 7.48 KB

File metadata and controls

114 lines (85 loc) · 7.48 KB

AGENTS.md

Guidance for AI coding agents working in this repository (github.com/kolide/launcher). These are the authoritative Go conventions for generated code and reviews.

For concrete usage, look at current code in the repo rather than relying on inline snippets here — examples drift, the code does not. Grep for the relevant symbol (e.g. gowrapper.Go, rungroup.NewRunGroup, tablewrapper.New) to find live, correct examples.

Filesystem paths (Go)

  • Build paths with filepath.Join in any file that compiles on multiple platforms (plain .go files, _unix.go). Never concatenate separators manually (no base + "/" + name).
  • Use path/filepath, not path, for filesystem paths — path is slash-only and meant for URLs.
  • Hardcoded literal paths are acceptable only in single-platform files (_darwin.go, _windows.go, _linux.go, or behind a //go:build <os> constraint), especially when copied verbatim from vendor docs.

Go code generation rules for this repository (authoritative)

  • goroutines (required)

    • Do use ee/gowrapper.Go(ctx, slogger, func(){ ... }) or GoWithRecoveryAction instead of raw go func().
    • Never launch raw goroutines; they are forbidden in .golangci.yml and must be wrapped for panic logging and recovery.
    • Always pass a meaningful context.Context and a *slog.Logger.
  • early returns and error handling

    • Prefer guard clauses and early returns to reduce nesting.
    • Wrap and propagate errors with context using %w, e.g., fmt.Errorf("creating X: %w", err).
    • Do not use panic or os.Exit (forbidden by lints). Log errors with slog and return them.
    • Keep functions free of naked returns unless trivial.

Additional Go patterns for cross-platform and system integration

  • platform-specific code organization

    • Do use separate files with build constraints for platform-specific implementations.
    • Never use runtime.GOOS checks for code that won't compile on all platforms.
    • Split into _posix.go (for macOS + Linux), and otherwise _linux.go, _windows.go, _darwin.go files when using platform-specific packages.
  • temporary file lifecycle management

    • Do use agent.MkdirTemp() for temporary files with appropriate prefixes.
    • Do defer cleanup with error logging if cleanup fails.
    • Never leave temporary files without explicit cleanup.
  • system command execution

    • Do use ee/allowedcmd wrappers instead of direct exec.Command.
    • Do pass context for cancellation and timeouts.
    • Never use raw exec.Command (forbidden by forbidigo linter).

Testing patterns

  • table-driven tests (required pattern)

    • Do use table-driven tests for any function with multiple input/output scenarios.
    • Do use t.Run(tt.name, ...) to create named subtests for clear failure messages.
    • Do call t.Parallel() at both the top-level test function and inside each t.Run subtest.
    • Use tt (not tc) as the conventional loop variable for test cases in this codebase.
  • testify assertions

    • Use github.com/stretchr/testify/require (not assert) — require fails immediately on error, preventing cascading failures.
    • Common assertions: require.NoError, require.Error, require.Equal, require.EqualValues, require.Len, require.Contains, require.True, require.False, require.Nil, require.NotNil.
    • Add descriptive messages to assertions when helpful: require.NoError(t, err, "could not create test store").
  • test context and cleanup

    • Use t.Context() instead of context.Background() or context.TODO() in tests — this provides a context that is automatically cancelled when the test ends.
    • Use t.TempDir() for temporary directories — automatically cleaned up after the test.
    • Use t.Cleanup(func() { ... }) for registering teardown logic instead of defer when the cleanup must survive subtest boundaries.
  • mocks (mockery + testify)

    • Mocks are auto-generated by mockery into ee/agent/types/mocks/ (and similar mocks/ directories).
    • Interfaces annotated with //mockery:generate: true get mock implementations generated.
    • Do not edit generated mock files — regenerate them with mockery if the interface changes.
    • Instantiate mocks via constructors like typesmocks.NewKnapsack(t) or typesmocks.NewFlags(t) — they auto-register t.Cleanup to assert expectations.
    • Use .On("MethodName", args...).Return(values...) to set up expectations; use .Maybe() for optional calls.
  • test storage helpers (storageci)

    • Use storageci.NewStore(t, slogger, bucketName) from ee/agent/storage/ci to create test KV stores. It automatically uses in-memory storage in CI and bbolt locally.
    • Use storageci.SetupDB(t) to create a temporary bbolt database for tests — it handles t.TempDir() and t.Cleanup() for you.
    • Use storageci.MakeStores(t, slogger, db) to create all standard launcher stores at once for integration tests.
  • NopLogger for tests

    • Always use multislogger.NewNopLogger() (from pkg/log/multislogger) as the *slog.Logger in tests, unless you need to assert log output.
    • If you need to capture log output, create a real slogger writing to a threadsafebuffer.ThreadSafeBuffer.

Functional options pattern

  • This codebase uses the functional options pattern extensively for configurable constructors.
  • Define an unexported struct, a public Option type alias (type Option func(*myStruct)), and WithXxx functions returning Option.
  • Apply options with a variadic opts ...Option parameter in the constructor.

Observer pattern (FlagsChangeObserver)

  • Components that need to react to flag/configuration changes implement types.FlagsChangeObserver.
  • Register with flags.RegisterChangeObserver(observer, keys.SomeFlag, keys.AnotherFlag).
  • In FlagsChanged, check which keys changed using slices.Contains(flagKeys, keys.TargetKey) and react accordingly.
  • This pattern is used throughout the codebase (e.g., tablewrapper, TelemetryExporter, Runner).

Observability / tracing

  • Use observability.StartSpan(ctx, keyVals...) to create traced spans. Always defer span.End().
  • Key-value pairs in StartSpan follow the same snake_case key convention as slog.
  • Use observability.SetError(span, err) to record errors on spans.
  • Spans auto-extract caller information (file, line, function) for naming.

Custom osquery table pattern

  • All custom osquery tables must be wrapped with tablewrapper.New(...) for timeout protection and concurrency limiting.
  • Define a generate function matching func(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error).
  • Register columns with table.ColumnDefinition and pass everything through tablewrapper.New.
  • Use tablehelpers.MockQueryContext(...) in tests to simulate osquery query contexts.

Linting

  • Always run make lint after making changes to catch style and correctness issues before committing.
  • The linter configuration lives in .golangci.yml at the repo root.
  • Key linters enabled:
    • forbidigo: blocks raw go func(), os.Exit, panic, and direct exec.Command.
    • sloglint: enforces kv-only slog messages, snake_case keys, static messages, and context in all calls.
    • noctx / containedctx: ensures proper context passing (no embedded contexts in structs, no HTTP requests without context).
    • govet, errcheck, staticcheck: standard Go correctness linters.
  • To run: make lint (requires golangci-lint installed locally).