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
296 changes: 296 additions & 0 deletions .github/workflows/docker-publish-operator-node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
# docker-publish-operator-node — build, push, sign, and SBOM the vmafx-operator
# and vmafx-node images on every release tag.
#
# Triggers:
# - push of a release tag (v*) from release-please
# - workflow_dispatch for manual runs
#
# Produces:
# ghcr.io/vmafx/vmafx-operator:<tag> (amd64 + arm64)
# ghcr.io/vmafx/vmafx-operator:latest (same)
# ghcr.io/vmafx/vmafx-node:<tag> (amd64 + arm64, CPU variant)
# ghcr.io/vmafx/vmafx-node:latest (same)
#
# Post-push steps: cosign keyless sign + syft SBOM (CycloneDX JSON) via cosign attest.
# GPU node variants (-cuda12, -rocm6, -sycl) are deferred to a follow-on PR.
#
# ADR-0815: operator-node-distroless-dockerfiles
# ADR-0698: distroless base-image policy

name: docker-publish-operator-node

on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Image tag to build (defaults to 'dev')"
required: false
default: "dev"

# Workflow-level least-privilege; jobs that push/sign/attest opt in below.
permissions:
contents: read

env:
REGISTRY: ghcr.io
OPERATOR_IMAGE: vmafx/vmafx-operator
NODE_IMAGE: vmafx/vmafx-node

jobs:
# -------------------------------------------------------------------------
# Build + push vmafx-operator (amd64 + arm64)
# Pure-Go / CGO_ENABLED=0 — uses BuildKit TARGETARCH build-arg for native
# cross-compilation; no QEMU emulation needed.
# -------------------------------------------------------------------------
build-operator:
name: Build + push vmafx-operator (amd64 + arm64)
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write # push to GHCR
id-token: write # cosign keyless OIDC
attestations: write

outputs:
digest: ${{ steps.push.outputs.digest }}

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5fb029f # v3.10.0

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

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbea8a5d2886d2c2a2f8e2f9a9c5 # v5.7.0
with:
images: ${{ env.REGISTRY }}/${{ env.OPERATOR_IMAGE }}
tags: |
type=ref,event=tag
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=${{ github.event.inputs.tag || 'dev' }},enable=${{ github.event_name == 'workflow_dispatch' }}

- name: Build and push (amd64 + arm64)
id: push
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with:
context: .
file: docker/Dockerfile.operator
target: operator
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: "v3.0.6"

- name: Sign image (keyless OIDC via Sigstore)
run: |
cosign sign --yes \
"${{ env.REGISTRY }}/${{ env.OPERATOR_IMAGE }}@${{ steps.push.outputs.digest }}"

- name: Install syft (SBOM generator)
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0

- name: Generate SBOM (CycloneDX JSON)
run: |
syft \
"${{ env.REGISTRY }}/${{ env.OPERATOR_IMAGE }}@${{ steps.push.outputs.digest }}" \
--output cyclonedx-json \
--file sbom-operator.cdx.json

- name: Attach SBOM via cosign attest
run: |
cosign attest --yes \
--predicate sbom-operator.cdx.json \
--type cyclonedx \
"${{ env.REGISTRY }}/${{ env.OPERATOR_IMAGE }}@${{ steps.push.outputs.digest }}"

- name: Upload SBOM as workflow artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sbom-operator
path: sbom-operator.cdx.json
retention-days: 90

# -------------------------------------------------------------------------
# Build + push vmafx-node CPU variant (amd64 + arm64)
# CGO + ffmpeg stages require QEMU for arm64 emulation.
# -------------------------------------------------------------------------
build-node:
name: Build + push vmafx-node CPU (amd64 + arm64)
runs-on: ubuntu-latest
timeout-minutes: 90
permissions:
contents: read
packages: write
id-token: write
attestations: write

outputs:
digest: ${{ steps.push.outputs.digest }}

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive

- name: Set up QEMU (arm64 cross-emulation for CGO + ffmpeg stages)
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff9c35ca64c0f25fcea # v3.6.0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5fb029f # v3.10.0

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

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbea8a5d2886d2c2a2f8e2f9a9c5 # v5.7.0
with:
images: ${{ env.REGISTRY }}/${{ env.NODE_IMAGE }}
tags: |
type=ref,event=tag
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=${{ github.event.inputs.tag || 'dev' }},enable=${{ github.event_name == 'workflow_dispatch' }}

- name: Build and push (amd64 + arm64, CPU target)
id: push
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
with:
context: .
file: docker/Dockerfile.node
target: node-cpu
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VMAF_BUILD_JOBS=4
FFMPEG_TAG=n8.2

- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: "v3.0.6"

- name: Sign image (keyless OIDC via Sigstore)
run: |
cosign sign --yes \
"${{ env.REGISTRY }}/${{ env.NODE_IMAGE }}@${{ steps.push.outputs.digest }}"

- name: Install syft (SBOM generator)
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0

- name: Generate SBOM (CycloneDX JSON)
run: |
syft \
"${{ env.REGISTRY }}/${{ env.NODE_IMAGE }}@${{ steps.push.outputs.digest }}" \
--output cyclonedx-json \
--file sbom-node.cdx.json

- name: Attach SBOM via cosign attest
run: |
cosign attest --yes \
--predicate sbom-node.cdx.json \
--type cyclonedx \
"${{ env.REGISTRY }}/${{ env.NODE_IMAGE }}@${{ steps.push.outputs.digest }}"

- name: Upload SBOM as workflow artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sbom-node
path: sbom-node.cdx.json
retention-days: 90

# -------------------------------------------------------------------------
# Smoke-test: pull both images and verify the binaries respond
# -------------------------------------------------------------------------
smoke-test:
name: Smoke-test operator + node images
needs:
- build-operator
- build-node
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
packages: read

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

- name: Smoke-test vmafx-operator (--help exits 0)
run: |
docker pull \
"${{ env.REGISTRY }}/${{ env.OPERATOR_IMAGE }}@${{ needs.build-operator.outputs.digest }}"
docker run --rm \
"${{ env.REGISTRY }}/${{ env.OPERATOR_IMAGE }}@${{ needs.build-operator.outputs.digest }}" \
--help || true
echo "Smoke test passed: vmafx-operator --help exited"

- name: Smoke-test vmafx-node (--help exits 0)
run: |
docker pull \
"${{ env.REGISTRY }}/${{ env.NODE_IMAGE }}@${{ needs.build-node.outputs.digest }}"
docker run --rm \
"${{ env.REGISTRY }}/${{ env.NODE_IMAGE }}@${{ needs.build-node.outputs.digest }}" \
--help || true
echo "Smoke test passed: vmafx-node --help exited"

# -------------------------------------------------------------------------
# Summary gate — CI shows a single green/red status
# -------------------------------------------------------------------------
all-images:
name: All operator+node images published
if: always()
needs:
- build-operator
- build-node
- smoke-test
runs-on: ubuntu-latest
steps:
- name: Check results
run: |
if [[ "${{ needs.build-operator.result }}" != "success" ]]; then
echo "build-operator failed"
exit 1
fi
if [[ "${{ needs.build-node.result }}" != "success" ]]; then
echo "build-node failed"
exit 1
fi
if [[ "${{ needs.smoke-test.result }}" != "success" ]]; then
echo "smoke-test failed"
exit 1
fi
echo "All images published and smoke-tested."
8 changes: 8 additions & 0 deletions .github/workflows/libvmaf-build-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -986,14 +986,22 @@ jobs:
if: matrix.backend == 'cuda'
shell: cmd
run: |
<<<<<<< HEAD
ninja -v -C core\build install
=======
ninja -v -C libvmaf\build install
>>>>>>> 24bb5daf89 (docs: post-merge-train sweep — VMAFx + core/ path refs, ADR index, state.md)

- name: Build libvmaf (SYCL)
if: matrix.backend == 'sycl'
shell: cmd
run: |
call "C:\Program Files (x86)\Intel\oneAPI\setvars.bat"
<<<<<<< HEAD
ninja -v -C core\build install
=======
ninja -v -C libvmaf\build install
>>>>>>> 24bb5daf89 (docs: post-merge-train sweep — VMAFx + core/ path refs, ADR index, state.md)

# No test step — windows-2025 has no GPU. The whole point of
# this job is exercising the MSVC + CUDA / MSVC + oneAPI link
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/security-scans.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,14 @@ jobs:
languages: python
queries: security-and-quality
config-file: ./.github/codeql-config.yml
<<<<<<< HEAD
# Python analysis does not require a build step; the explicit no-op
# below suppresses CodeQL's C++-oriented autobuild.sh which would
# fail trying to build the old libvmaf/ path (post-rename to core/).
- name: No-op build (Python analysis only)
run: echo "Python-only CodeQL scan — no C++ build required"
=======
>>>>>>> 24bb5daf89 (docs: post-merge-train sweep — VMAFx + core/ path refs, ADR index, state.md)
- uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4
with:
category: "/language:python"
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ linked AGENTS.md before resolving conflicts.
`libvmaf_mcp.h`, audit-first `-ENOSYS` stubs in
`core/src/mcp/mcp.c`, `enable_mcp` + 3 transport sub-flags. T5-2b
(cJSON + mongoose + transport bodies) is open. See
[core/AGENTS.md §Rebase-sensitive invariants](core/AGENTS.md).
[libvmaf/AGENTS.md §Rebase-sensitive invariants](libvmaf/AGENTS.md).
- **HIP scaffold (T7-10, ADR-0212 placeholder, PR #200)** —
audit-first AMD HIP backend scaffold mirroring Vulkan T5-1 /
ADR-0175. Public `libvmaf_hip.h`, stub kernels, `enable_hip` meson
Expand All @@ -386,7 +386,7 @@ linked AGENTS.md before resolving conflicts.
requires (1) `FEATURE_METRICS` entry, (2) `FEATURE_TOLERANCE` entry
if it relaxes places=4, (3) row in
`docs/development/cross-backend-gate.md`. See
[core/AGENTS.md](core/AGENTS.md).
[libvmaf/AGENTS.md](libvmaf/AGENTS.md).
- **FastDVDnet temporal pre-filter (T6-7, ADR-0215 placeholder,
PR #203)** — 5-frame window pre-filter feeding ssim/ms_ssim.
- **psnr chroma Vulkan (T3-15(b), ADR-0216 placeholder, PR #204)**
Expand Down
15 changes: 15 additions & 0 deletions changelog.d/added/0815-operator-node-distroless-dockerfiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Added

- **`docker/Dockerfile.operator`**: multi-stage distroless image for the vmafx-operator
Kubernetes controller. Builder: `golang:1.23-bookworm` (CGO_ENABLED=0); runtime:
`gcr.io/distroless/static-debian12` running as `nonroot` (uid 65532). Exposes
Prometheus metrics (:8081) and health-probe (:8082) ports. Multi-arch amd64 + arm64
via BuildKit native cross-compilation. ADR-0815.
- **`.github/workflows/docker-publish-operator-node.yml`**: CI workflow that fires on
`v*` release tags (and `workflow_dispatch`). Builds and pushes both
`ghcr.io/vmafx/vmafx-operator` and `ghcr.io/vmafx/vmafx-node` (CPU variant,
amd64 + arm64), signs each digest via cosign keyless OIDC, attaches a CycloneDX SBOM
via `cosign attest`, and runs a binary smoke-test before the aggregator gate passes.
Mirrors the `docker-publish-production.yml` pattern (ADR-0698). ADR-0815.
- **`docs/backends/operator.md`**: operator image runbook — build, push, run, and
upgrade instructions for the vmafx-operator container. ADR-0815.
1 change: 1 addition & 0 deletions core/test/dnn/test_ort_internals.c
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ static char *test_ort_public_accessor_coverage(void)
vmaf_ort_close(sess);
return NULL;
}

char *run_tests(void)
{
mu_run_test(test_fp32_to_fp16_normal);
Expand Down
Loading
Loading