Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions .github/workflows/publish-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
name: Publish module image

# Publishes a minimal multi-arch (amd64 + arm64) OCI image containing only
# libvalkeyaudit.so to GHCR, for use as a Kubernetes ImageVolume.
#
# Strategy: build each arch NATIVELY (no QEMU) on its own runner, push each by
# digest, then merge into one manifest list. The per-arch builds set
# provenance/sbom = false so the published manifest list contains exactly two
# platform entries and NO `unknown/unknown` attestation manifest -- some
# kubelet/containerd ImageVolume paths choke on that. Supply-chain metadata is
# instead attached to the final index digest via GitHub attestations + cosign.

on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
valkey_version:
description: 'Valkey version whose valkeymodule.h to build against'
required: false
default: '9.1.0'

env:
REGISTRY: ghcr.io
# IMAGE_NAME is computed per-job (see "Normalize image name" steps): GHCR
# requires an all-lowercase repository path, but ${{ github.repository }} can
# contain uppercase owner/repo characters, which workflow-level `env:` cannot
# lowercase. The -module suffix keeps this separate from a future full-runtime image,
# i.e. the package is published as <owner>/valkey-audit-module.
VALKEY_VERSION: ${{ github.event.inputs.valkey_version || '9.1.0' }}

jobs:
build:
name: Build ${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
permissions:
contents: read
packages: write
steps:
- name: Normalize image name
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}-module" >> "$GITHUB_ENV"

- name: Checkout
uses: actions/checkout@v4

- name: Set up Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile.module
platforms: linux/${{ matrix.arch }}
build-args: |
VALKEY_VERSION=${{ env.VALKEY_VERSION }}
# Keep the manifest list clean for ImageVolume consumers.
provenance: false
sbom: false
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true

- name: Export digest
run: |
mkdir -p /tmp/digests
echo "${{ steps.build.outputs.digest }}" > "/tmp/digests/${{ matrix.arch }}"

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digest-${{ matrix.arch }}
path: /tmp/digests/*
retention-days: 1

merge:
name: Merge and publish manifest
runs-on: ubuntu-24.04
needs: [build]
permissions:
contents: read
packages: write
id-token: write # cosign keyless
attestations: write # GitHub build provenance
steps:
- name: Normalize image name
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}-module" >> "$GITHUB_ENV"

- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digest-*
merge-multiple: true

- name: Set up Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Docker metadata (tags + labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=valkey${{ env.VALKEY_VERSION }}

- name: Create and push manifest list
working-directory: /tmp/digests
run: |
set -eux
REFS=""
for f in *; do
REFS="$REFS ${REGISTRY}/${IMAGE_NAME}@$(cat "$f")"
done
TAGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
docker buildx imagetools create $TAGS $REFS

- name: Inspect published index
run: |
docker buildx imagetools inspect \
"${REGISTRY}/${IMAGE_NAME}:$(jq -r '.labels["org.opencontainers.image.version"]' <<< "$DOCKER_METADATA_OUTPUT_JSON")"

- name: Resolve index digest
id: digest
run: |
DIGEST=$(docker buildx imagetools inspect \
"${REGISTRY}/${IMAGE_NAME}:$(jq -r '.labels["org.opencontainers.image.version"]' <<< "$DOCKER_METADATA_OUTPUT_JSON")" \
--format '{{json .Manifest.Digest}}' | tr -d '"')
echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"

- name: Attest build provenance
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.digest.outputs.digest }}
push-to-registry: true

- name: Install cosign
uses: sigstore/cosign-installer@v3

- name: Sign image (keyless)
env:
DIGEST: ${{ steps.digest.outputs.digest }}
run: |
cosign sign --yes "${REGISTRY}/${IMAGE_NAME}@${DIGEST}"
20 changes: 17 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,17 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/valkey.conf ${CMAKE_CURRENT_BINA
# CHANGED: Conditional flags based on build type
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wunused -Wunused-parameter -pthread")

# Performance-focused flags for Release builds
set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG -march=native -mtune=native -flto")
# Performance-focused flags for Release builds.
# WARNING: -march=native tunes the binary to the BUILDING host's CPU and is NOT
# portable -- a binary built this way may SIGILL on older CPUs. Distributable
# builds (see docker/Dockerfile.module) MUST override the baseline, e.g.
# cmake -DAUDIT_C_FLAGS_RELEASE="-O2 -DNDEBUG -march=x86-64-v2 -flto"
# Indirection through a CACHE variable is deliberate: a plain
# set(CMAKE_C_FLAGS_RELEASE ...) would shadow and silently discard any
# -DCMAKE_C_FLAGS_RELEASE passed on the command line.
set(AUDIT_C_FLAGS_RELEASE "-O3 -DNDEBUG -march=native -mtune=native -flto"
CACHE STRING "C compiler flags for Release builds")
set(CMAKE_C_FLAGS_RELEASE "${AUDIT_C_FLAGS_RELEASE}")

# Alternative optimization levels for testing
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG")
Expand Down Expand Up @@ -84,5 +93,10 @@ install(TARGETS valkeyaudit
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

# Tests are built by default but can be skipped (e.g. distributable artifact
# builds) to avoid the configure-time googletest FetchContent download.
option(VALKEY_AUDIT_TESTS "Build the test suite" ON)
enable_testing()
add_subdirectory(test)
if(VALKEY_AUDIT_TESTS)
add_subdirectory(test)
endif()
83 changes: 83 additions & 0 deletions docker/Dockerfile.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# syntax=docker/dockerfile:1.7
#
# Builds a minimal OCI artifact image containing ONLY the compiled
# valkey-audit module (libvalkeyaudit.so). Intended to be mounted into a
# standard Valkey container via the Kubernetes ImageVolume feature, or copied
# out with an init container.
#
# The final image is FROM scratch: no shell, no runtime, just the .so + LICENSE.
#
# Build locally for one arch:
# docker build -f docker/Dockerfile.module -t valkey-audit:dev .
# Multi-arch is handled by the publish workflow using native runners.

# ---- Pinned inputs ---------------------------------------------------------
# Valkey version whose valkeymodule.h we compile against. The module links
# ONLY against libc/pthread (verified with ldd) and resolves Valkey symbols at
# load time, so this pins the module *API* we build to, not a runtime dep.
ARG VALKEY_VERSION=9.1.0

# glibc build base. bookworm matches the official `valkey/valkey:*-bookworm`
# (Debian) images. Use an older base (e.g. bullseye) if you need a lower glibc
# floor for forward-compat; do NOT expect a glibc build to load in an Alpine
# (musl) Valkey image — build a separate musl variant for that.
ARG BUILDER_BASE=debian:bookworm-slim

# ---- Build stage -----------------------------------------------------------
FROM ${BUILDER_BASE} AS build
ARG VALKEY_VERSION
# TARGETARCH is provided automatically by BuildKit (amd64 / arm64).
ARG TARGETARCH

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential cmake curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src
COPY . .

# Fetch the single, self-contained module header for the pinned Valkey version.
RUN mkdir -p /vkinc \
&& curl -fsSL -o /vkinc/valkeymodule.h \
"https://raw.githubusercontent.com/valkey-io/valkey/${VALKEY_VERSION}/src/valkeymodule.h"

# IMPORTANT: the repo's default Release flags use `-march=native -mtune=native`,
# which tunes the binary to the *builder's* CPU and will SIGILL on other hosts.
# For a distributable artifact we override the baseline per target arch via
# AUDIT_C_FLAGS_RELEASE. (CMakeLists.txt reads that CACHE variable into
# CMAKE_C_FLAGS_RELEASE; a -DCMAKE_C_FLAGS_RELEASE would be silently discarded
# by the in-file set().) VALKEY_AUDIT_TESTS=OFF skips the googletest download
# since we only build the module target here.
RUN set -eux; \
case "${TARGETARCH}" in \
amd64) MARCH="-march=x86-64-v2" ;; \
arm64) MARCH="-march=armv8-a" ;; \
*) MARCH="" ;; \
esac; \
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DVALKEY_INCLUDE_DIR=/vkinc \
-DVALKEY_AUDIT_TESTS=OFF \
-DAUDIT_C_FLAGS_RELEASE="-O2 -DNDEBUG ${MARCH} -flto"; \
cmake --build build --target valkeyaudit -j"$(nproc)"; \
# The target sets VERSION/SOVERSION, so the real file is libvalkeyaudit.so.X.Y.Z
# behind a symlink chain. Dereference into a stable, flat name and strip it.
mkdir -p /out; \
cp -L build/libvalkeyaudit.so /out/libvalkeyaudit.so; \
strip --strip-unneeded /out/libvalkeyaudit.so; \
cp LICENSE /out/LICENSE

# ---- Final artifact image --------------------------------------------------
FROM scratch
ARG VALKEY_VERSION
LABEL org.opencontainers.image.title="valkey-audit module" \
org.opencontainers.image.description="Compiled valkey-audit shared library (libvalkeyaudit.so) for use as a Kubernetes ImageVolume" \
org.opencontainers.image.licenses="BSD-3-Clause" \
org.opencontainers.image.source="https://github.com/martinrvisser/valkey-audit" \
io.valkey-audit.built-against-valkey="${VALKEY_VERSION}"

# Stable, documented path. With ImageVolume mounted at e.g. /modules, the module
# is then at /modules/libvalkeyaudit.so. Keep this path constant across versions.
COPY --from=build /out/libvalkeyaudit.so /libvalkeyaudit.so
COPY --from=build /out/LICENSE /LICENSE
Loading