Skip to content

Build and test FreeIPA containers #1213

Build and test FreeIPA containers

Build and test FreeIPA containers #1213

Workflow file for this run

name: Build and test FreeIPA containers
run-name: Build and test FreeIPA containers${{ github.event_name == 'workflow_dispatch' && format(' ({0})', inputs.os) || ''}}
on:
push:
pull_request:
workflow_dispatch:
inputs:
os:
description: OS / Dockerfile
type: choice
options:
- all
- fedora-rawhide
- fedora-42
- fedora-41
- almalinux-10
- almalinux-9
- almalinux-8
- rocky-9
- rocky-8
- centos-10-stream
- centos-9-stream
schedule:
- cron: '15 4 * * 1,3,5'
jobs:
init:
name: Process workflow inputs
runs-on: ubuntu-latest
timeout-minutes: 1
outputs:
os: ${{ steps.generate-os-list-dispatch-one.outputs.os }}${{ steps.generate-os-list-all.outputs.os }}
steps:
- id: generate-os-list-dispatch-one
if: github.event_name == 'workflow_dispatch' && inputs.os != 'all'
run: echo 'os=["${{ inputs.os }}"]' | tee $GITHUB_OUTPUT
- uses: actions/checkout@v4
if: github.event_name != 'workflow_dispatch' || inputs.os == 'all'
- id: generate-os-list-all
if: github.event_name != 'workflow_dispatch' || inputs.os == 'all'
run: |
yq '.on.workflow_dispatch.inputs.os.options | map(select(. != "all"))' .github/workflows/build-test.yaml -o json \
| jq -c . | sed 's/^/os=/' | tee $GITHUB_OUTPUT
build:
name: Build image
runs-on: ubuntu-24.04${{ (matrix.arch == 'arm64' && '-arm') || (matrix.arch == 'x86_64' && '') }}
needs: [ init ]
if: needs.init.outputs.os != '[]'
strategy:
fail-fast: false
matrix:
os: ${{ fromJSON(needs.init.outputs.os ) }}
arch: [ x86_64, arm64 ]
docker: [ docker ]
exclude:
- os: fedora-41
arch: arm64
- os: centos-10-stream
arch: x86_64
- os: centos-9-stream
arch: arm64
- os: almalinux-8
arch: arm64
- os: rocky-8
arch: arm64
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Separate git work tree with just the files needed for build
run: git worktree add --no-checkout ../minimize-for-build
- name: Populate with the Dockerfile
run: cd ../minimize-for-build && git checkout HEAD Dockerfile.${{ matrix.os }}
- name: Populate with files referenced from the Dockerfile
run: cd ../minimize-for-build && awk '/^(ADD|COPY)/ { for (i = 2; i < NF; i++) print $i }' Dockerfile.${{ matrix.os }} | while read f ; do git checkout HEAD $f ; done
- name: Ensure docker images sees the named parent image
run: awk '$1 == "FROM" { print $2 ; exit }' ../minimize-for-build/Dockerfile.${{ matrix.os }} | xargs ${{ matrix.docker }} pull
- name: Build image
run: ${{ matrix.docker }} build -t localhost/freeipa-server:${{ matrix.os }} -f Dockerfile.${{ matrix.os }} ../minimize-for-build
- name: Label the built image
run: docker="${{ matrix.docker }}" ./ci/label-image.sh Dockerfile.${{ matrix.os }} localhost/freeipa-server:${{ matrix.os }} $( cd ../minimize-for-build && git write-tree ) "${{ github.server_url }}/${{ github.repository }}" "actions/runs/${{ github.run_id }}"
- name: File issue if building image failed
if: ${{ failure() && github.event_name == 'schedule' }}
run: |
curl -s '${{ github.api_url }}/repos/${{ github.repository }}/issues?labels=image-build-fail' | jq -r '.[0].state' | grep open \
|| curl -s -X POST \
--url ${{ github.api_url }}/repos/${{ github.repository }}/issues \
-H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
-H 'Accept: application/vnd.github.v3+json' \
-d '{
"title": "Image build for ${{ matrix.os }} failed on '$( date -I )'",
"body": "This issue was automatically created by GitHub Action\n\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}.\n",
"labels": ["image-build-fail" ]
}'
- name: Create directory for artifacts
run: mkdir freeipa-server-${{ matrix.os }}-${{matrix.arch}}
- name: Save image
run: ${{ matrix.docker }} save localhost/freeipa-server:${{ matrix.os }} | gzip > freeipa-server-${{ matrix.os }}-${{matrix.arch}}/freeipa-server-${{ matrix.os }}-${{matrix.arch}}.tar.gz
- uses: actions/upload-artifact@v4
with:
name: freeipa-server-${{ matrix.os }}-${{matrix.arch}}
path: freeipa-server-${{ matrix.os }}-${{matrix.arch}}
retention-days: 1
- run: |
mkdir -p build-status/${{ matrix.os }}-${{ matrix.arch }}
( echo "- os: ${{ matrix.os }}" ; echo " arch: ${{ matrix.arch }}" ) > build-status/${{ matrix.os }}-${{ matrix.arch }}/build-dist
shell: bash
- name: Check resulting labels
run: |
skopeo inspect docker-archive:freeipa-server-${{ matrix.os }}-${{ matrix.arch }}/freeipa-server-${{ matrix.os }}-${{ matrix.arch }}.tar.gz | jq '.Labels'
CHECK_REPO=quay.io/freeipa/freeipa-server
diff -u <( skopeo inspect docker://$CHECK_REPO:${{ matrix.os }} | jq '.Labels' ) <( skopeo inspect docker-archive:freeipa-server-${{ matrix.os }}-${{ matrix.arch }}/freeipa-server-${{ matrix.os }}-${{ matrix.arch }}.tar.gz | jq '.Labels' ) || true
if cmp \
<( skopeo inspect docker://$CHECK_REPO:${{ matrix.os }} \
| jq -r '.Labels."org.opencontainers.image.base.digest", .Labels."org.opencontainers.image.version"' ) \
<( skopeo inspect docker-archive:freeipa-server-${{ matrix.os }}-${{ matrix.arch }}/freeipa-server-${{ matrix.os }}-${{ matrix.arch }}.tar.gz \
| jq -r '.Labels."org.opencontainers.image.base.digest", .Labels."org.opencontainers.image.version"' ) ; then
echo Newly built freeipa-server:${{ matrix.os }} is the same as image at $CHECK_REPO.
else
cp build-status/${{ matrix.os }}-${{ matrix.arch }}/build-dist build-status/${{ matrix.os }}-${{ matrix.arch }}/fresh-image
fi
shell: bash
- name: Check for nopush
run: |
if yq '.push.exclude[]' .github/build-test-params.yaml -o json \
| jq -c --arg os "${{ matrix.os }}" --arg arch "${{ matrix.arch }}" '. as $input | ( $ARGS.named | { os, arch } | with_entries(select(.value != null))) | select(contains($input))' \
| grep . ; then
rm -f build-status/${{ matrix.os }}-${{ matrix.arch }}/fresh-image
fi
shell: bash
- uses: actions/upload-artifact@v4
with:
name: build-status-${{ matrix.os }}-${{ matrix.arch }}
path: build-status
retention-days: 1
test-plan:
name: FreeIPA test plan
runs-on: ubuntu-latest
needs: [ build ]
timeout-minutes: 1
outputs:
matrix-run: ${{ steps.produce-matrix.outputs.matrix-run }}
matrix-test-upgrade: ${{ steps.produce-matrix.outputs.matrix-test-upgrade }}
matrix-k8s: ${{ steps.produce-matrix.outputs.matrix-k8s }}
matrix-push: ${{ steps.produce-matrix.outputs.matrix-push }}
matrix-push-data: ${{ steps.produce-matrix.outputs.matrix-push-data }}
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: build-status-*
path: build-status
merge-multiple: true
- name: Amend the .github/build-test-params.yaml with information about built images
run: |
(
echo 'build-dist:'
yq '.jobs.build.strategy.matrix.arch as $arch | .on.workflow_dispatch.inputs.os.options | .[] + "-" + $arch[]' .github/workflows/build-test.yaml \
| while read i ; do
if [ -f build-status/$i/build-dist ] ; then cat build-status/$i/build-dist ; fi
done
echo 'fresh-dist:'
cat build-status/*/fresh-image || :
cat .github/build-test-params.yaml
) | yq . - -o json | tee .github/build-test-data.json
shell: bash
- id: produce-matrix
run: |
jq -f ci/generate-test-matrix.jq -c --arg job run .github/build-test-data.json \
| ( echo -n 'matrix-run=' >> $GITHUB_OUTPUT ; tee -a $GITHUB_OUTPUT )
jq -f ci/generate-test-matrix.jq -c --arg job test-upgrade .github/build-test-data.json \
| ( echo -n 'matrix-test-upgrade=' >> $GITHUB_OUTPUT ; tee -a $GITHUB_OUTPUT )
jq -f ci/generate-test-matrix.jq -c --arg job k8s .github/build-test-data.json \
| ( echo -n 'matrix-k8s=' >> $GITHUB_OUTPUT ; tee -a $GITHUB_OUTPUT )
jq -f ci/generate-test-matrix.jq -c --arg job push .github/build-test-data.json \
| ( echo -n 'matrix-push=' >> $GITHUB_OUTPUT ; tee -a $GITHUB_OUTPUT )
jq -f ci/generate-test-matrix.jq -c --arg job push-data .github/build-test-data.json \
| ( echo -n 'matrix-push-data=' >> $GITHUB_OUTPUT ; tee -a $GITHUB_OUTPUT )
shell: bash
- run: |
echo '${{ steps.produce-matrix.outputs.matrix-run }}' \
| jq -rf ci/test-matrix-to-html.jq --arg job run --argjson build-dist "$( jq -c '.["build-dist"]' .github/build-test-data.json )" >> $GITHUB_STEP_SUMMARY
shell: bash
- run: |
echo '${{ steps.produce-matrix.outputs.matrix-test-upgrade }}' \
| jq -rf ci/test-matrix-to-html.jq --arg job test-upgrade --argjson build-dist "$( jq -c '.["build-dist"]' .github/build-test-data.json )" >> $GITHUB_STEP_SUMMARY
shell: bash
- run: |
echo '${{ steps.produce-matrix.outputs.matrix-k8s }}' \
| jq -rf ci/test-matrix-to-html.jq --arg job k8s --argjson build-dist "$( jq -c '.["build-dist"]' .github/build-test-data.json )" >> $GITHUB_STEP_SUMMARY
shell: bash
- run: jq -n -rf ci/test-matrix-to-html.jq --arg job legend >> $GITHUB_STEP_SUMMARY
shell: bash
master-and-replica:
name: Run
runs-on: ${{ matrix.runs-on }}${{ (matrix.arch == 'arm64' && '-arm') || (matrix.arch == 'x86_64' && '') }}
needs: [ build, test-plan ]
if: needs.test-plan.outputs.matrix-run != '[]'
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.test-plan.outputs.matrix-run) }}
timeout-minutes: 30
env:
runtime: ${{ matrix.runtime == 'docker rootless' && 'docker' || matrix.runtime }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/docker-cgroups-ubuntu-22
if: matrix.runtime == 'docker'
- run: sudo systemctl disable --now docker.service docker.socket
if: matrix.runtime == 'docker rootless'
- run: |
cat <<EOT | sudo tee "/etc/apparmor.d/home.runner.bin.rootlesskit"
# ref: https://ubuntu.com/blog/ubuntu-23-10-restricted-unprivileged-user-namespaces
abi <abi/4.0>,
include <tunables/global>
/home/runner/bin/rootlesskit flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/home.runner.bin.rootlesskit>
}
EOT
sudo systemctl restart apparmor.service
if: startsWith(matrix.runs-on, 'ubuntu-24.04') && matrix.runtime == 'docker rootless'
- run: curl -fsSL https://get.docker.com/rootless | FORCE_ROOTLESS_INSTALL=1 sh
if: matrix.runtime == 'docker rootless'
- name: Install podman 4.*
uses: ./.github/actions/install-podman-4
if: matrix.runs-on == 'ubuntu-22.04' && (matrix.runtime == 'podman' || matrix.runtime == 'sudo podman')
- uses: actions/download-artifact@v4
with:
name: freeipa-server-${{ matrix.os }}-${{matrix.arch}}
- name: Load image
run: gunzip < freeipa-server-${{ matrix.os }}-${{matrix.arch}}.tar.gz | $runtime load
- run: $runtime volume create ${{ matrix.volume }}
if: matrix.volume == 'freeipa-data'
- name: Run master and replica
run: docker="$runtime" readonly=${{ matrix.readonly }} ca=${{ matrix.ca }} VOLUME=${{ matrix.volume }} seccomp=${{ matrix.seccomp }} replica=${{ matrix.replica }} tests/run-master-and-replica.sh localhost/freeipa-server:${{ matrix.os }}
- name: Show package difference
if: failure()
run: diff -U 0 <( $runtime run --rm --entrypoint rpm quay.io/freeipa/freeipa-server:${{ matrix.os }} -qa | sort ) <( $runtime run --rm --entrypoint rpm localhost/freeipa-server:${{ matrix.os }} -qa | sort ) || true
- name: Run partial tests
if: failure()
run: docker="$runtime" tests/run-partial-tests.sh Dockerfile.${{ matrix.os }}
test-upgrade:
name: Upgrade
runs-on: ${{ matrix.runs-on }}${{ (matrix.arch == 'arm64' && '-arm') || (matrix.arch == 'x86_64' && '') }}
needs: [ build, test-plan ]
if: needs.test-plan.outputs.matrix-test-upgrade != '[]'
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.test-plan.outputs.matrix-test-upgrade) }}
timeout-minutes: 20
env:
runtime: ${{ matrix.runtime == 'docker rootless' && 'docker' || matrix.runtime }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/docker-cgroups-ubuntu-22
if: matrix.runtime == 'docker'
- name: Install podman 4.*
uses: ./.github/actions/install-podman-4
if: matrix.runs-on == 'ubuntu-22.04' && (matrix.runtime == 'podman' || matrix.runtime == 'sudo podman')
- uses: actions/download-artifact@v4
with:
name: freeipa-server-${{ matrix.os }}-${{matrix.arch}}
- name: Load image
run: gunzip < freeipa-server-${{ matrix.os }}-${{matrix.arch}}.tar.gz | $runtime load
- name: Populate volume with data using image driver
run: |
$runtime pull quay.io/freeipa/freeipa-server:data-${{ matrix.data-from }} \
&& $runtime volume create --driver image --opt image=quay.io/freeipa/freeipa-server:data-${{ matrix.data-from }} ${{ matrix.volume }}
if: matrix.volume == 'volume-image'
- name: Populate volume directory with data
run: |
mkdir -p /tmp/freeipa-data/image \
&& skopeo copy docker://quay.io/freeipa/freeipa-server:data-${{ matrix.data-from }} dir:/tmp/freeipa-data/image \
&& jq -r '.layers[].digest | sub("sha256:"; "")' /tmp/freeipa-data/image/manifest.json \
| while read f ; do $runtime run --rm -v /tmp/freeipa-data:/data:z --workdir /data docker.io/library/busybox tar xf image/$f ; done \
&& rm -rf /tmp/freeipa-data/image
if: matrix.volume == '' || matrix.volume == 'volume'
- name: Populate volume with data
run: |
$runtime volume create ${{ matrix.volume }} \
&& $runtime run --rm -v /tmp/freeipa-data:/data-in:z -v ${{ matrix.volume }}:/data:z --workdir /data docker.io/library/busybox cp -a /data-in/. .
if: matrix.volume == 'volume'
- name: Run master and replica
run: docker="$runtime" VOLUME=${{ matrix.volume != '' && matrix.volume || '/tmp/freeipa-data' }} tests/run-master-and-replica.sh localhost/freeipa-server:${{ matrix.os }}
test-k8s:
name: Run in K8s
runs-on: ${{ matrix.runs-on }}${{ (matrix.arch == 'arm64' && '-arm') || (matrix.arch == 'x86_64' && '') }}
needs: [ build, test-plan ]
if: needs.test-plan.outputs.matrix-k8s != '[]'
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.test-plan.outputs.matrix-k8s ) }}
timeout-minutes: 30
env:
CRIO_VERSION: v1.32
steps:
- uses: actions/checkout@v4
- run: test -f /sys/fs/cgroup/cgroup.controllers
- uses: ./.github/actions/install-crio
if: matrix.runtime == 'cri-o'
- uses: ./.github/actions/docker-cgroups-ubuntu-22
if: matrix.runtime == 'docker'
- run: sudo systemctl stop docker && sudo rm -f /var/run/docker.sock
if: matrix.runtime != 'docker'
- uses: ./.github/actions/install-containerd-2.1
if: matrix.runtime == 'containerd'
- uses: ./.github/actions/install-k8s
env:
KUBERNETES_VERSION: v1.33
if: matrix.kubernetes == 'kubeadm init'
- uses: ./.github/actions/install-k3s
if: matrix.kubernetes == 'k3s'
- uses: actions/download-artifact@v4
with:
name: freeipa-server-${{ matrix.os }}-${{matrix.arch}}
- run: sudo apt install -y skopeo
if: matrix.runtime == 'cri-o'
- run: sudo skopeo copy docker-archive:freeipa-server-${{ matrix.os }}-${{matrix.arch}}.tar.gz containers-storage:localhost/freeipa-server:${{ matrix.os }}
if: matrix.runtime == 'cri-o'
- name: Load image to docker
run: gunzip < freeipa-server-${{ matrix.os }}-${{matrix.arch}}.tar.gz | docker load
if: matrix.runtime == 'docker'
- name: Load image to containerd
run: gunzip < freeipa-server-${{ matrix.os }}-${{matrix.arch}}.tar.gz | sudo ctr -n k8s.io images import -
if: matrix.runtime == 'containerd'
- run: patch tests/freeipa-k8s.yaml < tests/freeipa-k8s.yaml.docker.patch
if: matrix.runtime == 'docker'
- run: patch tests/freeipa-replica-k8s.yaml < tests/freeipa-k8s.yaml.docker.patch
if: matrix.runtime == 'docker'
- run: patch tests/freeipa-k8s.yaml < tests/freeipa-k8s.yaml.containerd.patch
if: matrix.runtime == 'containerd'
- run: patch tests/freeipa-replica-k8s.yaml < tests/freeipa-k8s.yaml.containerd.patch
if: matrix.runtime == 'containerd'
- name: Run master and replica in K8s
run: tests/run-in-k8s.sh localhost/freeipa-server:${{ matrix.os }}
push-after-success:
name: Push images to registries
runs-on: ubuntu-24.04
needs: [ master-and-replica, test-upgrade, test-k8s, test-plan ]
if: needs.test-plan.outputs.matrix-push != '[]' && github.event_name != 'pull_request' && github.repository == 'freeipa/freeipa-container' && github.ref == 'refs/heads/master'
strategy:
fail-fast: false
matrix:
os: ${{ fromJSON(needs.test-plan.outputs.matrix-push ) }}
timeout-minutes: 30
steps:
- run: |
ARCHES=$( jq -cn '${{ needs.test-plan.outputs.matrix-push-data }} | map(select(.os == "${{ matrix.os }}")) | .[].arch' )
echo "arches=$ARCHES" | tee -a $GITHUB_OUTPUT
( echo -n "arches_num=" ; echo "$ARCHES" | jq -r 'length' ) | tee -a $GITHUB_OUTPUT
( echo -n "arch0=" ; echo "$ARCHES" | jq -r '.[0]' ) | tee -a $GITHUB_OUTPUT
id: get-arches
- uses: actions/download-artifact@v4
with:
name: freeipa-server-${{ matrix.os }}-${{ steps.get-arches.outputs.arch0 }}
if: steps.get-arches.outputs.arches_num == 1
- uses: actions/download-artifact@v4
with:
pattern: freeipa-server-${{ matrix.os }}-*
merge-multiple: true
if: steps.get-arches.outputs.arches_num != 1
- run: |
echo '${{ steps.get-arches.outputs.arches }}' \
| jq -r '.[] | "docker-archive:freeipa-server-${{ matrix.os }}-" + . + ".tar.gz"' \
| xargs podman manifest create freeipa-server:${{ matrix.os }}
if: steps.get-arches.outputs.arches_num != 1
- name: Prepare authentication file
run: |
cat > auth.json << 'EOF'
${{ secrets.REGISTRY_CREDENTIALS_FILE }}
EOF
- name: Copy ${{ matrix.os }} to registries
run: |
set -e
f=docker-archive:freeipa-server-${{ matrix.os }}-${{ steps.get-arches.outputs.arch0 }}.tar.gz
while read r ; do
echo Copying freeipa-server:${{ matrix.os }} to ${r#docker://}
if [ "${{ steps.get-arches.outputs.arches_num }}" -eq 1 ] ; then
skopeo copy --authfile=auth.json $f $r:${{ matrix.os }}
else
podman manifest push --authfile=auth.json freeipa-server:${{ matrix.os }} $r:${{ matrix.os }}
fi
VERSION=$( skopeo inspect --format='{{index .Labels "org.opencontainers.image.version"}}' $f | sed 's/-.*//' )
test -n "$VERSION"
skopeo copy --authfile=auth.json --all $r:${{ matrix.os }} $r:${{ matrix.os }}-$VERSION
echo Tagged as ${{ matrix.os }}-$VERSION as well
done << 'EOF'
${{ secrets.REGISTRY_TARGET_LIST }}
EOF