Swap puppet compiler ENC to the encapic Go binary (#277)

## Why

The puppet-on-k8s compilers classify nodes with a uv/python ENC script (`encapi-enc`). Each fresh compiler pod resolves the script's python dependencies on first invocation, and that resolution fails on cold pods (observed exits 135/2), breaking puppet agent catalog compilation. `encapic` (git.unkin.net/unkin/encapic) is a stdlib-only Go replacement with no runtime dependency resolution — a behavioural drop-in whose output matches the python script byte-for-byte.

## Changes

- Points the compiler `external_nodes` at `/opt/bin/encapic`.
- Reworks the `setup-shared-bins` init container to `curl` the encapic `v0.1.0` `encapic_linux_amd64` release binary (sha256-verified against the published `.sha256`, installed mode 0755) into the shared bins dir, instead of copying the python script and installing uv.
- Removes the `puppet-encapi-enc` configmap generator, its volume and mount, and the `resources/encapi-enc` script. uv was consumed solely by that script (grep of `apps/base/puppet` confirms no other consumer), so its installation is removed too.

`kubectl kustomize apps/overlays/au-syd1/puppet` builds clean.

## Merge gate

Do not merge until the encapic `v0.1.0` release assets exist:
`https://git.unkin.net/unkin/encapic/releases/download/v0.1.0/encapic_linux_amd64` (+ `.sha256`). The init container pulls them at pod start.

## Rollback

Revert this PR to restore the `encapi-enc` configmap script + uv install and repoint `external_nodes`.

---------

Co-authored-by: benvin <neotheo@gmail.com>
Reviewed-on: #277
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
This commit was merged in pull request #277.
This commit is contained in:
2026-07-25 12:40:42 +10:00
committed by BenVincent
parent 3af12180fd
commit 05318553d6
4 changed files with 14 additions and 77 deletions
@@ -187,28 +187,28 @@ spec:
- -c - -c
args: args:
- | - |
set -e
echo "Setting up shared binaries..." echo "Setting up shared binaries..."
mkdir -p /opt/bin mkdir -p /opt/bin
mkdir -p /opt/bin/.cache/uv
# Copy encapi ENC script to shared bin volume # Install the encapic ENC client (stdlib-only Go binary) to the shared
cp /configmaps/encapi-enc /opt/bin/encapi-enc # bin volume. It replaces the uv/python ENC script, whose
chmod +x /opt/bin/encapi-enc # first-invocation dependency resolution failed on fresh compiler pods.
ENCAPIC_VERSION=v0.1.0
# Install uv to shared bin volume BASE=https://git.unkin.net/unkin/encapic/releases/download/$ENCAPIC_VERSION
cd /tmp cd /tmp
wget -O uv-x86_64-unknown-linux-gnu.tar.gz https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/github/astral-sh/uv/releases/download/0.9.20/uv-x86_64-unknown-linux-gnu.tar.gz curl -fsSL -o encapic "$BASE/encapic_linux_amd64"
tar xf uv-x86_64-unknown-linux-gnu.tar.gz curl -fsSL -o encapic.sha256 "$BASE/encapic_linux_amd64.sha256"
cp uv-x86_64-unknown-linux-gnu/uv /opt/bin/uv # The published checksum names the release asset; verify against the
chmod +x /opt/bin/uv # file we downloaded regardless of the recorded filename.
EXPECTED=$(awk '{print $1}' encapic.sha256)
echo "$EXPECTED encapic" | sha256sum -c -
install -m 0755 encapic /opt/bin/encapic
echo "Shared binaries setup completed" echo "Shared binaries setup completed"
volumeMounts: volumeMounts:
- mountPath: /opt/bin/ - mountPath: /opt/bin/
name: puppet-shared-bins name: puppet-shared-bins
- mountPath: /configmaps/encapi-enc
name: puppet-encapi-enc
subPath: encapi-enc
securityContext: securityContext:
fsGroup: 999 fsGroup: 999
volumes: volumes:
@@ -231,9 +231,6 @@ spec:
- name: compiler-autosign-conf - name: compiler-autosign-conf
configMap: configMap:
name: compiler-autosign.conf name: compiler-autosign.conf
- name: puppet-encapi-enc
configMap:
name: puppet-encapi-enc
- name: puppet-shared-bins - name: puppet-shared-bins
persistentVolumeClaim: persistentVolumeClaim:
claimName: puppet-shared-bins claimName: puppet-shared-bins
-5
View File
@@ -53,11 +53,6 @@ configMapGenerator:
- resources/compiler/puppetdb.conf - resources/compiler/puppetdb.conf
options: options:
disableNameSuffixHash: true disableNameSuffixHash: true
- name: puppet-encapi-enc
files:
- resources/encapi-enc
options:
disableNameSuffixHash: true
- name: additional-ruby-gems - name: additional-ruby-gems
files: files:
- resources/additional-ruby-gems.sh - resources/additional-ruby-gems.sh
@@ -11,7 +11,7 @@ logdir = /var/log/puppetlabs/puppetserver
rundir = /var/run/puppetlabs/puppetserver rundir = /var/run/puppetlabs/puppetserver
pidfile = /var/run/puppetlabs/puppetserver/puppetserver.pid pidfile = /var/run/puppetlabs/puppetserver/puppetserver.pid
node_terminus = exec node_terminus = exec
external_nodes = /opt/bin/encapi-enc external_nodes = /opt/bin/encapic
autosign = /etc/puppetlabs/puppet/autosign.conf autosign = /etc/puppetlabs/puppet/autosign.conf
storeconfigs = true storeconfigs = true
storeconfigs_backend = puppetdb storeconfigs_backend = puppetdb
-55
View File
@@ -1,55 +0,0 @@
#!/usr/bin/env -S /opt/bin/uv run --quiet --cache-dir /opt/bin/.cache/uv --script
# /// script
# requires-python = ">=3.11"
# dependencies = ['pyyaml','requests']
# ///
"""
External Node Classifier (ENC) for Puppet.
If the environment specified in the YAML file is 'testing',
the environment is not included in the output.
"""
import os
import sys
import yaml
import requests
# In-cluster encapi service (cobbler-wire-compatible endpoint). Plain HTTP,
# so no CA bundle is needed. Overridable via ENCAPI_URL.
ENCAPI_URL = os.environ.get("ENCAPI_URL", "http://encapi.encapi.svc.cluster.local")
def fetch_enc_data(base_url: str, hostname: str) -> str:
"""
Fetches and modifies ENC data from a given URL to ensure classes are in list format.
"""
url = f"{base_url}/cblr/svc/op/puppet/hostname/{hostname}"
try:
response = requests.get(url)
response.raise_for_status()
except requests.RequestException as e:
sys.exit(f"Request failed: {e}")
data = yaml.safe_load(response.text)
data["parameters"] = data.get("parameters", {})
# Ensure 'classes' is in the desired list format
if "classes" in data:
if isinstance(data["classes"], dict):
data["parameters"]["enc_role"] = list(data["classes"].keys())
data["classes"] = list(data["classes"].keys())
else:
data["parameters"]["enc_role"] = list(data["classes"])
data["classes"] = list(data["classes"])
if "environment" in data:
data["parameters"]["enc_env"] = data["environment"]
if data["environment"] == "testing":
del data["environment"]
return yaml.dump(data)
if __name__ == "__main__":
if len(sys.argv) != 2:
sys.exit(f"Usage: {sys.argv[0]} <hostname>")
print(fetch_enc_data(ENCAPI_URL, sys.argv[1]))