Files
unkinben 373d21a744 initial implementation: encapi ENC server + CLI
Postgres-backed External Node Classifier for Puppet, replacing Cobbler.
- encapi HTTP server (chi + pgx): read/write API + two ENC document shapes
  (reshaped for the exec terminus; cobbler-wire for enc_direct_facts.rb)
- encapi-cli: classify/node/role/status CRUD + import-cobbler seeder
- pkg/client Go SDK; unit tests across all packages (DB via testcontainers)
- Dockerfile (distroless), Makefile, nfpm RPM (encapi-cli + encapi-enc wrapper),
  Woodpecker CI, docs/cutover.md
2026-07-04 23:45:15 +10:00

3.2 KiB

Cutover: Cobbler ENC → encapi

Today Cobbler is Puppet's ENC. Two consumers hit http://cobbler.main.unkin.net/cblr/svc/op/puppet/hostname/<host>:

  1. The exec ENCpuppet-prod sets, in site/profiles/manifests/puppet/server.pp:

    node_terminus  = exec
    external_nodes = /opt/cobbler-enc/cobbler-enc
    

    /opt/cobbler-enc/cobbler-enc (templated from site/profiles/templates/puppet/server/cobbler-enc.erb) fetches Cobbler and reshapes it (list-form classes, drops environment when testing, adds enc_role/enc_env).

  2. The enc_role/enc_env factsmodules/libs/lib/facter/enc_direct_facts.rb, a fact on every node, fetches the same Cobbler endpoint (cached 7d at /var/cache/puppet_enc.yaml) and derives enc_role = classes.keys.first, enc_env = environment. Those facts drive enc_role_tier1/2/3 + enc_role_path, i.e. the whole Hiera hierarchy.

encapi serves drop-in replacements for both.

1. Seed encapi from the current estate

export ENCAPI_URL=https://encapi.k8s.syd1.au.unkin.net
export ENCAPI_WRITE_TOKEN=…
encapi-cli import-cobbler --dry-run     # review
encapi-cli import-cobbler               # writes statuses, roles, nodes

import-cobbler lists hosts from PuppetDB (http://puppetdbapi.service.consul:8080), reads each host's Cobbler ENC, and creates the derived status, role, and node.

2. Repoint the exec ENC (profiles::puppet::cobbler_enc)

Install the CLI RPM on the puppet masters (it ships encapi-cli, /usr/local/bin/encapi-enc, and /etc/encapi/enc.conf), set ENCAPI_URL in /etc/encapi/enc.conf, and point Puppet at the wrapper:

node_terminus  = exec
external_nodes = /usr/local/bin/encapi-enc

encapi-enc <certname> runs encapi-cli classify <certname>, which returns the identical reshaped document (GET /api/v1/nodes/<certname>/enc).

3. Repoint the enc_direct_facts.rb fact

Change only the base URL in enc_direct_facts.rb:

uri = URI("https://encapi.k8s.syd1.au.unkin.net/cblr/svc/op/puppet/hostname/#{...}")

encapi's /cblr/svc/op/puppet/hostname/<host> returns the cobbler-wire shape (classes as a map, environment always present), so classes.keys.first and environment still resolve. No other fact logic changes.

4. Verify before flipping

For a sample of hosts, diff the old and new output:

diff <(curl -s "http://cobbler.main.unkin.net/cblr/svc/op/puppet/hostname/$H") \
     <(curl -s "https://encapi.k8s.syd1.au.unkin.net/cblr/svc/op/puppet/hostname/$H")

diff <(/opt/cobbler-enc/cobbler-enc "$H") \
     <(encapi-cli classify "$H")

The classes/environment/enc_role/enc_env fields must match. (Cobbler's provisioning params — epel, tree, from_cobbler, operatingsystemrelease — are intentionally dropped: they are unused by the Puppet manifests. If a future need arises, wire ENCAPI_DISTRO_API_URL to the kickstart-replacement API and they reappear under parameters.)

5. Decommission

Once masters and agents are repointed and a Puppet run is clean, retire profiles::puppet::cobbler_enc and Cobbler's ENC role. Cobbler can keep doing provisioning/kickstart; only its ENC duty moves to encapi.