hashicorp/consul
Patterns and conventions
Conventions used throughout the codebase. Match these when sending PRs.
Code layout
agent/holds everything an agent needs at runtime. The centralAgentstruct inagent/agent.goowns sub-systems (HTTP, DNS, local state, proxy config, checks, ...).agent/consul/holds server-only logic: Raft, the state store, RPC endpoints, leader loops, peering, gateways.internal/holds the v2 resource framework (controllers, storage, generic resource machinery) and its consumers.command/holds CLI subcommands; each subcommand is its own package.api/,sdk/,envoyextensions/,proto-public/,troubleshoot/are public Go modules (each with its owngo.mod).
When adding a new subsystem, prefer creating a sub-package under agent/ (or agent/consul/ for server-only) rather than growing agent/agent.go.
File naming
xxx.go— implementationxxx_test.go— testsxxx_ce.go/xxx_ce_test.go— Community Edition specific (ifdef pattern). Enterprise has matching_ent.gofiles in HashiCorp's private fork; they get merged into a unified codebase at build time.xxx_endpoint.go— RPC or HTTP endpoint definitionsxxx_endpoint_test.go— endpoint tests
Imports
Group imports as follows (enforced by the contributing guide):
import (
"context"
"fmt"
"net/http"
"github.com/hashicorp/go-cleanhttp"
"github.com/go-viper/mapstructure/v2"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/lib"
)Run goimports -local github.com/hashicorp/consul/ (or make fmt) to enforce.
Error handling
- Errors are wrapped with
fmt.Errorf("...: %w", err)so callers canerrors.Is/errors.Asthem. - Sentinel errors live next to the package they belong to (e.g.,
agent/structs/errors.go). - ACL errors use
acl.ErrPermissionDeniedand friends; the RPC layer maps them to user-visible messages. - Returned status codes from HTTP endpoints follow Go's
net/httpconventions, mapped inagent/http.go(HTTPHandlers.wrap).
Concurrency
- Background goroutines own a context and exit when it's cancelled. Use
lib/routineoragent/routine-leak-checker/patterns. - Long-running loops use jittered tickers (
lib/retry.Jitter) and respect the agent's shutdown channel. - Locks: prefer narrow critical sections; many state-store operations use the immutable MemDB pattern (read snapshot, write transaction).
Blocking queries and watches
When adding a new RPC that returns mutable data, support blocking queries:
err := s.blockingQuery(
&args.QueryOptions,
&reply.QueryMeta,
func(ws memdb.WatchSet, state *state.Store) error {
idx, value, err := state.MyThing(ws, args.Key)
if err != nil {
return err
}
reply.Index = idx
reply.Value = value
return nil
},
)The helper lives in agent/blockingquery/. The pattern is everywhere — search for s.blockingQuery( for examples.
RPC and gRPC
- Internal RPC uses
net/rpcwith msgpack codec on port 8300 and is registered inagent/consul/server.go(s.rpcServer.RegisterName). - gRPC services come in two flavors: internal (
agent/grpc-internal/) used by Consul-to-Consul traffic, and external (agent/grpc-external/) used by clients (dataplane, public APIs). - Public proto schemas live in
proto-public/; internal schemas inproto/.
When adding a new RPC, follow the existing XEndpoint struct pattern in agent/consul/x_endpoint.go. Each endpoint method is a request/reply pair on the registered struct.
Config entries
When adding a config-entry kind:
- Add the struct in
agent/structs/config_entry_<kind>.goand register it inagent/structs/config_entry.go(theMakeConfigEntryswitch). - Add the JSON ↔ HCL decode rules.
- Add validation, ACL evaluation, and DeepCopy.
- Update
agent/consul/config_endpoint.goif the kind needs special handling. - Update
agent/consul/state/config_entry.goto register the schema. - Apply changes to
api/config_entry.gofor the public client. - Wire it into the discovery chain (
agent/consul/discoverychain/) if applicable.
The full checklist is docs/contributing/checklist-adding-config-entry.md.
Adding agent config fields
See docs/config/checklist-adding-config-fields.md. The agent config is split between user-facing structs (agent/config/source_*.go), the in-memory RuntimeConfig (agent/config/runtime.go), and the builder that maps one to the other (agent/config/builder.go).
CE / Enterprise split
The _ce.go suffix marks files that contain only the Community Edition implementation. Enterprise has counterpart _ent.go files (not in this repo). Common patterns:
acl_ce.go: stub functions that return "not implemented in CE" or no-op for partition/namespace logic.*_ce_test.go: tests that only exist in CE.
When changing a _ce.go file, check whether the analogous Enterprise file exists; maintainers will point this out in review.
Logging style
logger := agentLogger.Named("rpc")
logger.Info("server forwarded RPC", "method", method, "to", server)
logger.Debug("query metadata", "index", idx, "duration", elapsed)Always use structured key/value pairs after the message. Pre-format only when computing the value is cheap.
Tests
- Use
requirefor fatal assertions andassertfor non-fatal. Both fromgithub.com/stretchr/testify. t.Runsub-tests for table-driven cases.- Use
t.Parallel()only after verifying the test doesn't share global state. - Build helpers in
testing.gofiles (not_test.go) when they need to be shared with other packages.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.