Build and test FreeIPA containers #1220
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: 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 | |