From 1e48c06bfe8a5466b579ee043102fa173bf766c0 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sat, 19 Sep 2026 18:12:47 +1000 Subject: [PATCH] Package encapic as an RPM published to rpm-internal The VM puppet masters are dnf-managed hosts moving off cobbler onto encapi, and encapic ships only as a release binary the k8s compilers curl. - drive the release with goreleaser, keeping the encapic_linux_amd64 asset and its .sha256 byte-compatible with the compiler initContainer - build encapic--1.x86_64.rpm and PUT it to artifactapi rpm-internal - install /usr/bin/encapic, the /usr/bin/encapic-enc ENC entrypoint and /etc/encapic/encapic.conf as %config(noreplace) - add make rpm/rpm-package/pre-commit and a PR packaging check --- .goreleaser.yaml | 79 ++++++++++++++++++++++++++++++++++++++++ .woodpecker/build.yaml | 22 +++++++++++ .woodpecker/release.yaml | 69 ++++++++++++++++++++--------------- Makefile | 14 ++++++- README.md | 29 ++++++++++++++- packaging/encapic-enc | 13 +++++++ packaging/encapic.conf | 4 ++ 7 files changed, 198 insertions(+), 32 deletions(-) create mode 100644 .goreleaser.yaml create mode 100755 packaging/encapic-enc create mode 100644 packaging/encapic.conf diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..21567d9 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,79 @@ +version: 2 + +project_name: encapic + +builds: + - id: encapic + main: . + binary: encapic + env: + - CGO_ENABLED=0 + goos: + - linux + goarch: + - amd64 + flags: + - -trimpath + ldflags: + - -s -w -X main.version={{ .Version }} + +archives: + # Raw binary, not an archive: the puppetserver-compiler initContainer fetches + # this exact asset name by URL and sha256-verifies it before installing it. + - id: binaries + ids: [encapic] + formats: [binary] + name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" + +nfpms: + - id: encapic + ids: [encapic] + package_name: encapic + # encapic--1.x86_64.rpm + file_name_template: "{{ .ConventionalFileName }}" + vendor: unkin + homepage: https://git.unkin.net/unkin/encapic + maintainer: Ben Vincent + description: Puppet exec ENC client for encapi + license: MIT + formats: + - rpm + release: "1" + bindir: /usr/bin + contents: + # Puppet's exec node_terminus passes a certname and no environment, so + # external_nodes points at this shim, which sources the URL first. + - src: packaging/encapic-enc + dst: /usr/bin/encapic-enc + file_info: + mode: 0755 + # noreplace: puppet manages this file's contents (hiera-driven URL), so an + # upgrade must not overwrite it. + - src: packaging/encapic.conf + dst: /etc/encapic/encapic.conf + type: "config|noreplace" + file_info: + mode: 0644 + +checksum: + # One .sha256 per file rather than a single checksums.txt: the + # compiler initContainer fetches encapic_linux_amd64.sha256 next to the binary. + split: true + name_template: "{{ .ArtifactName }}.sha256" + +release: + gitea: + owner: unkin + name: encapic + +gitea_urls: + api: https://git.unkin.net/api/v1 + download: https://git.unkin.net + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^chore:" diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml index 74123b5..322dfdc 100644 --- a/.woodpecker/build.yaml +++ b/.woodpecker/build.yaml @@ -16,3 +16,25 @@ steps: limits: memory: 2Gi cpu: 2 + + # The release itself only runs on a tag, so validate the packaging config and + # build the artifacts (binary + RPM) here without publishing anything. + - name: package + image: golang:1.26 + commands: + - git fetch --tags --force + - go install github.com/goreleaser/goreleaser/v2@v2.17.1 + - goreleaser check + - goreleaser release --snapshot --clean + - ls -l dist/*.rpm dist/*.sha256 + depends_on: [build] + backend_options: + kubernetes: + serviceAccountName: default + resources: + requests: + memory: 512Mi + cpu: 1 + limits: + memory: 2Gi + cpu: 2 diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml index a0955ea..e3565aa 100644 --- a/.woodpecker/release.yaml +++ b/.woodpecker/release.yaml @@ -18,13 +18,21 @@ steps: memory: 2Gi cpu: 2 - # Build the static linux/amd64 binary and its checksum, both attached to the - # Gitea release. The binary is what the puppet compilers pull at pod start. - - name: build - image: golang:1.25 + # goreleaser is the single driver for the release: it builds the static + # binary, builds the RPM (nfpm is built in) and creates the Gitea release + # with the binary, RPM and per-artifact sha256 files attached. The binary + # asset name is unchanged — the puppet compilers pull it at pod start. + - name: goreleaser + image: golang:1.26 + environment: + GITEA_TOKEN: + from_secret: RELEASER_TOKEN commands: - - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.version=${CI_COMMIT_TAG}" -o encapic_linux_amd64 . - - sha256sum encapic_linux_amd64 > encapic_linux_amd64.sha256 + # goreleaser reads the tag history for the changelog; Woodpecker's clone + # can be shallow. + - git fetch --tags --force + - go install github.com/goreleaser/goreleaser/v2@v2.17.1 + - goreleaser release --clean depends_on: [test] backend_options: kubernetes: @@ -37,35 +45,38 @@ steps: memory: 2Gi cpu: 2 - # Cut a Gitea release with the binary + checksum attached. - - name: release + # goreleaser cannot PUT to artifactapi, so the RPM it built into dist/ is + # published to the local rpm repo here (a real yum repo; repodata + # regenerates automatically). + - name: upload-rpm image: git.unkin.net/unkin/almalinux9-base:20260606 - environment: - RELEASER_TOKEN: - from_secret: RELEASER_TOKEN commands: - | - curl --output /usr/local/bin/tea https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/gitea-dl/tea/0.12.0/tea-0.12.0-linux-amd64 && chmod +x /usr/local/bin/tea - tea logins add --name gitea --url https://git.unkin.net --token "$${RELEASER_TOKEN}" --no-version-check - PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") - if [ -n "$PREV_TAG" ]; then - NOTES=$(git log "${PREV_TAG}..${CI_COMMIT_TAG}" --pretty=format:"- %s") - else - NOTES=$(git log --pretty=format:"- %s") - fi - tea releases create --tag "${CI_COMMIT_TAG}" --title "${CI_COMMIT_TAG}" --note "${NOTES}" --login gitea --repo "${CI_REPO}" - tea releases assets create "${CI_COMMIT_TAG}" \ - encapic_linux_amd64 \ - encapic_linux_amd64.sha256 \ - --login gitea --repo "${CI_REPO}" - depends_on: [build] + HOST="https://artifactapi.k8s.syd1.au.unkin.net" + REPO="rpm-internal" + for rpm in dist/*.rpm; do + FILE=$$(basename "$$rpm") + # artifactapi has no HEAD route (returns 405); probe with GET against + # the served path (RPMs are stored under Packages/) to avoid re-upload. + code=$$(curl -s -o /dev/null -w '%{http_code}' "$$HOST/api/v2/remotes/$$REPO/files/Packages/$$FILE" || true) + if [ "$$code" = "200" ]; then + echo "$$FILE already exists in $$REPO (HTTP $$code); skipping upload" + continue + fi + echo "Uploading $$FILE to $$REPO (existence probe returned $$code)" + curl -f -X PUT \ + "$$HOST/api/v2/remotes/$$REPO/files/$$FILE" \ + -H "Content-Type: application/x-rpm" \ + --data-binary @"$$rpm" + done + depends_on: [goreleaser] backend_options: kubernetes: serviceAccountName: default resources: requests: - memory: 128Mi - cpu: 100m - limits: memory: 512Mi - cpu: 500m + cpu: 1 + limits: + memory: 2Gi + cpu: 2 diff --git a/Makefile b/Makefile index 0b505f1..a92247c 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ GOFLAGS := -ldflags="-s -w -X main.version=$(VERSION)" OS ?= $(shell go env GOOS) ARCH ?= $(shell go env GOARCH) -.PHONY: all build test lint fmt clean install patch minor major _tag +.PHONY: all build test lint fmt pre-commit clean install rpm rpm-package patch minor major _tag all: build @@ -22,12 +22,24 @@ lint: fmt: gofmt -w . +pre-commit: + uvx pre-commit run --all-files + clean: rm -rf $(DIST) $(BINARY) install: go install $(GOFLAGS) . +# Build every release artifact locally without publishing: the raw binary the +# puppet compilers fetch, and the RPM the VM masters install. +rpm: rpm-package + +# goreleaser drives the build and the nfpm packaging in one pass, so packaging +# an already-built binary is not a separate step. +rpm-package: + goreleaser release --snapshot --clean + # Bump helpers — read the latest semver tag and create the next one. # If no tag exists yet, start from v0.0.0. _LATEST := $(shell git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$$' | head -1) diff --git a/README.md b/README.md index 513c329..11a2220 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,33 @@ make build # static binary into dist/ make test # go test -v -race ./... make lint # golangci-lint make fmt # gofmt -w . +make rpm # binary + RPM into dist/ via goreleaser, publishing nothing ``` Release: `make minor` (etc.) tags `vX.Y.Z` and pushes it; the `release` -Woodpecker pipeline builds `encapic_linux_amd64` (+ `.sha256`) and attaches -them to a Gitea release. +Woodpecker pipeline runs goreleaser, which attaches `encapic_linux_amd64` +(+ `.sha256`) and the RPM to a Gitea release, and then publishes the RPM to the +artifactapi `rpm-internal` yum repo. + +## Installation + +The kubernetes compilers fetch `encapic_linux_amd64` from the Gitea release in +an initContainer and install it to `/opt/bin/encapic`. + +The VM puppet masters install the RPM from `rpm-internal`: + +``` +dnf install encapic +``` + +It ships three paths: + +- `/usr/bin/encapic` — the binary; +- `/usr/bin/encapic-enc` — the ENC entrypoint. Puppet's exec `node_terminus` + invokes `external_nodes` with a certname and no environment, so the shim + sources the URL before exec'ing the binary. Point `external_nodes` at this, + not at `/usr/bin/encapic`; +- `/etc/encapic/encapic.conf` — `ENCAPI_URL=...`, defaulting to the external + address `https://encapi.k8s.syd1.au.unkin.net` because a VM cannot resolve + the in-cluster default. Shipped `%config(noreplace)` so puppet may own its + contents without an upgrade reverting them. diff --git a/packaging/encapic-enc b/packaging/encapic-enc new file mode 100755 index 0000000..88fbd22 --- /dev/null +++ b/packaging/encapic-enc @@ -0,0 +1,13 @@ +#!/bin/sh +# Puppet's exec node_terminus invokes external_nodes with a certname and no +# environment, so the encapi URL cannot be inherited; source it here instead. +set -eu + +if [ -r /etc/encapic/encapic.conf ]; then + . /etc/encapic/encapic.conf +fi +if [ -n "${ENCAPI_URL:-}" ]; then + export ENCAPI_URL +fi + +exec /usr/bin/encapic "$@" diff --git a/packaging/encapic.conf b/packaging/encapic.conf new file mode 100644 index 0000000..481e61d --- /dev/null +++ b/packaging/encapic.conf @@ -0,0 +1,4 @@ +# Base URL of the encapi service, read by /usr/bin/encapic-enc. +# The binary's compiled-in default is the in-cluster address, which VM puppet +# masters cannot resolve. +ENCAPI_URL=https://encapi.k8s.syd1.au.unkin.net