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
7 changes: 7 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ This repository builds and publishes Docker images for Network Optix VMS product
- Base images (`nx-base`, `nx-base-lsio`) are built and pushed, then used as `FROM` images for derived product Dockerfiles.
- Derived images should track base image tag changes (for example, the Ubuntu distro tag) to keep builds consistent.

### CI Pipeline (GitHub Actions)

- Pull requests run unit tests and style checks, plus a fast smoke build (NxMeta and NxMeta-LSIO, amd64 only, no push) that runs only when image files change -- not the full matrix.
- Publishing is schedule/manual only via `publish-release.yml`, which builds the base images once and then publishes the full matrix for both the `main` and `develop` branches in a single run.
- Merges to `main`/`develop` do not publish; auto-merged Dependabot and codegen PRs are picked up by the next scheduled publish. Do not reintroduce push-triggered publishing or full-matrix PR builds.
- Structured files are linted in-editor via the workspace-recommended extensions in [.vscode/extensions.json](../.vscode/extensions.json) (C#, Markdown, YAML, Docker, GitHub Actions) rather than a CI lint job; lint changed files before pushing, and run `actionlint` for deeper workflow checks. See AGENTS.md.

## What to Keep in Sync

- Generated Dockerfiles and scripts must reflect CreateMatrix behavior.
Expand Down
33 changes: 27 additions & 6 deletions .github/workflows/build-base-images-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ on:
required: false
type: boolean
default: true
platforms:
required: false
type: string
default: linux/amd64,linux/arm64
# Branch to check out. The publisher passes main so the shared
# nx-base tag is always built from the release branch regardless of
# the dispatch ref. Empty falls back to the triggering ref.
ref:
required: false
type: string
default: ''

jobs:

Expand All @@ -32,18 +43,24 @@ jobs:

- name: Checkout step
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}

- name: Setup QEMU step
uses: docker/setup-qemu-action@v4
with:
platforms: linux/amd64,linux/arm64
platforms: ${{ inputs.platforms }}

- name: Setup Buildx step
uses: docker/setup-buildx-action@v4
with:
platforms: linux/amd64,linux/arm64
platforms: ${{ inputs.platforms }}

# Only needed when pushing; skipped for non-publishing builds (e.g.
# PR smoke) so they don't depend on Docker Hub secrets (unavailable
# on forked PRs). Public base images still pull anonymously.
- name: Login to Docker Hub step
if: ${{ inputs.push }}
uses: docker/login-action@v4
with:
registry: docker.io
Expand All @@ -53,17 +70,21 @@ jobs:
- name: Docker build and push base step
uses: docker/build-push-action@v7
with:
push: ${{ inputs.push && (github.ref_name == 'main' || github.ref_name == 'develop') }}
# Push is controlled by the caller, not the trigger ref: the
# publisher passes push: true with ref: main, so it must publish
# the shared base tag even when dispatched from another branch.
# Cache scopes key on the built ref, not github.ref_name.
push: ${{ inputs.push }}
context: Docker
file: ${{ matrix.base.dockerfile }}
platforms: linux/amd64,linux/arm64
platforms: ${{ inputs.platforms }}
tags: ${{ matrix.base.tags }}
cache-from: |
type=registry,ref=${{ matrix.base.cache_tag }}
type=gha,scope=develop-base-${{ matrix.base.name }}
type=gha,scope=main-base-${{ matrix.base.name }}
${{ github.event.pull_request && format('type=gha,scope=pr-base-{0}-{1}', github.event.pull_request.number, matrix.base.name) || '' }}
cache-to: |
${{ inputs.push && (github.ref_name == 'main' || github.ref_name == 'develop') && format('type=gha,mode=max,scope={0}-base-{1},ignore-error=true', github.ref_name, matrix.base.name) || '' }}
${{ inputs.push && (github.ref_name == 'main' || github.ref_name == 'develop') && 'type=inline' || '' }}
${{ inputs.push && format('type=gha,mode=max,scope={0}-base-{1},ignore-error=true', inputs.ref != '' && inputs.ref || github.ref_name, matrix.base.name) || '' }}
${{ inputs.push && 'type=inline' || '' }}
${{ github.event.pull_request && !github.event.pull_request.head.repo.fork && format('type=gha,mode=max,scope=pr-base-{0}-{1},ignore-error=true', github.event.pull_request.number, matrix.base.name) || '' }}
6 changes: 4 additions & 2 deletions .github/workflows/build-datebadge-task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ jobs:
- name: Get current date step
id: date
run: |
echo "date=$(date)" >> $GITHUB_OUTPUT
echo "date=$(date)" >> "$GITHUB_OUTPUT"

# No ref gate: the sole caller (publish-release) only runs this after
# a successful publish and can be dispatched from any ref, so the
# "Last Build" badge should update on every publish.
- name: Build BYOB date badge step
if: ${{ github.ref_name == 'main' }}
uses: RubbaBoy/BYOB@v1
with:
name: lastbuild
Expand Down
272 changes: 168 additions & 104 deletions .github/workflows/build-docker-task.yml
Original file line number Diff line number Diff line change
@@ -1,104 +1,168 @@
name: Build Docker image task


on:
workflow_call:
inputs:
push:
required: false
type: boolean
default: false

jobs:

get-matrix:
name: Get matrix job
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.getmatrix.outputs.matrix }}

steps:

- name: Checkout step
uses: actions/checkout@v6

- name: Load matrix.json step
id: getmatrix
run: |
echo "matrix=$(jq --compact-output '.' ./Make/Matrix.json)" >> $GITHUB_OUTPUT

get-version:
name: Get version information job
uses: ./.github/workflows/get-version-task.yml
secrets: inherit

build-base:
name: Build base image job
uses: ./.github/workflows/build-base-images-task.yml
with:
push: ${{ inputs.push }}
secrets: inherit

build-docker:
name: Build Docker image job
runs-on: ubuntu-latest
needs: [get-version, get-matrix, build-base]

strategy:
max-parallel: 4
matrix:
images: ${{ fromJson(needs.get-matrix.outputs.matrix).Images }}

steps:

- name: Checkout step
uses: actions/checkout@v6

- name: Setup QEMU step
uses: docker/setup-qemu-action@v4
with:
platforms: linux/amd64,linux/arm64

- name: Setup Buildx step
uses: docker/setup-buildx-action@v4
with:
platforms: linux/amd64,linux/arm64

- name: Login to Docker Hub step
uses: docker/login-action@v4
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

- name: Create tags and args step
id: tagsargs
run: |
TAGS=$(jq -r '.[]' <<< '${{ toJson(matrix.images.Tags) }}')
ARGS=$(jq -r '.[]' <<< '${{ toJson(matrix.images.Args) }}')
{
echo "tags<<EOF"
echo "$TAGS"
echo "EOF"
echo "args<<EOF"
echo "$ARGS"
echo "LABEL_VERSION=${{ needs.get-version.outputs.SemVer2 }}"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Docker build and push step
uses: docker/build-push-action@v7
with:
push: ${{ inputs.push && (github.ref_name == matrix.images.Branch) }}
context: Docker
file: Docker/${{ matrix.images.Name }}.Dockerfile
platforms: linux/amd64,linux/arm64
tags: ${{ steps.tagsargs.outputs.tags }}
build-args: ${{ steps.tagsargs.outputs.args }}
cache-from: |
type=gha,scope=develop-${{ matrix.images.Name }}
type=gha,scope=main-${{ matrix.images.Name }}
${{ github.event.pull_request && format('type=gha,scope=pr-{0}-{1}', github.event.pull_request.number, matrix.images.Name) || '' }}
cache-to: |
${{ (github.ref_name == 'main' || github.ref_name == 'develop') && format('type=gha,mode=min,scope={0}-{1},ignore-error=true', github.ref_name, matrix.images.Name) || '' }}
${{ github.event.pull_request && !github.event.pull_request.head.repo.fork && format('type=gha,mode=min,scope=pr-{0}-{1},ignore-error=true', github.event.pull_request.number, matrix.images.Name) || '' }}
name: Build Docker image task


on:
workflow_call:
inputs:
push:
required: false
type: boolean
default: false
# Smoke mode: build a single Ubuntu + single LSIO variant on amd64
# only, never push. Used for fast PR feedback in place of the full
# matrix. See test-pull-request.yml.
smoke:
required: false
type: boolean
default: false
# Branch to check out and whose images to build/push. Empty uses the
# triggering ref (PR context). The publisher passes main and develop
# so both branches' tags are produced from one scheduled run.
ref:
required: false
type: string
default: ''
# When false the caller is expected to have built the base images
# already (the publisher builds them once, see publish-release.yml),
# so the internal base build is skipped.
build_base:
required: false
type: boolean
default: true

jobs:

get-matrix:
name: Get matrix job
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.getmatrix.outputs.matrix }}

steps:

- name: Checkout step
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}

- name: Load matrix.json step
id: getmatrix
env:
SMOKE: ${{ inputs.smoke }}
REF: ${{ inputs.ref }}
run: |
# $ref below is a jq variable (passed via --arg), not a shell
# variable, so it must stay single-quoted / unexpanded.
# shellcheck disable=SC2016
if [[ "$SMOKE" == "true" ]]; then
# One Ubuntu + one LSIO variant per branch exercises the shared
# Dockerfile build logic and each branch's build args; tags are
# irrelevant since smoke never pushes. De-dup by name+branch so a
# PR targeting develop still validates the develop rows.
FILTER='.Images |= (map(select(.Name == "NxMeta" or .Name == "NxMeta-LSIO")) | unique_by([.Name, .Branch]))'
elif [[ -n "$REF" ]]; then
# Publish: build only the rows targeting the branch being built
# (avoids building the other branch's rows just to discard them).
# $ref is passed via --arg so the value is never interpolated
# into the jq program.
FILTER='.Images |= map(select(.Branch == $ref))'
else
FILTER='.'
fi
echo "matrix=$(jq --arg ref "$REF" --compact-output "$FILTER" ./Make/Matrix.json)" >> "$GITHUB_OUTPUT"

get-version:
name: Get version information job
uses: ./.github/workflows/get-version-task.yml
secrets: inherit
with:
ref: ${{ inputs.ref }}

build-base:
name: Build base image job
if: ${{ inputs.build_base }}
uses: ./.github/workflows/build-base-images-task.yml
with:
push: ${{ inputs.push }}
platforms: ${{ inputs.smoke && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
ref: ${{ inputs.ref }}
secrets: inherit

build-docker:
name: Build Docker image job
runs-on: ubuntu-latest
needs: [get-version, get-matrix, build-base]
# always() so the job still runs when build-base is intentionally
# skipped (build_base == false); fail only if a prerequisite failed.
if: >-
${{ always()
&& needs.get-version.result == 'success'
&& needs.get-matrix.result == 'success'
&& (needs.build-base.result == 'success' || needs.build-base.result == 'skipped') }}

strategy:
max-parallel: 4
matrix:
images: ${{ fromJson(needs.get-matrix.outputs.matrix).Images }}

steps:

- name: Checkout step
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.ref }}

- name: Setup QEMU step
uses: docker/setup-qemu-action@v4
with:
platforms: ${{ inputs.smoke && 'linux/amd64' || 'linux/amd64,linux/arm64' }}

- name: Setup Buildx step
uses: docker/setup-buildx-action@v4
with:
platforms: ${{ inputs.smoke && 'linux/amd64' || 'linux/amd64,linux/arm64' }}

# Only needed when pushing; skipped for non-publishing builds (e.g.
# PR smoke) so they don't depend on Docker Hub secrets (unavailable
# on forked PRs). The public base image still pulls anonymously.
- name: Login to Docker Hub step
if: ${{ inputs.push }}
uses: docker/login-action@v4
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

Comment thread
ptr727 marked this conversation as resolved.
- name: Create tags and args step
id: tagsargs
run: |
TAGS=$(jq -r '.[]' <<< '${{ toJson(matrix.images.Tags) }}')
ARGS=$(jq -r '.[]' <<< '${{ toJson(matrix.images.Args) }}')
{
echo "tags<<EOF"
echo "$TAGS"
echo "EOF"
echo "args<<EOF"
echo "$ARGS"
echo "LABEL_VERSION=${{ needs.get-version.outputs.SemVer2 }}"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Docker build and push step
uses: docker/build-push-action@v7
with:
# Matrix is already filtered to the target branch, so push when asked.
# Smoke callers pass push:false.
push: ${{ inputs.push }}
context: Docker
file: Docker/${{ matrix.images.Name }}.Dockerfile
platforms: ${{ inputs.smoke && 'linux/amd64' || 'linux/amd64,linux/arm64' }}
tags: ${{ steps.tagsargs.outputs.tags }}
build-args: ${{ steps.tagsargs.outputs.args }}
cache-from: |
type=gha,scope=develop-${{ matrix.images.Name }}
type=gha,scope=main-${{ matrix.images.Name }}
${{ github.event.pull_request && format('type=gha,scope=pr-{0}-{1}', github.event.pull_request.number, matrix.images.Name) || '' }}
cache-to: |
${{ inputs.push && format('type=gha,mode=min,scope={0}-{1},ignore-error=true', inputs.ref != '' && inputs.ref || github.ref_name, matrix.images.Name) || '' }}
${{ github.event.pull_request && !github.event.pull_request.head.repo.fork && format('type=gha,mode=min,scope=pr-{0}-{1},ignore-error=true', github.event.pull_request.number, matrix.images.Name) || '' }}
Loading
Loading