ci: move keycloak+aio+csi jobs to zuul (#1043)

Signed-off-by: Mohammed Naser <mnaser@vexxhost.com>
diff --git a/.github/workflows/csi.yml b/.github/workflows/csi.yml
deleted file mode 100644
index 0f10664..0000000
--- a/.github/workflows/csi.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright (c) 2023 VEXXHOST, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-name: csi
-
-on:
-  pull_request:
-    paths:
-      - .github/workflows/csi.yml
-      - molecule/csi/**
-      - playbooks/csi.yml
-      - playbooks/kubernetes.yml
-      - roles/ceph_csi_rbd/**
-      - roles/csi/**
-      - galaxy.yml
-  push:
-    branches:
-      - main
-    paths:
-      - .github/workflows/csi.yml
-      - molecule/csi/**
-      - playbooks/csi.yml
-      - playbooks/kubernetes.yml
-      - roles/ceph_csi_rbd/**
-      - roles/csi/**
-      - galaxy.yml
-
-jobs:
-  test:
-    runs-on: v3-standard-16
-    strategy:
-      fail-fast: false
-      matrix:
-        driver:
-          - local-path-provisioner
-          - rbd
-    steps:
-      - name: Checkout project
-        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
-
-      - name: Install Poetry
-        run: pipx install poetry
-
-      - name: Setup Python
-        uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5
-        with:
-          cache: poetry
-
-      - name: Install dependencies
-        run: poetry install --no-interaction --with dev
-
-      # NOTE(mnaser): LVM commands take a long time if there are any existing
-      #               loop devices created by "snapd", so we uninstall it.
-      - name: Uninstall "snapd"
-        run: sudo apt-get purge -y snapd
-
-      - name: Turn off swap
-        run: sudo swapoff -a
-
-      - name: Run Molecule
-        run: poetry run molecule test -s csi
-        env:
-          MOLECULE_CSI_DRIVER: ${{ matrix.driver }}
diff --git a/.github/workflows/keycloak.yml b/.github/workflows/keycloak.yml
deleted file mode 100644
index 5698ea2..0000000
--- a/.github/workflows/keycloak.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright (c) 2023 VEXXHOST, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-name: keycloak
-
-on:
-  pull_request:
-    paths-ignore:
-      - images/**
-      - go.mod
-      - go.sum
-
-jobs:
-  molecule:
-    runs-on: v3-standard-8
-    steps:
-      - name: Checkout project
-        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
-
-      - name: Install Poetry
-        run: pipx install poetry
-
-      - name: Setup Python
-        uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5
-        with:
-          cache: poetry
-
-      - name: Install dependencies
-        run: poetry install --no-interaction --with dev
-
-      - name: Turn off swap
-        run: sudo swapoff -a
-
-      - name: Run Molecule
-        run: poetry run molecule test -s keycloak
diff --git a/Jenkinsfile b/Jenkinsfile
index 074a060..6228f11 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -127,60 +127,6 @@
       }
     }
 
-    stage('integration') {
-      matrix {
-        axes {
-          axis {
-            name 'NETWORK_BACKEND'
-            values 'openvswitch', 'ovn'
-          }
-        }
-
-        agent {
-          label 'jammy-16c-64g'
-        }
-
-        environment {
-          ATMOSPHERE_DEBUG = "true"
-          ATMOSPHERE_NETWORK_BACKEND = "${NETWORK_BACKEND}"
-
-          // NOTE(mnaser): OVN is currently unstable and we don't want it to mark builds as failed.
-          BUILD_RESULT_ON_FAILURE = "${NETWORK_BACKEND == 'ovn' ? 'SUCCESS' : 'FAILURE'}"
-          STAGE_RESULT_ON_FAILURE = "${NETWORK_BACKEND == 'ovn' ? 'UNSTABLE' : 'FAILURE'}"
-        }
-
-        stages {
-          stage('molecule') {
-            steps {
-              // Checkout code with built/pinned images
-              unstash 'src-with-pinned-images'
-
-              // Install dependencies
-              sh 'sudo apt-get install -y git python3-pip'
-              sh 'sudo pip install poetry'
-              sh 'sudo poetry install --with dev'
-
-              catchError(buildResult: "${BUILD_RESULT_ON_FAILURE}", stageResult: "${STAGE_RESULT_ON_FAILURE}") {
-                sh 'sudo --preserve-env=ATMOSPHERE_DEBUG,ATMOSPHERE_NETWORK_BACKEND poetry run molecule test -s aio'
-              }
-            }
-          }
-        }
-
-        post {
-          always {
-            // Kubernetes logs
-            sh "sudo ./build/fetch-kubernetes-logs.sh logs/${NETWORK_BACKEND}/kubernetes || true"
-            archiveArtifacts artifacts: 'logs/**', allowEmptyArchive: true
-
-            // JUnit results for Tempest
-            sh "sudo ./build/fetch-junit-xml.sh tempest-${NETWORK_BACKEND}.xml || true"
-            junit "tempest-${NETWORK_BACKEND}.xml"
-          }
-        }
-      }
-    }
-
     // promote images
     // release?
     // todo: manual pin commit to main (avoiding loop)
diff --git a/build/fetch-kubernetes-logs.sh b/build/fetch-kubernetes-logs.sh
deleted file mode 100755
index 5b028b5..0000000
--- a/build/fetch-kubernetes-logs.sh
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/bash -x
-
-# Check if an argument was provided
-if [ "$#" -ne 1 ]; then
-    echo "Usage: $0 <path_to_save_logs>"
-    exit 1
-fi
-
-# Define the base directory where you want to save the logs
-BASE_DIR="$1"
-
-# Create the base directory if it doesn't exist
-mkdir -p "$BASE_DIR"
-
-# Function to fetch logs for a pod and container
-fetch_logs() {
-    local ns="$1"
-    local pod="$2"
-    local container="$3"
-    local pod_dir="$BASE_DIR/$ns/$pod"
-    local log_file="$pod_dir/$container.log"
-    local prev_log_file="$pod_dir/${container}-previous.log"
-
-    # Ensure the pod directory exists
-    mkdir -p "$pod_dir"
-
-    # Fetch current logs
-    kubectl logs "$pod" -n "$ns" -c "$container" > "$log_file" 2>/dev/null
-
-    # Fetch previous logs if they exist
-    if kubectl logs "$pod" -n "$ns" -c "$container" --previous &>/dev/null; then
-        kubectl logs "$pod" -n "$ns" -c "$container" --previous > "$prev_log_file" 2>/dev/null
-    fi
-}
-
-export -f fetch_logs
-export BASE_DIR
-
-# Get all namespaces
-namespaces=$(kubectl get ns -o jsonpath='{.items[*].metadata.name}')
-
-# Loop through each namespace
-for ns in $namespaces; do
-    (
-        # Create a directory for the namespace
-        mkdir -p "$BASE_DIR/$ns"
-
-        # Get all pods in the namespace
-        pods=$(kubectl get pods -n "$ns" -o jsonpath='{.items[*].metadata.name}')
-
-        # Loop through each pod
-        for pod in $pods; do
-            (
-                # Create a directory for the pod
-                mkdir -p "$BASE_DIR/$ns/$pod"
-
-                # Get all containers in the pod
-                containers=$(kubectl get pod "$pod" -n "$ns" -o jsonpath='{.spec.containers[*].name}')
-
-                # Loop through each container
-                for container in $containers; do
-                    # Fetch logs in parallel
-                    fetch_logs "$ns" "$pod" "$container" &
-                done
-
-                # Wait for all background log fetches to complete before moving to the next pod
-                wait
-            ) &
-        done
-        # Wait for all background pods processing to complete before moving to the next namespace
-        wait
-    ) &
-done
-
-# Wait for all background namespaces processing to complete
-wait
-
-echo "Logs have been saved to $BASE_DIR"
diff --git a/galaxy.yml b/galaxy.yml
index f879dfa..5b75cb9 100644
--- a/galaxy.yml
+++ b/galaxy.yml
@@ -17,7 +17,7 @@
   kubernetes.core: 2.4.0
   openstack.cloud: 1.7.0
   vexxhost.ceph: 3.0.1
-  vexxhost.kubernetes: ">=1.13.2"
+  vexxhost.kubernetes: ">=1.13.4"
 tags:
   - application
   - cloud
diff --git a/molecule/aio/group_vars/all/molecule.yml b/molecule/aio/group_vars/all/molecule.yml
index 6b68864..590db4e 100644
--- a/molecule/aio/group_vars/all/molecule.yml
+++ b/molecule/aio/group_vars/all/molecule.yml
@@ -15,7 +15,7 @@
   operator:
     replicas: 1
 
-csi_driver: local-path-provisioner
+csi_driver: "{{ lookup('env', 'MOLECULE_CSI_DRIVER') | default('local-path-provisioner', True) }}"
 
 cluster_issuer_type: self-signed
 
diff --git a/molecule/csi/converge.yml b/molecule/csi/converge.yml
index 4f54b15..6f62d32 100644
--- a/molecule/csi/converge.yml
+++ b/molecule/csi/converge.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2023 VEXXHOST, Inc.
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -12,4 +12,12 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- ansible.builtin.import_playbook: vexxhost.atmosphere.csi
+- name: Install Ceph
+  ansible.builtin.import_playbook: vexxhost.atmosphere.ceph
+  when: csi_driver == 'rbd'
+
+- name: Install Kubernetes
+  ansible.builtin.import_playbook: vexxhost.atmosphere.kubernetes
+
+- name: Install CSI
+  ansible.builtin.import_playbook: vexxhost.atmosphere.csi
diff --git a/molecule/csi/create.yml b/molecule/csi/create.yml
new file mode 120000
index 0000000..5e5dbdc
--- /dev/null
+++ b/molecule/csi/create.yml
@@ -0,0 +1 @@
+../aio/create.yml
\ No newline at end of file
diff --git a/molecule/csi/group_vars b/molecule/csi/group_vars
new file mode 120000
index 0000000..ee802c8
--- /dev/null
+++ b/molecule/csi/group_vars
@@ -0,0 +1 @@
+../aio/group_vars/
\ No newline at end of file
diff --git a/molecule/csi/host_vars b/molecule/csi/host_vars
new file mode 120000
index 0000000..52a636e
--- /dev/null
+++ b/molecule/csi/host_vars
@@ -0,0 +1 @@
+../aio/host_vars/
\ No newline at end of file
diff --git a/molecule/csi/molecule.yml b/molecule/csi/molecule.yml
index 6446c65..d1e4346 120000
--- a/molecule/csi/molecule.yml
+++ b/molecule/csi/molecule.yml
@@ -1 +1 @@
-../shared/molecule.yml
\ No newline at end of file
+../aio/molecule.yml
\ No newline at end of file
diff --git a/molecule/csi/prepare.yml b/molecule/csi/prepare.yml
deleted file mode 100644
index d922e73..0000000
--- a/molecule/csi/prepare.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright (c) 2023 VEXXHOST, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-- ansible.builtin.import_playbook: ../shared/prepare/base.yml
-- ansible.builtin.import_playbook: ../shared/prepare/ceph.yml
-- ansible.builtin.import_playbook: ../shared/prepare/kubernetes.yml
diff --git a/molecule/csi/prepare.yml b/molecule/csi/prepare.yml
new file mode 120000
index 0000000..2eb2ce8
--- /dev/null
+++ b/molecule/csi/prepare.yml
@@ -0,0 +1 @@
+../aio/prepare.yml
\ No newline at end of file
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..a167475
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,40 @@
+[tox]
+minversion = 4
+skipsdist = True
+
+[testenv:molecule]
+deps =
+  molecule
+  molecule-plugins[docker]
+  # TODO(mnaser): We should remove these once we have an actual installable
+  #               Python package and drop `skipsdist`.
+  docker-image-py
+  jmespath
+  netaddr
+  openstacksdk<0.99.0
+  rjsonnet
+
+[testenv:molecule-keycloak]
+deps =
+  {[testenv:molecule]deps}
+commands =
+  molecule test -s keycloak
+
+[testenv:molecule-csi-{rbd,local-path-provisioner}]
+deps =
+  {[testenv:molecule]deps}
+setenv =
+  rbd: MOLECULE_CSI_DRIVER = rbd
+  local-path-provisioner: MOLECULE_CSI_DRIVER = local-path-provisioner
+commands =
+  molecule test -s csi
+
+[testenv:molecule-aio-{openvswitch,ovn}]
+deps =
+  {[testenv:molecule]deps}
+setenv =
+  ATMOSPHERE_DEBUG = true
+  openvswitch: ATMOSPHERE_NETWORK_BACKEND = openvswitch
+  ovn: ATMOSPHERE_NETWORK_BACKEND = ovn
+commands =
+  molecule test -s aio
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
new file mode 100644
index 0000000..f67102a
--- /dev/null
+++ b/zuul.d/jobs.yaml
@@ -0,0 +1,71 @@
+# Copyright (c) 2024 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- job:
+    name: atmosphere-molecule
+    parent: tox
+    abstract: true
+    pre-run: zuul.d/playbooks/molecule/pre.yml
+
+- job:
+    name: atmosphere-molecule-keycloak
+    parent: atmosphere-molecule
+    pre-run: zuul.d/playbooks/molecule-keycloak/pre.yml
+    vars:
+      tox_envlist: molecule-keycloak
+
+- job:
+    name: atmosphere-molecule-csi
+    parent: atmosphere-molecule
+    run: zuul.d/playbooks/molecule-csi/run.yml
+    abstract: true
+
+- job:
+    name: atmosphere-molecule-csi-local-path-provisioner
+    parent: atmosphere-molecule-csi
+    vars:
+      csi_driver: local-path-provisioner
+
+- job:
+    name: atmosphere-molecule-csi-rbd
+    parent: atmosphere-molecule-csi
+    vars:
+      csi_driver: rbd
+
+- job:
+    name: atmosphere-molecule-aio
+    parent: atmosphere-molecule
+    abstract: true
+    timeout: 7200
+    pre-run: zuul.d/playbooks/molecule-aio/pre.yml
+    run: zuul.d/playbooks/molecule-aio/run.yml
+    post-run: zuul.d/playbooks/molecule-aio/post.yml
+    roles:
+      - zuul: openstack/openstack-helm-infra
+    nodeset:
+      nodes:
+        - name: ubuntu-jammy
+          label: jammy-16c-64g
+
+- job:
+    name: atmosphere-molecule-aio-openvswitch
+    parent: atmosphere-molecule-aio
+    vars:
+      network_backend: openvswitch
+
+- job:
+    name: atmosphere-molecule-aio-ovn
+    parent: atmosphere-molecule-aio
+    vars:
+      network_backend: ovn
diff --git a/build/fetch-junit-xml.sh b/zuul.d/playbooks/molecule-aio/post.yml
old mode 100755
new mode 100644
similarity index 63%
copy from build/fetch-junit-xml.sh
copy to zuul.d/playbooks/molecule-aio/post.yml
index 785df28..d711edc
--- a/build/fetch-junit-xml.sh
+++ b/zuul.d/playbooks/molecule-aio/post.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -12,7 +12,15 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-REPORT_FILE_NAME="${1:-report.xml}"
-
-sudo apt-get install -y subunit python3-junitxml
-sudo subunit-1to2 /tmp/stestr/0 | subunit2junitxml > "${REPORT_FILE_NAME}"
+- hosts: all
+  become: true
+  vars:
+    work_dir: "{{ zuul.project.src_dir }}"
+    logs_dir: "/tmp/logs"
+  roles:
+    - gather-host-logs
+    - helm-release-status
+    - describe-kubernetes-objects
+    - gather-pod-logs
+    - gather-prom-metrics
+    - gather-selenium-data
diff --git a/build/fetch-junit-xml.sh b/zuul.d/playbooks/molecule-aio/pre.yml
old mode 100755
new mode 100644
similarity index 72%
rename from build/fetch-junit-xml.sh
rename to zuul.d/playbooks/molecule-aio/pre.yml
index 785df28..8443f26
--- a/build/fetch-junit-xml.sh
+++ b/zuul.d/playbooks/molecule-aio/pre.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -12,7 +12,10 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-REPORT_FILE_NAME="${1:-report.xml}"
-
-sudo apt-get install -y subunit python3-junitxml
-sudo subunit-1to2 /tmp/stestr/0 | subunit2junitxml > "${REPORT_FILE_NAME}"
+- name: Prepare host for Molecule
+  hosts: all
+  tasks:
+    - name: Install "jq" for log collection
+      become: true
+      ansible.builtin.package:
+        name: jq
diff --git a/molecule/csi/cleanup.yml b/zuul.d/playbooks/molecule-aio/run.yml
similarity index 78%
copy from molecule/csi/cleanup.yml
copy to zuul.d/playbooks/molecule-aio/run.yml
index 06dd8c4..92bdf0c 100644
--- a/molecule/csi/cleanup.yml
+++ b/zuul.d/playbooks/molecule-aio/run.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2023 VEXXHOST, Inc.
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -12,4 +12,9 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- ansible.builtin.import_playbook: ../shared/cleanup/ceph.yml
+- hosts: all
+  become: true
+  roles:
+    - tox
+  vars:
+    tox_envlist: "molecule-aio-{{ network_backend }}"
diff --git a/molecule/csi/cleanup.yml b/zuul.d/playbooks/molecule-csi/run.yml
similarity index 79%
copy from molecule/csi/cleanup.yml
copy to zuul.d/playbooks/molecule-csi/run.yml
index 06dd8c4..ce4465e 100644
--- a/molecule/csi/cleanup.yml
+++ b/zuul.d/playbooks/molecule-csi/run.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2023 VEXXHOST, Inc.
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -12,4 +12,9 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- ansible.builtin.import_playbook: ../shared/cleanup/ceph.yml
+- hosts: all
+  become: true
+  roles:
+    - tox
+  vars:
+    tox_envlist: "molecule-csi-{{ csi_driver }}"
diff --git a/molecule/csi/cleanup.yml b/zuul.d/playbooks/molecule-keycloak/pre.yml
similarity index 81%
rename from molecule/csi/cleanup.yml
rename to zuul.d/playbooks/molecule-keycloak/pre.yml
index 06dd8c4..ab0c6e1 100644
--- a/molecule/csi/cleanup.yml
+++ b/zuul.d/playbooks/molecule-keycloak/pre.yml
@@ -1,4 +1,4 @@
-# Copyright (c) 2023 VEXXHOST, Inc.
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -12,4 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- ansible.builtin.import_playbook: ../shared/cleanup/ceph.yml
+- name: Prepare host for Keycloak tests
+  hosts: all
+  roles:
+    - ensure-docker
diff --git a/zuul.d/playbooks/molecule/pre.yml b/zuul.d/playbooks/molecule/pre.yml
new file mode 100644
index 0000000..447f05e
--- /dev/null
+++ b/zuul.d/playbooks/molecule/pre.yml
@@ -0,0 +1,27 @@
+# Copyright (c) 2024 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Prepare host for Molecule tests
+  hosts: all
+  tasks:
+    - name: Disable swap
+      ansible.builtin.command: swapoff -a
+      ignore_errors: yes
+
+    # TODO(mnaser): Drop this when we can use https://github.com/vexxhost/atmosphere/pull/977
+    - name: Prefix all images for the job to point to mirror
+      ansible.builtin.shell: |
+        sed -i 's/  \(.*\): \(.*\)$/  \1: registry.atmosphere.dev\/\2/' roles/defaults/vars/main.yml
+      args:
+        chdir: "{{ zuul.project.src_dir }}"
diff --git a/build/fetch-junit-xml.sh b/zuul.d/project.yaml
old mode 100755
new mode 100644
similarity index 64%
copy from build/fetch-junit-xml.sh
copy to zuul.d/project.yaml
index 785df28..a94f6dc
--- a/build/fetch-junit-xml.sh
+++ b/zuul.d/project.yaml
@@ -1,4 +1,4 @@
-# Copyright (c) 2022 VEXXHOST, Inc.
+# Copyright (c) 2024 VEXXHOST, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
 # not use this file except in compliance with the License. You may obtain
@@ -12,7 +12,11 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-REPORT_FILE_NAME="${1:-report.xml}"
-
-sudo apt-get install -y subunit python3-junitxml
-sudo subunit-1to2 /tmp/stestr/0 | subunit2junitxml > "${REPORT_FILE_NAME}"
+- project:
+    check:
+      jobs:
+        - atmosphere-molecule-aio-openvswitch
+        - atmosphere-molecule-aio-ovn
+        - atmosphere-molecule-csi-local-path-provisioner
+        - atmosphere-molecule-csi-rbd
+        - atmosphere-molecule-keycloak