Architecture

The source is four packages and about 2,400 lines.

Where it lives

main.go calls into cmd. Packages: cmd, config, hosts, host.

The packages

cmd
The fisk command line, flag parsing, and process startup. Loads both configuration files, builds the Choria framework, starts the Prometheus listener and the signal handler, then calls hosts.Process. One file, cmd/provisioner.go.
config
The YAML configuration, its defaults, and its validation. Also holds the pause flag that rpcWrapper and runHelper check before every outbound action. Files: config.go, pausable.go, stats.go.
hosts
Fleet-level scheduling. Broadcast discovery, the lifecycle listener, a work queue, the worker pool, the finisher, and leader election. Files: hosts.go, event.go, provisioner.go, election.go, stats.go.
host
Everything that happens to one server. The provisioning sequence, the RPC wrappers, the helper invocation, and RPM version comparison. Files: host.go, rpc.go, helper.go, version.go, stats.go.

cmd imports hosts, hosts imports host, and all three import config, which imports none of them. host does not import hosts, so host/host_test.go builds a Host from a config.Config and a logger with no scheduler involved.

cmdflags, choria framework, /metrics, signalshostsdiscovery + eventswork queue + workersleader electionfinisherhostProvision()rpcWrapperrunHelperJWT + keysCSR checksrpm versionsconfigYAML loaddefaultsvalidationPause / ResumeFlip / Pausedimported byall three, importsnone of themhosts.Process(ctx, cfg, fw)host.NewHost / h.Provision
cmd imports hosts, hosts imports host, and nothing imports upward, so host compiles and tests on its own.

The boundaries

The helper, the pause flag, and the generated RPC clients each change without touching the other two.

  1. The helper Provisioner marshals a Host to JSON, writes it to a child process on standard input, and unmarshals the reply into host.ConfigResponse (host/helper.go:38). The helper reaches certificate authorities, CMDBs, and naming services, in any language.
  2. The pause flag config.Config declares Pause, Resume, Flip, and Paused (config/pausable.go). Leader election calls Pause and Resume, and both rpcWrapper and runHelper check the flag before sending an RPC request or starting the helper process.
  3. Generated RPC clients host/rpc.go uses two clients generated in go-choria, provclient.ChoriaProvisionClient for the choria_provision agent and rpcutilclient.RpcutilClient for rpcutil. Every call to either goes through rpcWrapper, which holds the retries, timing, and pause check for both.

Package-level state in hosts

The hosts package declares its state as package variables rather than struct fields (hosts/hosts.go:24-33): the hosts map of identity to *host.Host, the buffered work and done channels, a mutex, and the logger, the Choria framework, and the configuration.

var (
	hosts = make(map[string]*host.Host)
	work  = make(chan *host.Host, 50000)
	done  = make(chan *host.Host, 50000)
	mu    = &sync.Mutex{}
	log   *logrus.Entry
	fw    *choria.Framework
	conf  *config.Config
	wg    = &sync.WaitGroup{}
)
One instance per process

hosts.Process writes these package variables on entry, so exactly one instance can run per process. That matches the deployment model, where a process runs one Provisioner against one site, and the scheduler functions take no receiver. Running two Provisioners in one binary requires moving this state into a struct.

Configuration and startup

cmd.run loads the Provisioner YAML from --config through config.Load, and the Choria client configuration from --choria-config, defaulting to choria.UserConfig(). cmd.run then overrides the Choria client configuration in code (cmd/provisioner.go:61-79): log level and log file come from the Provisioner configuration, and the collective is forced to provisioning for both Collectives and MainCollective.

Setting choria_insecure disables TLS, sets protocol.Secure to false, and forces the file security provider. Setting broker_provisioning_password switches the NATS credentials to user provisioner with that password. The broker’s dedicated provisioning account accepts that user.

config.Load fills defaults and rejects configurations that cannot work (config/config.go:64-142):

RuleBehavior
workers unsetDefaults to runtime.NumCPU().
lifecycle_component unsetDefaults to provision_mode_server.
lifecycle_component contains ., >, or *Rejected, since the value is interpolated into a NATS subject.
cert_deny_list emptyDefaults to four patterns that block privileged Choria certificate names.
features.pki and features.ed25519 both setRejected. The two enrollment models are exclusive.
features.ed25519 setImplies features.jwt.
server_jwt_validity unsetDefaults to one year.
interval below one minuteRejected.

Observability

config/stats.go, hosts/stats.go, and host/stats.go each register their metrics in an init function: the pause gauge, the fleet-level counters, and per-server timing and errors. Every metric carries a site label taken from the site configuration key, so a query across installations can sum or group by site. cmd.setupPrometheus serves them on /metrics when monitor_port is set.

Next

Continue to Discovery and the work queue to see how a server reaches a worker.