CI workflow (kyl191/ansible-role-openvpn)
The CI workflow from kyl191/ansible-role-openvpn, explained and optimized by Latchkey.
CI health: C - fair
Point runs-on at Latchkey and get run de-duplication, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the CI workflow from the kyl191/ansible-role-openvpn repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
---
name: CI
on: # yamllint disable-line rule:truthy
push:
branches:
- master
pull_request:
branches:
- master
jobs:
check-syntax:
runs-on: ubuntu-latest
name: check-syntax
steps:
- uses: actions/checkout@v4
- name: Run ansible-lint
uses: ansible/ansible-lint@main
with:
requirements_file: requirements.yml
build-rhel-legacy:
runs-on: ubuntu-latest
name: rhel-legacy-${{ matrix.version }}
needs:
- check-syntax
strategy:
fail-fast: false
matrix:
version:
- "almalinux:8"
- "rockylinux:8"
container:
image: diodonfrost/ansible-${{ matrix.version }}
env:
container: docker
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup
- ${{ github.workspace }}:/etc/ansible/roles/kyl191.openvpn
options: "--cap-add NET_ADMIN --cap-add SYS_ADMIN --device /dev/net/tun"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Update ansible
run: dnf install -y python39 && pip3.9 install -U ansible
- name: Install required dependencies from Ansible Galaxy
run: ansible-galaxy install -r /etc/ansible/roles/kyl191.openvpn/requirements.yml
- name: Make sure ansible connection is sane
run: ansible -m setup -c local -i 127.0.0.1, all
- name: Run ansible playbook
run: >
ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv
-e '{"openvpn_use_tls_crypt": false}'
- name: Check idempotency
run: >
ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv
-e '{"openvpn_use_tls_crypt": false}'
- name: Container state debug output
continue-on-error: true
run: |
ls -lR /etc/openvpn
echo "cat openvpn_udp_1194.conf"
find /etc/openvpn/ -maxdepth 3 -name openvpn_udp_1194.conf -type f -exec cat {} \;
echo "cat alpha-*.ovpn"
find /etc/openvpn/ -maxdepth 3 -name "alpha-*.ovpn" -type f -exec cat {} \;
build-debian-like:
runs-on: ubuntu-latest
name: debian-like-${{ matrix.version }}
needs:
- check-syntax
strategy:
fail-fast: false
matrix:
version:
- "ubuntu:22.04"
- "debian:12"
container:
image: diodonfrost/ansible-${{ matrix.version }}
env:
container: docker
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup
- ${{ github.workspace }}:/etc/ansible/roles/kyl191.openvpn
options: "--cap-add NET_ADMIN --cap-add SYS_ADMIN --device /dev/net/tun"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Upgrade ansible
run: apt update && apt-get install --only-upgrade ansible -y
- name: Install required dependencies from Ansible Galaxy
run: ansible-galaxy install -r /etc/ansible/roles/kyl191.openvpn/requirements.yml
- name: Make sure ansible connection is sane
run: ansible -m setup -c local -i 127.0.0.1, all
- name: Run ansible playbook
run: ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv -e openvpn_firewall=iptables
- name: Check idempotency
run: ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv -e openvpn_firewall=iptables
- name: Container state debug output
continue-on-error: true
run: |
ls -lR /etc/openvpn
echo "cat openvpn_udp_1194.conf"
find /etc/openvpn/ -maxdepth 3 -name openvpn_udp_1194.conf -type f -exec cat {} \;
echo "cat alpha-*.ovpn"
find /etc/openvpn/ -maxdepth 3 -name "alpha-*.ovpn" -type f -exec cat {} \;
build-systemd:
runs-on: ubuntu-latest
name: ${{ matrix.version }}
needs:
- check-syntax
strategy:
fail-fast: false
matrix:
version:
- "fedora-42"
- "fedora-43"
- "almalinux-9"
- "almalinux-10"
- "rockylinux-9"
- "rockylinux-10"
- "centos-stream9"
- "centos-stream10"
- "debian-13"
- "ubuntu-24.04"
- "ubuntu-25.10"
# - "ubi9-init"
# - "ubi10-init"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Login to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | sudo podman login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Create container
run: sudo podman create --name ${{ matrix.version }} --privileged --cap-add NET_ADMIN --cap-add SYS_ADMIN --device /dev/net/tun --cgroupns=host --network=bridge --systemd=always --volume=${GITHUB_WORKSPACE}:/etc/ansible/roles/kyl191.openvpn --user=root ghcr.io/kyl191/ansible-images:${{ matrix.version }}
- name: Start container
run: sudo podman start ${{ matrix.version }}
# https://www.jeffgeerling.com/blog/2020/resolving-fedora-dnf-error-no-such-file-or-directory-varlibdnfrpmdblockpid
# Add '|| true' so the CI doesn't exit if the second command fails
- name: Wait for container to start
run: |
until sudo podman exec ${{ matrix.version }} systemctl is-active --quiet multi-user.target; do
echo "Waiting for systemd... Current failed units:"
sudo podman exec ${{ matrix.version }} systemctl list-units --failed --no-legend --no-pager 2>/dev/null || true
sleep 5
done
timeout-minutes: 5
- name: Install required dependencies from Ansible Galaxy
run: sudo podman exec ${{ matrix.version }} ansible-galaxy install -r /etc/ansible/roles/kyl191.openvpn/requirements.yml
- name: Make sure ansible connection is sane
run: sudo podman exec ${{ matrix.version }} ansible -m setup -c local -i 127.0.0.1, all
- name: Run ansible playbook
run: >
sudo podman exec ${{ matrix.version }} ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv -e '{"openvpn_ci_build": false}'
- name: Check idempotency
run: >
sudo podman exec ${{ matrix.version }} ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv -e '{"openvpn_ci_build": false}'
- name: Move generated client config file
run: sudo podman exec ${{ matrix.version }} cp /etc/openvpn/server/alpha-localhost.ovpn /etc/openvpn/client/alpha-localhost.conf
- name: Attempt openvpn connection
run: sudo podman exec ${{ matrix.version }} systemctl start openvpn-client@alpha-localhost.service
- name: Wait for a bit
run: sleep 5
- name: Stop openvpn connection
run: sudo podman exec ${{ matrix.version }} systemctl stop openvpn-client@alpha-localhost.service
- name: Check revocation
run: >
sudo podman exec ${{ matrix.version }} ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/revocation-test.yml -vv -e '{"openvpn_ci_build": false}'
- name: Container state debug output
continue-on-error: true
run: |
sudo podman exec ${{ matrix.version }} ls -lR /etc/openvpn
echo "cat openvpn_udp_1194.conf"
sudo podman exec ${{ matrix.version }} find /etc/openvpn/ -maxdepth 3 -name openvpn_udp_1194.conf -type f -exec cat {} \;
echo "cat alpha-*.ovpn"
sudo podman exec ${{ matrix.version }} find /etc/openvpn/ -maxdepth 3 -name "alpha-*.ovpn" -type f -exec cat {} \;
echo cat /var/log/openvpn.log
sudo podman exec ${{ matrix.version }} cat /var/log/openvpn.log
echo journalctl -u openvpn-client@alpha-localhost.service
sudo podman exec ${{ matrix.version }} journalctl -u openvpn-client@alpha-localhost.service
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
--- name: CI on: # yamllint disable-line rule:truthy push: branches: - master pull_request: branches: - master concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: check-syntax: timeout-minutes: 30 runs-on: latchkey-small name: check-syntax steps: - uses: actions/checkout@v4 - name: Run ansible-lint uses: ansible/ansible-lint@main with: requirements_file: requirements.yml build-rhel-legacy: timeout-minutes: 30 runs-on: latchkey-small name: rhel-legacy-${{ matrix.version }} needs: - check-syntax strategy: fail-fast: false matrix: version: - "almalinux:8" - "rockylinux:8" container: image: diodonfrost/ansible-${{ matrix.version }} env: container: docker volumes: - /sys/fs/cgroup:/sys/fs/cgroup - ${{ github.workspace }}:/etc/ansible/roles/kyl191.openvpn options: "--cap-add NET_ADMIN --cap-add SYS_ADMIN --device /dev/net/tun" steps: - name: Checkout repository uses: actions/checkout@v4 - name: Update ansible run: dnf install -y python39 && pip3.9 install -U ansible - name: Install required dependencies from Ansible Galaxy run: ansible-galaxy install -r /etc/ansible/roles/kyl191.openvpn/requirements.yml - name: Make sure ansible connection is sane run: ansible -m setup -c local -i 127.0.0.1, all - name: Run ansible playbook run: > ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv -e '{"openvpn_use_tls_crypt": false}' - name: Check idempotency run: > ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv -e '{"openvpn_use_tls_crypt": false}' - name: Container state debug output continue-on-error: true run: | ls -lR /etc/openvpn echo "cat openvpn_udp_1194.conf" find /etc/openvpn/ -maxdepth 3 -name openvpn_udp_1194.conf -type f -exec cat {} \; echo "cat alpha-*.ovpn" find /etc/openvpn/ -maxdepth 3 -name "alpha-*.ovpn" -type f -exec cat {} \; build-debian-like: timeout-minutes: 30 runs-on: latchkey-small name: debian-like-${{ matrix.version }} needs: - check-syntax strategy: fail-fast: false matrix: version: - "ubuntu:22.04" - "debian:12" container: image: diodonfrost/ansible-${{ matrix.version }} env: container: docker volumes: - /sys/fs/cgroup:/sys/fs/cgroup - ${{ github.workspace }}:/etc/ansible/roles/kyl191.openvpn options: "--cap-add NET_ADMIN --cap-add SYS_ADMIN --device /dev/net/tun" steps: - name: Checkout repository uses: actions/checkout@v4 - name: Upgrade ansible run: apt update && apt-get install --only-upgrade ansible -y - name: Install required dependencies from Ansible Galaxy run: ansible-galaxy install -r /etc/ansible/roles/kyl191.openvpn/requirements.yml - name: Make sure ansible connection is sane run: ansible -m setup -c local -i 127.0.0.1, all - name: Run ansible playbook run: ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv -e openvpn_firewall=iptables - name: Check idempotency run: ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv -e openvpn_firewall=iptables - name: Container state debug output continue-on-error: true run: | ls -lR /etc/openvpn echo "cat openvpn_udp_1194.conf" find /etc/openvpn/ -maxdepth 3 -name openvpn_udp_1194.conf -type f -exec cat {} \; echo "cat alpha-*.ovpn" find /etc/openvpn/ -maxdepth 3 -name "alpha-*.ovpn" -type f -exec cat {} \; build-systemd: timeout-minutes: 30 runs-on: latchkey-small name: ${{ matrix.version }} needs: - check-syntax strategy: fail-fast: false matrix: version: - "fedora-42" - "fedora-43" - "almalinux-9" - "almalinux-10" - "rockylinux-9" - "rockylinux-10" - "centos-stream9" - "centos-stream10" - "debian-13" - "ubuntu-24.04" - "ubuntu-25.10" # - "ubi9-init" # - "ubi10-init" steps: - name: Checkout repository uses: actions/checkout@v4 - name: Login to GHCR run: echo "${{ secrets.GITHUB_TOKEN }}" | sudo podman login ghcr.io -u ${{ github.actor }} --password-stdin - name: Create container run: sudo podman create --name ${{ matrix.version }} --privileged --cap-add NET_ADMIN --cap-add SYS_ADMIN --device /dev/net/tun --cgroupns=host --network=bridge --systemd=always --volume=${GITHUB_WORKSPACE}:/etc/ansible/roles/kyl191.openvpn --user=root ghcr.io/kyl191/ansible-images:${{ matrix.version }} - name: Start container run: sudo podman start ${{ matrix.version }} # https://www.jeffgeerling.com/blog/2020/resolving-fedora-dnf-error-no-such-file-or-directory-varlibdnfrpmdblockpid # Add '|| true' so the CI doesn't exit if the second command fails - name: Wait for container to start run: | until sudo podman exec ${{ matrix.version }} systemctl is-active --quiet multi-user.target; do echo "Waiting for systemd... Current failed units:" sudo podman exec ${{ matrix.version }} systemctl list-units --failed --no-legend --no-pager 2>/dev/null || true sleep 5 done timeout-minutes: 5 - name: Install required dependencies from Ansible Galaxy run: sudo podman exec ${{ matrix.version }} ansible-galaxy install -r /etc/ansible/roles/kyl191.openvpn/requirements.yml - name: Make sure ansible connection is sane run: sudo podman exec ${{ matrix.version }} ansible -m setup -c local -i 127.0.0.1, all - name: Run ansible playbook run: > sudo podman exec ${{ matrix.version }} ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv -e '{"openvpn_ci_build": false}' - name: Check idempotency run: > sudo podman exec ${{ matrix.version }} ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/test.yml -vv -e '{"openvpn_ci_build": false}' - name: Move generated client config file run: sudo podman exec ${{ matrix.version }} cp /etc/openvpn/server/alpha-localhost.ovpn /etc/openvpn/client/alpha-localhost.conf - name: Attempt openvpn connection run: sudo podman exec ${{ matrix.version }} systemctl start openvpn-client@alpha-localhost.service - name: Wait for a bit run: sleep 5 - name: Stop openvpn connection run: sudo podman exec ${{ matrix.version }} systemctl stop openvpn-client@alpha-localhost.service - name: Check revocation run: > sudo podman exec ${{ matrix.version }} ansible-playbook /etc/ansible/roles/kyl191.openvpn/tests/revocation-test.yml -vv -e '{"openvpn_ci_build": false}' - name: Container state debug output continue-on-error: true run: | sudo podman exec ${{ matrix.version }} ls -lR /etc/openvpn echo "cat openvpn_udp_1194.conf" sudo podman exec ${{ matrix.version }} find /etc/openvpn/ -maxdepth 3 -name openvpn_udp_1194.conf -type f -exec cat {} \; echo "cat alpha-*.ovpn" sudo podman exec ${{ matrix.version }} find /etc/openvpn/ -maxdepth 3 -name "alpha-*.ovpn" -type f -exec cat {} \; echo cat /var/log/openvpn.log sudo podman exec ${{ matrix.version }} cat /var/log/openvpn.log echo journalctl -u openvpn-client@alpha-localhost.service sudo podman exec ${{ matrix.version }} journalctl -u openvpn-client@alpha-localhost.service
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
1 third-party action is referenced by a movable tag. Pin it to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
This workflow runs 4 jobs (16 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.