release: cut v1.2.1 #25
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: release | |
| # Triggers: | |
| # * tag push (vX.Y.Z) — full release: builds packages for every distro | |
| # in the matrix, then attaches them to a GitHub Release. | |
| # * manual dispatch — same matrix, but artifacts go to the workflow run | |
| # only (no Release object created). Useful for testing the pipeline | |
| # between proper releases. | |
| # * push to main — regression gate: full matrix, build + validate + | |
| # packaged e2e, artifacts on the run only. Catches packaging bugs at | |
| # merge time instead of at tag time, and yields current-main packages | |
| # for every distro. Path-filtered so doc-only commits skip it (paths | |
| # are not evaluated for tag pushes, so releases are unaffected). | |
| on: | |
| push: | |
| branches: | |
| - main | |
| tags: | |
| - 'v*' | |
| paths: | |
| - 'src/**' | |
| - 'proto/**' | |
| - 'pkg/**' | |
| - 'tests/e2e/**' | |
| - 'CMakeLists.txt' | |
| - 'configure.ac' | |
| - 'Makefile.am' | |
| - 'config_m4/**' | |
| - 'autogen.sh' | |
| - 'VERSION' | |
| - 'grpc-collector.pc.in' | |
| - '.dockerignore' | |
| - '.github/workflows/release.yml' | |
| workflow_dispatch: | |
| # Superseded main-push runs are cancelled; tag runs never are. | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref_type != 'tag' }} | |
| permissions: | |
| contents: write # creates the GitHub Release on tag push. | |
| packages: write # for the optional ghcr.io image push. | |
| jobs: | |
| packages: | |
| name: ${{ matrix.distro.name }} | |
| runs-on: ubuntu-latest | |
| container: | |
| image: ${{ matrix.distro.image }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| distro: | |
| - { name: "debian-12 (bookworm)", image: "debian:12" } | |
| - { name: "debian-13 (trixie)", image: "debian:13" } | |
| - { name: "ubuntu-24.04 (noble)", image: "ubuntu:24.04" } | |
| - { name: "fedora", image: "fedora:latest" } | |
| steps: | |
| # actions/checkout needs `git` inside the container; install it | |
| # first on the rpm distros where the base image is minimal. | |
| - name: bootstrap git (rpm distros) | |
| if: contains(matrix.distro.image, 'rocky') || contains(matrix.distro.image, 'fedora') | |
| run: | | |
| # --allowerasing lets dnf swap curl-minimal for curl on rocky:9 | |
| dnf install -y -q --allowerasing git tar gzip curl >/dev/null | |
| - name: bootstrap git (deb distros) | |
| if: contains(matrix.distro.image, 'debian') || contains(matrix.distro.image, 'ubuntu') | |
| run: | | |
| export DEBIAN_FRONTEND=noninteractive | |
| apt-get update -qq | |
| apt-get install -y --no-install-recommends git ca-certificates curl tar >/dev/null | |
| - uses: actions/checkout@v5 | |
| - name: install build deps | |
| run: bash pkg/build/install_deps.sh | |
| - name: install nfpm | |
| run: | | |
| NFPM_VERSION=v2.41.3 | |
| NFPM_ARCH=x86_64 | |
| curl -fsSL "https://github.com/goreleaser/nfpm/releases/download/${NFPM_VERSION}/nfpm_${NFPM_VERSION#v}_Linux_${NFPM_ARCH}.tar.gz" \ | |
| | tar -xz -C /usr/local/bin nfpm | |
| nfpm --version | |
| - name: derive version | |
| id: version | |
| # Deb container images default this step to dash, which lacks | |
| # ${VAR::7} — same trap the validate steps already pin bash for. | |
| shell: bash | |
| run: | | |
| if [ "${GITHUB_REF_TYPE}" = "tag" ]; then | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| else | |
| # No '-' or '+': rpm forbids them in Version. Dots only. | |
| VERSION="$(sed 's/^v//' VERSION 2>/dev/null || echo 0.0.0).dev.${GITHUB_SHA::7}" | |
| fi | |
| echo "VERSION=${VERSION}" >> "${GITHUB_ENV}" | |
| echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" | |
| - name: build collector + library | |
| run: bash pkg/build/build_collector.sh | |
| - name: nfpm pack (standalone + library) | |
| run: bash pkg/build/make_packages.sh | |
| - name: smoke-test the package | |
| run: | | |
| # Install the freshly built standalone .deb/.rpm into the same | |
| # container; verify the binary loads and prints --help/-V. | |
| set -e | |
| if [ "${PKG_FORMAT}" = "deb" ]; then | |
| apt-get install -y -qq ./dist/mdt-dialout-collector_${VERSION}_${DISTRO_TAG}_amd64.deb | |
| else | |
| dnf install -y -q ./dist/mdt-dialout-collector_${VERSION}_${DISTRO_TAG}_amd64.rpm | |
| fi | |
| /opt/mdt-dialout-collector/bin/mdt_dialout_collector -V || true | |
| ls -l /lib/systemd/system/mdt-dialout-collector.service | |
| ls -l /etc/opt/mdt-dialout-collector/ | |
| - name: upload artifacts | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: packages-${{ env.DISTRO_TAG }} | |
| path: dist/* | |
| validate: | |
| # Install the freshly built packages in a CLEAN container of the | |
| # target distro (no build deps). Catches missing/wrong runtime | |
| # depends and unresolved transitive deps on system gRPC — neither | |
| # of which the build-container smoke step can see. | |
| name: validate ${{ matrix.distro.name }} | |
| needs: packages | |
| runs-on: ubuntu-latest | |
| container: | |
| image: ${{ matrix.distro.image }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| distro: | |
| - { name: "debian-12 (bookworm)", image: "debian:12", tag: "bookworm" } | |
| - { name: "debian-13 (trixie)", image: "debian:13", tag: "trixie" } | |
| - { name: "ubuntu-24.04 (noble)", image: "ubuntu:24.04", tag: "noble" } | |
| - { name: "fedora", image: "fedora:latest", tag: "fc" } | |
| steps: | |
| - name: bootstrap (rpm distros) | |
| if: contains(matrix.distro.image, 'rocky') || contains(matrix.distro.image, 'fedora') | |
| run: dnf install -y -q git tar gzip iproute >/dev/null | |
| - name: bootstrap (deb distros) | |
| if: contains(matrix.distro.image, 'debian') || contains(matrix.distro.image, 'ubuntu') | |
| run: | | |
| export DEBIAN_FRONTEND=noninteractive | |
| # Ubuntu's minimal image excludes man pages via dpkg-cfg path-exclude; | |
| # un-exclude them so the validate file-layout check can find them. | |
| rm -f /etc/dpkg/dpkg.cfg.d/excludes | |
| apt-get update -qq | |
| apt-get install -y --no-install-recommends \ | |
| git ca-certificates iproute2 >/dev/null | |
| - uses: actions/checkout@v5 | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| name: packages-${{ matrix.distro.tag }} | |
| path: dist | |
| - name: locate packages | |
| id: pkgs | |
| # Force bash explicitly: container default may be sh, which lacks shopt. | |
| shell: bash | |
| run: | | |
| # Diagnostic ALWAYS prints, even if locate later fails or skips. | |
| echo "::warning title=shell::$(readlink -f "$0" 2>/dev/null || echo unknown) BASH_VERSION=${BASH_VERSION:-NONE}" | |
| echo "::group::dist tree" | |
| ls -laR dist/ 2>&1 || echo "(dist/ missing)" | |
| echo "::endgroup::" | |
| # Surface the layout as an annotation so future debug rounds don't | |
| # require another commit cycle. | |
| tree_summary=$(ls -mR dist/ 2>&1 | tr '\n' '|') | |
| echo "::warning title=dist layout::${tree_summary}" | |
| # Recursive glob — handles both dist/foo.deb and dist/<name>/foo.deb. | |
| shopt -s nullglob globstar | |
| all=( dist/**/mdt-dialout-collector_*.deb dist/**/mdt-dialout-collector_*.rpm \ | |
| dist/**/mdt-dialout-collector-lib_*.deb dist/**/mdt-dialout-collector-lib_*.rpm ) | |
| shopt -u nullglob globstar | |
| STANDALONE= ; LIBRARY= | |
| for f in "${all[@]}"; do | |
| case "$(basename "$f")" in | |
| mdt-dialout-collector-lib_*) LIBRARY="$f" ;; | |
| mdt-dialout-collector_*) STANDALONE="$f" ;; | |
| esac | |
| done | |
| echo "STANDALONE=${STANDALONE}" | |
| echo "LIBRARY=${LIBRARY}" | |
| test -n "${STANDALONE}" || { echo "::error::standalone .deb/.rpm not found"; exit 1; } | |
| test -n "${LIBRARY}" || { echo "::error::library .deb/.rpm not found"; exit 1; } | |
| echo "standalone=${STANDALONE}" >> "${GITHUB_OUTPUT}" | |
| echo "library=${LIBRARY}" >> "${GITHUB_OUTPUT}" | |
| # Split the standalone validation into individually-named steps so | |
| # which one fails is visible from the workflow API/UI even without | |
| # access to the per-step log body. | |
| - name: validate — install standalone package | |
| run: | | |
| PKG="${{ steps.pkgs.outputs.standalone }}" | |
| case "${PKG}" in /*|./*) ;; *) PKG="./${PKG}" ;; esac | |
| . /etc/os-release | |
| case "${ID}" in | |
| debian|ubuntu) | |
| export DEBIAN_FRONTEND=noninteractive | |
| apt-get update -qq | |
| apt-get install -y -qq "${PKG}" | |
| ;; | |
| fedora|rocky|rhel|centos|almalinux) | |
| if [ "${ID}" != "fedora" ]; then | |
| dnf install -y -q epel-release >/dev/null | |
| fi | |
| dnf install -y -q "${PKG}" | |
| ;; | |
| esac | |
| - name: validate — file layout | |
| shell: bash | |
| run: | | |
| # Enumerate so the failure annotation names the missing file. | |
| ok=1 | |
| for path in \ | |
| /usr/share/man/man1/mdt_dialout_collector.1.gz \ | |
| /opt/mdt-dialout-collector/bin/mdt_dialout_collector \ | |
| /usr/bin/mdt_dialout_collector \ | |
| /lib/systemd/system/mdt-dialout-collector.service \ | |
| /etc/opt/mdt-dialout-collector/mdt_dialout_collector.conf | |
| do | |
| # -e follows symlinks; -L succeeds only on symlinks. | |
| if [ -e "${path}" ] || [ -L "${path}" ]; then | |
| echo " OK ${path}" | |
| else | |
| echo "::error title=missing file::${path}" | |
| ok=0 | |
| fi | |
| done | |
| [ "${ok}" = 1 ] || exit 1 | |
| - name: validate — launch daemon | |
| shell: bash | |
| run: | | |
| cp pkg/validate/minimal.conf /tmp/mdt-validate.conf | |
| # Plain `&` + disown — `$!` reliably captures the daemon PID. (setsid | |
| # forks-then-execs when the shell is a process-group leader, which | |
| # makes `$!` the dead parent's PID — see network-analytics/mdt-dialout-collector#issue.) | |
| /opt/mdt-dialout-collector/bin/mdt_dialout_collector \ | |
| -f /tmp/mdt-validate.conf > /tmp/mdt-validate.log 2>&1 & | |
| echo $! > /tmp/mdt-validate.pid | |
| disown | |
| sleep 3 | |
| PID="$(cat /tmp/mdt-validate.pid)" | |
| if ! kill -0 "${PID}" 2>/dev/null; then | |
| echo "::error::daemon exited prematurely (pid was ${PID})" | |
| cat /tmp/mdt-validate.log | |
| exit 1 | |
| fi | |
| echo "daemon running, pid=${PID}" | |
| - name: validate — vendor sockets listening | |
| shell: bash | |
| run: | | |
| set -e | |
| for port in 20007 20008 20009 20010; do | |
| if ! ss -lnt | grep -q ":${port}\b"; then | |
| echo "::error::port ${port} not listening" | |
| ss -lntp || true | |
| echo "--- daemon log ---" | |
| cat /tmp/mdt-validate.log || true | |
| exit 1 | |
| fi | |
| done | |
| - name: validate — graceful shutdown | |
| shell: bash | |
| run: | | |
| PID="$(cat /tmp/mdt-validate.pid)" | |
| echo "PID from file: '${PID}'" | |
| # Treat zombies as exited: the daemon's parent (the launching step's | |
| # bash) is gone, and the container's PID 1 doesn't always reap orphans | |
| # — so a cleanly-exited daemon may linger as <defunct>. kill -0 | |
| # succeeds on zombies, so we must inspect /proc/PID/status. | |
| is_alive() { | |
| local p="$1" | |
| kill -0 "${p}" 2>/dev/null || return 1 | |
| local st=$(awk '/^State:/{print $2}' /proc/${p}/status 2>/dev/null) | |
| [ "${st}" != "Z" ] | |
| } | |
| if is_alive "${PID}"; then | |
| kill -TERM "${PID}" 2>/dev/null || { | |
| echo "::error::kill -TERM failed for live pid ${PID}" | |
| exit 1 | |
| } | |
| # 4 vendor Srvs drain up to 5 s each — 30 s wait covers worst case. | |
| for i in $(seq 1 120); do | |
| is_alive "${PID}" || break | |
| sleep 0.25 | |
| done | |
| else | |
| echo "::warning::pid ${PID} already not-alive (state $(awk '/^State:/{print $2}' /proc/${PID}/status 2>/dev/null || echo missing))" | |
| fi | |
| if is_alive "${PID}"; then | |
| tail_log=$(tail -20 /tmp/mdt-validate.log 2>/dev/null | tr '\n' '|') | |
| echo "::error title=shutdown stuck::pid=${PID} state=$(grep ^State /proc/${PID}/status 2>/dev/null) | log tail: ${tail_log}" | |
| cat /tmp/mdt-validate.log | |
| kill -KILL "${PID}" 2>/dev/null || true | |
| exit 1 | |
| fi | |
| echo "--- daemon log ---" | |
| cat /tmp/mdt-validate.log | |
| tail_log=$(tail -10 /tmp/mdt-validate.log 2>/dev/null | tr '\n' '|') | |
| echo "::warning title=shutdown ok::log tail: ${tail_log}" | |
| - name: validate library package | |
| run: bash pkg/validate/library.sh "${{ steps.pkgs.outputs.library }}" | |
| e2e-package: | |
| # Real-traffic e2e against the freshly-built package: podman spins up | |
| # redpanda + per-vendor synthetic gRPC clients + a clean-distro container | |
| # that apt/dnf-installs the .deb/.rpm. Catches regressions the static | |
| # validate-package job can't see (postinst behavior + actual data path | |
| # under load). | |
| name: e2e ${{ matrix.distro.name }} | |
| needs: packages | |
| runs-on: ubuntu-latest # host runner: needs podman (pre-installed on ubuntu-latest) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| distro: | |
| - { name: "debian-12 (bookworm)", tag: "bookworm" } | |
| - { name: "debian-13 (trixie)", tag: "trixie" } | |
| - { name: "ubuntu-24.04 (noble)", tag: "noble" } | |
| - { name: "fedora", tag: "fc" } | |
| steps: | |
| - uses: actions/checkout@v5 | |
| # GitHub runners share NAT egress IPs whose anonymous Docker Hub pull | |
| # quota is chronically exhausted — every e2e image (debian bases + | |
| # redpanda) comes from docker.io, so pull authenticated when the | |
| # ci.yaml mirror credentials are present; degrade to anonymous if not. | |
| - name: podman login docker.io (avoid anonymous pull rate limit) | |
| env: | |
| DH_USER: ${{ vars.DOCKERHUB_USERNAME }} | |
| DH_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} | |
| run: | | |
| if [ -z "${DH_USER}" ] || [ -z "${DH_TOKEN}" ]; then | |
| echo "::warning::vars.DOCKERHUB_USERNAME or secrets.DOCKERHUB_TOKEN unset — anonymous pulls (rate-limit risk)" | |
| exit 0 | |
| fi | |
| echo "${DH_TOKEN}" | podman login docker.io -u "${DH_USER}" --password-stdin | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| name: packages-${{ matrix.distro.tag }} | |
| path: dist | |
| - name: locate standalone package | |
| id: pkg | |
| shell: bash | |
| run: | | |
| shopt -s nullglob globstar | |
| files=( dist/**/mdt-dialout-collector_*.deb dist/**/mdt-dialout-collector_*.rpm ) | |
| shopt -u nullglob globstar | |
| STANDALONE= | |
| for f in "${files[@]}"; do | |
| case "$(basename "$f")" in | |
| mdt-dialout-collector-lib_*) ;; | |
| mdt-dialout-collector_*) STANDALONE="$f" ;; | |
| esac | |
| done | |
| test -n "${STANDALONE}" \ | |
| || { echo "::error::no standalone .deb/.rpm in dist/"; ls -laR dist/; exit 1; } | |
| echo "path=${STANDALONE}" >> "${GITHUB_OUTPUT}" | |
| - name: run e2e against installed package | |
| run: bash tests/e2e/run.sh --package "${{ steps.pkg.outputs.path }}" | |
| release: | |
| name: GitHub Release | |
| needs: [packages, validate, e2e-package] | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') | |
| steps: | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| path: dist | |
| merge-multiple: true | |
| - name: list artifacts | |
| run: ls -lh dist/ | |
| - name: create release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| files: dist/* | |
| generate_release_notes: true | |
| fail_on_unmatched_files: true |