mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-13 08:59:08 +02:00
* [relay] Add a release-wired UBI image variant * [relay] Align the UBI image with the shared UBI pattern Main now ships UBI variants of the client, combined server and proxy that share one license collector and one image layout, and the Red Hat certification workflow expects every certified image to carry both amd64 and arm64. The relay variant predated that, kept its own copy of the license script, built amd64 only, and ran as UID 65532 on the privileged default port :443, so it would not start under OpenShift or rootless Podman. Use release_files/collect-licenses.sh and build amd64 and arm64 like the other UBI images. Run as 1000:0 with a group-0 writable /var/lib/netbird for Let's Encrypt data, and default the listener to :8443 so an arbitrary non-root UID can bind it; the relay's Let's Encrypt flow uses TLS-ALPN on that same listener, so no separate challenge port is needed. * [infrastructure] Certify the relay UBI image on release Releases certify the UBI images through redhat-certify.yml, which only lists the client, proxy and combined server, so a released relay UBI image would never reach the Red Hat catalog. Add relay to the component list and the manual dispatch options. Its Partner Connect ID comes from REDHAT_CERT_ID_RELAY, which must exist before this merges: the resolve step fails the whole run when any component's variable is missing.
206 lines
8.2 KiB
YAML
206 lines
8.2 KiB
YAML
name: Red Hat Certification
|
|
|
|
# Certify published UBI images in the Red Hat Ecosystem Catalog. Called by
|
|
# release.yml on stable tags, or run by hand to (re)certify any released
|
|
# version. preflight submits every architecture of an image's manifest list
|
|
# to Pyxis; auto-publish on the component makes it public once certified.
|
|
#
|
|
# Each component's Partner Connect ID comes from the REDHAT_CERT_ID_<NAME>
|
|
# repository variable, e.g. REDHAT_CERT_ID_CLIENT_ROOTLESS. The run fails
|
|
# before certifying anything if a selected component's variable is not set.
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
component:
|
|
type: string
|
|
required: true
|
|
version:
|
|
type: string
|
|
required: true
|
|
secrets:
|
|
PYXIS_API_TOKEN:
|
|
required: true
|
|
workflow_dispatch:
|
|
inputs:
|
|
component:
|
|
description: "Component to certify"
|
|
type: choice
|
|
required: true
|
|
default: all
|
|
options:
|
|
- all
|
|
- client-rootless
|
|
- reverse-proxy
|
|
- netbird-server
|
|
- signal
|
|
- relay
|
|
version:
|
|
description: "Released version, e.g. v0.80.0"
|
|
type: string
|
|
required: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
resolve:
|
|
name: Resolve components
|
|
runs-on: ubuntu-24.04
|
|
outputs:
|
|
version: ${{ steps.resolve.outputs.version }}
|
|
matrix: ${{ steps.resolve.outputs.matrix }}
|
|
steps:
|
|
- name: Resolve components and images
|
|
id: resolve
|
|
env:
|
|
COMPONENT: ${{ inputs.component }}
|
|
INPUT_VERSION: ${{ inputs.version }}
|
|
REPO_VARS: ${{ toJSON(vars) }}
|
|
run: |
|
|
set -euo pipefail
|
|
version="${INPUT_VERSION#v}"
|
|
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "::error::Only stable x.y.z versions are certified, got '${INPUT_VERSION}'"
|
|
exit 1
|
|
fi
|
|
# name, image repository, tag suffix (must match .goreleaser.yaml).
|
|
# Keep the names in sync with the workflow_dispatch options above.
|
|
components=(
|
|
"client-rootless ghcr.io/netbirdio/netbird -rootless-ubi"
|
|
"reverse-proxy ghcr.io/netbirdio/reverse-proxy -ubi"
|
|
"netbird-server ghcr.io/netbirdio/netbird-server -ubi"
|
|
"signal ghcr.io/netbirdio/signal -ubi"
|
|
"relay ghcr.io/netbirdio/relay -ubi"
|
|
)
|
|
matrix="[]"
|
|
missing=()
|
|
for c in "${components[@]}"; do
|
|
read -r name repo suffix <<< "$c"
|
|
[[ "$COMPONENT" == all || "$COMPONENT" == "$name" ]] || continue
|
|
var="REDHAT_CERT_ID_${name^^}"; var="${var//-/_}"
|
|
id="$(jq -r --arg v "$var" '.[$v] // empty' <<< "$REPO_VARS")"
|
|
if [[ -z "$id" ]]; then
|
|
missing+=("$var")
|
|
continue
|
|
fi
|
|
matrix="$(jq -c --arg n "$name" --arg t "${version}${suffix}" --arg r "${repo}:${version}${suffix}" --arg i "$id" \
|
|
'. + [{component: $n, tag: $t, ref: $r, component_id: $i}]' <<< "$matrix")"
|
|
done
|
|
if (( ${#missing[@]} )); then
|
|
echo "::error::Set these repository variables to the Partner Connect component IDs: ${missing[*]}"
|
|
exit 1
|
|
fi
|
|
if [[ "$matrix" == "[]" ]]; then
|
|
echo "::error::No component to certify for '${COMPONENT}'"
|
|
exit 1
|
|
fi
|
|
echo "Components to certify: ${matrix}"
|
|
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
|
echo "matrix=${matrix}" >> "$GITHUB_OUTPUT"
|
|
|
|
certify:
|
|
name: "Certify ${{ matrix.component }} UBI image"
|
|
needs: resolve
|
|
runs-on: ubuntu-24.04
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include: ${{ fromJSON(needs.resolve.outputs.matrix) }}
|
|
env:
|
|
PREFLIGHT_VERSION: "1.21.0"
|
|
# sha256 of preflight-linux-amd64 from the 1.21.0 GitHub release.
|
|
# Red Hat publishes no checksum file, so the value is pinned here.
|
|
PREFLIGHT_SHA256: "5e653135503c72f8702bbe31d7643197d12937c68086879133dd6b9650a9a449"
|
|
steps:
|
|
- name: Verify the multi-arch image is on ghcr.io
|
|
env:
|
|
IMAGE_REF: ${{ matrix.ref }}
|
|
run: |
|
|
set -euo pipefail
|
|
docker buildx imagetools inspect "$IMAGE_REF" --raw > manifest.json
|
|
for arch in amd64 arm64; do
|
|
if ! jq -e --arg a "$arch" '.manifests[] | select(.platform.architecture == $a)' manifest.json > /dev/null; then
|
|
echo "::error::${IMAGE_REF} has no ${arch} manifest"
|
|
exit 1
|
|
fi
|
|
done
|
|
echo "Manifest list for ${IMAGE_REF}:"
|
|
jq -r '.manifests[] | "\(.platform.os)/\(.platform.architecture) \(.digest)"' manifest.json
|
|
|
|
- name: Install preflight
|
|
run: |
|
|
set -euo pipefail
|
|
curl -fsSL --proto '=https' --proto-redir '=https' -o preflight \
|
|
"https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases/download/${PREFLIGHT_VERSION}/preflight-linux-amd64"
|
|
echo "${PREFLIGHT_SHA256} preflight" | sha256sum -c -
|
|
chmod +x preflight
|
|
./preflight --version
|
|
|
|
- name: Run preflight checks and submit to Red Hat
|
|
env:
|
|
IMAGE_REF: ${{ matrix.ref }}
|
|
PFLT_PYXIS_API_TOKEN: ${{ secrets.PYXIS_API_TOKEN }}
|
|
PFLT_CERTIFICATION_COMPONENT_ID: ${{ matrix.component_id }}
|
|
PFLT_ARTIFACTS: artifacts
|
|
PFLT_LOGFILE: artifacts/preflight.log
|
|
PFLT_LOGLEVEL: info
|
|
PFLT_JUNIT: "true"
|
|
run: |
|
|
set -euo pipefail
|
|
# No --platform: preflight walks the manifest list and submits every
|
|
# architecture in one run, grouped under one manifest-list digest.
|
|
# preflight does not create the PFLT_LOGFILE directory, and --submit
|
|
# fails if the log file is missing.
|
|
mkdir -p artifacts
|
|
./preflight check container "$IMAGE_REF" --submit
|
|
|
|
- name: Fail if any check did not pass
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob globstar
|
|
results=(artifacts/**/results.json)
|
|
if [[ ${#results[@]} -eq 0 ]]; then
|
|
echo "::error::preflight produced no results.json"
|
|
exit 1
|
|
fi
|
|
status=0
|
|
for f in "${results[@]}"; do
|
|
arch="$(basename "$(dirname "$f")")"
|
|
passed="$(jq -r '.passed' "$f")"
|
|
failed="$(jq -r '[.results.failed[]?.name] | join(", ")' "$f")"
|
|
echo "${arch}: passed=${passed} ${failed:+failed checks: ${failed}}"
|
|
[[ "$passed" == "true" ]] || status=1
|
|
done
|
|
exit $status
|
|
|
|
- name: Upload preflight artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: redhat-preflight-${{ matrix.component }}-${{ needs.resolve.outputs.version }}
|
|
path: artifacts/
|
|
retention-days: 30
|
|
|
|
- name: Wait for Pyxis to mark both architectures certified
|
|
env:
|
|
TAG: ${{ matrix.tag }}
|
|
COMPONENT_ID: ${{ matrix.component_id }}
|
|
PFLT_PYXIS_API_TOKEN: ${{ secrets.PYXIS_API_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Filter on the tag server-side so older versions are found past the first page.
|
|
url="https://catalog.redhat.com/api/containers/v1/projects/certification/id/${COMPONENT_ID}/images?filter=repositories.tags.name==${TAG}&page_size=100"
|
|
for attempt in $(seq 1 20); do
|
|
certified="$(curl -fsS --proto '=https' --proto-redir '=https' -H "X-API-KEY: ${PFLT_PYXIS_API_TOKEN}" "$url" \
|
|
| jq -r --arg t "$TAG" '[.data[] | select(.repositories[]?.tags[]?.name == $t) | select(.certified == true) | .architecture] | unique | join(",")')"
|
|
echo "attempt ${attempt}: certified architectures for ${TAG}: ${certified:-none}"
|
|
if [[ "$certified" == "amd64,arm64" ]]; then
|
|
echo "Both architectures certified. Auto-publish is enabled on the component, so the catalog updates on its own."
|
|
exit 0
|
|
fi
|
|
sleep 30
|
|
done
|
|
echo "::error::Pyxis has not marked both architectures certified after 10 minutes. Check https://connect.redhat.com/component/view/${COMPONENT_ID}/images"
|
|
exit 1
|