Merge "Add glance_image_tempfile_path" into stable/2024.1
diff --git a/.ansible-lint b/.ansible-lint
index 2776884..d6ddfd2 100644
--- a/.ansible-lint
+++ b/.ansible-lint
@@ -7,6 +7,7 @@
   - molecule
   - playbooks
   - plugins/filter
+  - plugins/modules/subnet.py
   - roles/defaults/vars/main.yml
   - roles/kube_prometheus_stack/files/jsonnet
   - roles/storpool_csi/files
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2c0ca4e..0fe99aa 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,4 +1,4 @@
-exclude: "^(roles/kube_prometheus_stack/files/jsonnet|charts)"
+exclude: "^(roles/kube_prometheus_stack/files/jsonnet|charts|plugins/modules/subnet.py)"
 
 repos:
   - repo: local
diff --git a/charts/barbican/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/barbican/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/barbican/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/barbican/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/ceph-provisioners/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/ceph-provisioners/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/ceph-provisioners/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/ceph-provisioners/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/cinder/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/cinder/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/cinder/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/cinder/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/designate/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/designate/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/designate/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/designate/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/glance/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/glance/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/glance/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/glance/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/heat/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/heat/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/heat/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/heat/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/horizon/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/horizon/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/horizon/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/horizon/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/ironic/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/ironic/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/ironic/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/ironic/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/keystone/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/keystone/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/keystone/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/keystone/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/libvirt/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/libvirt/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/libvirt/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/libvirt/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/magnum/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/magnum/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/magnum/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/magnum/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/manila/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/manila/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/manila/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/manila/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/memcached/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/memcached/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/memcached/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/memcached/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/neutron/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/neutron/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/neutron/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/neutron/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/nova/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/nova/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/nova/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/nova/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/octavia/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/octavia/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/octavia/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/octavia/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/openvswitch/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/openvswitch/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/openvswitch/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/openvswitch/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/ovn/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/ovn/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/ovn/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/ovn/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/patches/helm-toolkit/0002-Add-priority-runtime-ClassName-for-db_sync.patch b/charts/patches/helm-toolkit/0002-Add-priority-runtime-ClassName-for-db_sync.patch
new file mode 100644
index 0000000..1d32782
--- /dev/null
+++ b/charts/patches/helm-toolkit/0002-Add-priority-runtime-ClassName-for-db_sync.patch
@@ -0,0 +1,28 @@
+From ab35df279310c02396f3ef66e58949f3d6556105 Mon Sep 17 00:00:00 2001
+From: Mohammed Naser <mnaser@vexxhost.com>
+Date: Thu, 6 Feb 2025 09:18:59 -0500
+Subject: [PATCH] Add {priority,runtime}ClassName for db_sync
+
+---
+ helm-toolkit/templates/manifests/_job-db-sync.tpl | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/helm-toolkit/templates/manifests/_job-db-sync.tpl b/helm-toolkit/templates/manifests/_job-db-sync.tpl
+index 4696c88f..67fac86b 100644
+--- a/helm-toolkit/templates/manifests/_job-db-sync.tpl
++++ b/helm-toolkit/templates/manifests/_job-db-sync.tpl
+@@ -68,6 +68,12 @@ spec:
+       annotations:
+ {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
+     spec:
++{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
++      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
++{{- end }}
++{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
++      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
++{{- end }}
+       serviceAccountName: {{ $serviceAccountName }}
+       restartPolicy: OnFailure
+       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
+-- 
+2.43.0
diff --git a/charts/placement/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/placement/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/placement/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/placement/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/charts/tempest/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl b/charts/tempest/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
index 4696c88..5c35dd0 100644
--- a/charts/tempest/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
+++ b/charts/tempest/charts/helm-toolkit/templates/manifests/_job-db-sync.tpl
@@ -68,6 +68,12 @@
       annotations:
 {{ tuple $envAll | include "helm-toolkit.snippets.release_uuid" | indent 8 }}
     spec:
+{{- if and $envAll.Values.pod.priorityClassName $envAll.Values.pod.priorityClassName.db_sync }}
+      priorityClassName: {{ $envAll.Values.pod.priorityClassName.db_sync }}
+{{- end }}
+{{- if and $envAll.Values.pod.runtimeClassName $envAll.Values.pod.runtimeClassName.db_sync }}
+      runtimeClassName: {{ $envAll.Values.pod.runtimeClassName.db_sync }}
+{{- end }}
       serviceAccountName: {{ $serviceAccountName }}
       restartPolicy: OnFailure
       {{ tuple $envAll "db_sync" | include "helm-toolkit.snippets.kubernetes_image_pull_secrets" | indent 6 }}
diff --git a/galaxy.yml b/galaxy.yml
index ccbcf02..ab7e9b0 100644
--- a/galaxy.yml
+++ b/galaxy.yml
@@ -15,7 +15,7 @@
   community.general: 7.3.0
   community.mysql: 3.6.0
   kubernetes.core: 2.4.0
-  openstack.cloud: 1.7.0
+  openstack.cloud: ">=2.0.0"
   vexxhost.ceph: 3.0.1
   vexxhost.kubernetes: ">=2.0.1"
 tags:
diff --git a/molecule/default/requirements.txt b/molecule/default/requirements.txt
index 862d238..4544af7 100644
--- a/molecule/default/requirements.txt
+++ b/molecule/default/requirements.txt
@@ -1,3 +1,3 @@
 molecule==3.5.2 # https://github.com/ansible-community/molecule/issues/3435
-openstacksdk==0.61.0
+openstacksdk
 netaddr
diff --git a/molecule/keycloak/verify.yml b/molecule/keycloak/verify.yml
index e481319..7a4a179 100644
--- a/molecule/keycloak/verify.yml
+++ b/molecule/keycloak/verify.yml
@@ -48,15 +48,15 @@
       #              we try a few more times.
       retries: 30
       delay: 1
-      until: identity_user_info_result.openstack_users | length > 0
+      until: identity_user_info_result.users | length > 0
 
     - name: Assert that the user exists
       run_once: true
       ansible.builtin.assert:
         that:
-          - identity_user_info_result.openstack_users | length > 0
-          - identity_user_info_result.openstack_users[0].id == keycloak_user_info.id | regex_replace('-', '')
-          - identity_user_info_result.openstack_users[0].name == keycloak_user_info.username
+          - identity_user_info_result.users | length > 0
+          - identity_user_info_result.users[0].id == keycloak_user_info.id | regex_replace('-', '')
+          - identity_user_info_result.users[0].name == keycloak_user_info.username
 
     # TODO: Simulate Keystone authentication
     # TODO: Simulate Horizon login
diff --git a/plugins/modules/subnet.py b/plugins/modules/subnet.py
new file mode 100644
index 0000000..7c50aee
--- /dev/null
+++ b/plugins/modules/subnet.py
@@ -0,0 +1,486 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2013, Benno Joy <benno@ansible.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+DOCUMENTATION = '''
+---
+module: subnet
+short_description: Add/Remove subnet to an OpenStack network
+author: OpenStack Ansible SIG
+description:
+   - Add or Remove a subnet to an OpenStack network
+options:
+    state:
+        description:
+            - Indicate desired state of the resource
+        choices: ['present', 'absent']
+        default: present
+        type: str
+    allocation_pool_start:
+        description:
+            - From the subnet pool the starting address from which the IP
+              should be allocated.
+        type: str
+    allocation_pool_end:
+        description:
+            - From the subnet pool the last IP that should be assigned to the
+              virtual machines.
+        type: str
+    allocation_pools:
+        description:
+            - List of allocation pools to assign to the subnet. Each element
+              consists of a 'start' and 'end' value.
+        type: list
+        elements: dict
+    cidr:
+        description:
+            - The CIDR representation of the subnet that should be assigned to
+              the subnet. Required when I(state) is 'present' and a subnetpool
+              is not specified.
+        type: str
+    description:
+        description:
+            - Description of the subnet
+        type: str
+    disable_gateway_ip:
+        description:
+            - The gateway IP would not be assigned for this subnet
+        type: bool
+        aliases: ['no_gateway_ip']
+        default: 'false'
+    dns_nameservers:
+        description:
+            - List of DNS nameservers for this subnet.
+        type: list
+        elements: str
+    extra_attrs:
+        description:
+            - Dictionary with extra key/value pairs passed to the API
+        required: false
+        aliases: ['extra_specs']
+        default: {}
+        type: dict
+    host_routes:
+        description:
+            - A list of host route dictionaries for the subnet.
+        type: list
+        elements: dict
+        suboptions:
+            destination:
+                description: The destination network (CIDR).
+                type: str
+                required: true
+            nexthop:
+                description: The next hop (aka gateway) for the I(destination).
+                type: str
+                required: true
+    gateway_ip:
+        description:
+            - The ip that would be assigned to the gateway for this subnet
+        type: str
+    ip_version:
+        description:
+            - The IP version of the subnet 4 or 6
+        default: 4
+        type: int
+        choices: [4, 6]
+    is_dhcp_enabled:
+        description:
+            - Whether DHCP should be enabled for this subnet.
+        type: bool
+        aliases: ['enable_dhcp']
+        default: 'true'
+    ipv6_ra_mode:
+        description:
+            - IPv6 router advertisement mode
+        choices: ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac']
+        type: str
+    ipv6_address_mode:
+        description:
+            - IPv6 address mode
+        choices: ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac']
+        type: str
+    name:
+        description:
+            - The name of the subnet that should be created. Although Neutron
+              allows for non-unique subnet names, this module enforces subnet
+              name uniqueness.
+        required: true
+        type: str
+    network:
+        description:
+            - Name or id of the network to which the subnet should be attached
+            - Required when I(state) is 'present'
+        aliases: ['network_name']
+        type: str
+    project:
+        description:
+            - Project name or ID containing the subnet (name admin-only)
+        type: str
+    prefix_length:
+        description:
+            - The prefix length to use for subnet allocation from a subnet pool
+        type: str
+    use_default_subnet_pool:
+        description:
+            - Use the default subnetpool for I(ip_version) to obtain a CIDR.
+        type: bool
+        aliases: ['use_default_subnetpool']
+    subnet_pool:
+        description:
+            - The subnet pool name or ID from which to obtain a CIDR
+        type: str
+        required: false
+extends_documentation_fragment:
+- openstack.cloud.openstack
+'''
+
+EXAMPLES = '''
+# Create a new (or update an existing) subnet on the specified network
+- openstack.cloud.subnet:
+    state: present
+    network_name: network1
+    name: net1subnet
+    cidr: 192.168.0.0/24
+    dns_nameservers:
+       - 8.8.8.7
+       - 8.8.8.8
+    host_routes:
+       - destination: 0.0.0.0/0
+         nexthop: 12.34.56.78
+       - destination: 192.168.0.0/24
+         nexthop: 192.168.0.1
+
+# Delete a subnet
+- openstack.cloud.subnet:
+    state: absent
+    name: net1subnet
+
+# Create an ipv6 stateless subnet
+- openstack.cloud.subnet:
+    state: present
+    name: intv6
+    network_name: internal
+    ip_version: 6
+    cidr: 2db8:1::/64
+    dns_nameservers:
+        - 2001:4860:4860::8888
+        - 2001:4860:4860::8844
+    ipv6_ra_mode: dhcpv6-stateless
+    ipv6_address_mode: dhcpv6-stateless
+'''
+
+RETURN = '''
+id:
+    description: Id of subnet
+    returned: On success when subnet exists.
+    type: str
+subnet:
+    description: Dictionary describing the subnet.
+    returned: On success when subnet exists.
+    type: dict
+    contains:
+        allocation_pools:
+            description: Allocation pools associated with this subnet.
+            returned: success
+            type: list
+            elements: dict
+        cidr:
+            description: Subnet's CIDR.
+            returned: success
+            type: str
+        created_at:
+            description: Created at timestamp
+            type: str
+        description:
+            description: Description
+            type: str
+        dns_nameservers:
+            description: DNS name servers for this subnet.
+            returned: success
+            type: list
+            elements: str
+        dns_publish_fixed_ip:
+            description: Whether to publish DNS records for fixed IPs.
+            returned: success
+            type: bool
+        gateway_ip:
+            description: Subnet's gateway ip.
+            returned: success
+            type: str
+        host_routes:
+            description: A list of host routes.
+            returned: success
+            type: str
+        id:
+            description: Unique UUID.
+            returned: success
+            type: str
+        ip_version:
+            description: IP version for this subnet.
+            returned: success
+            type: int
+        ipv6_address_mode:
+            description: |
+                The IPv6 address modes which are 'dhcpv6-stateful',
+                'dhcpv6-stateless' or 'slaac'.
+            returned: success
+            type: str
+        ipv6_ra_mode:
+            description: |
+                The IPv6 router advertisements modes which can be 'slaac',
+                'dhcpv6-stateful', 'dhcpv6-stateless'.
+            returned: success
+            type: str
+        is_dhcp_enabled:
+            description: DHCP enable flag for this subnet.
+            returned: success
+            type: bool
+        name:
+            description: Name given to the subnet.
+            returned: success
+            type: str
+        network_id:
+            description: Network ID this subnet belongs in.
+            returned: success
+            type: str
+        prefix_length:
+            description: |
+                The prefix length to use for subnet allocation from a subnet
+                pool.
+            returned: success
+            type: str
+        project_id:
+            description: Project id associated with this subnet.
+            returned: success
+            type: str
+        revision_number:
+            description: Revision number of the resource
+            returned: success
+            type: int
+        segment_id:
+            description: The ID of the segment this subnet is associated with.
+            returned: success
+            type: str
+        service_types:
+            description: Service types for this subnet
+            returned: success
+            type: list
+        subnet_pool_id:
+            description: The subnet pool ID from which to obtain a CIDR.
+            returned: success
+            type: str
+        tags:
+            description: Tags
+            type: str
+        updated_at:
+            description: Timestamp when the subnet was last updated.
+            returned: success
+            type: str
+        use_default_subnet_pool:
+            description: |
+                Whether to use the default subnet pool to obtain a CIDR.
+            returned: success
+            type: bool
+'''
+
+from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
+
+
+class SubnetModule(OpenStackModule):
+    ipv6_mode_choices = ['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac']
+    argument_spec = dict(
+        name=dict(required=True),
+        network=dict(aliases=['network_name']),
+        cidr=dict(),
+        description=dict(),
+        ip_version=dict(type='int', default=4, choices=[4, 6]),
+        is_dhcp_enabled=dict(type='bool', default=True,
+                             aliases=['enable_dhcp']),
+        gateway_ip=dict(),
+        disable_gateway_ip=dict(
+            type='bool', default=False, aliases=['no_gateway_ip']),
+        dns_nameservers=dict(type='list', elements='str'),
+        allocation_pool_start=dict(),
+        allocation_pool_end=dict(),
+        allocation_pools=dict(type='list', elements='dict'),
+        host_routes=dict(type='list', elements='dict'),
+        ipv6_ra_mode=dict(choices=ipv6_mode_choices),
+        ipv6_address_mode=dict(choices=ipv6_mode_choices),
+        subnet_pool=dict(),
+        prefix_length=dict(),
+        use_default_subnet_pool=dict(
+            type='bool', aliases=['use_default_subnetpool']),
+        extra_attrs=dict(type='dict', default=dict(), aliases=['extra_specs']),
+        state=dict(default='present',
+                   choices=['absent', 'present']),
+        project=dict(),
+    )
+
+    module_kwargs = dict(
+        supports_check_mode=True,
+        required_together=[['allocation_pool_end', 'allocation_pool_start']],
+        required_if=[
+            ('state', 'present', ('network',)),
+            ('state', 'present',
+             ('cidr', 'use_default_subnet_pool', 'subnet_pool'), True),
+        ],
+        mutually_exclusive=[
+            ('use_default_subnet_pool', 'subnet_pool'),
+            ('allocation_pool_start', 'allocation_pools'),
+            ('allocation_pool_end', 'allocation_pools')
+        ]
+    )
+
+    # resource attributes obtainable directly from params
+    attr_params = ('cidr', 'description',
+                   'dns_nameservers', 'gateway_ip', 'host_routes',
+                   'ip_version', 'ipv6_address_mode', 'ipv6_ra_mode',
+                   'is_dhcp_enabled', 'name', 'prefix_length',
+                   'use_default_subnet_pool',)
+
+    def _validate_update(self, subnet, update):
+        """ Check for differences in non-updatable values """
+        # Ref.: https://docs.openstack.org/api-ref/network/v2/index.html#update-subnet
+        for attr in ('cidr', 'ip_version', 'ipv6_ra_mode', 'ipv6_address_mode',
+                     'prefix_length', 'use_default_subnet_pool'):
+            if attr in update and update[attr] != subnet[attr]:
+                self.fail_json(
+                    msg='Cannot update {0} in existing subnet'.format(attr))
+
+    def _system_state_change(self, subnet, network, project, subnet_pool):
+        state = self.params['state']
+        if state == 'absent':
+            return subnet is not None
+        # else state is present
+        if not subnet:
+            return True
+        params = self._build_params(network, project, subnet_pool)
+        updates = self._build_updates(subnet, params)
+        self._validate_update(subnet, updates)
+        return bool(updates)
+
+    def _build_pool(self):
+        pool_start = self.params['allocation_pool_start']
+        pool_end = self.params['allocation_pool_end']
+        if pool_start:
+            return [dict(start=pool_start, end=pool_end)]
+        return None
+
+    def _build_params(self, network, project, subnet_pool):
+        params = {attr: self.params[attr] for attr in self.attr_params}
+        params['network_id'] = network.id
+        if project:
+            params['project_id'] = project.id
+        if subnet_pool:
+            params['subnet_pool_id'] = subnet_pool.id
+        if self.params['allocation_pool_start']:
+            params['allocation_pools'] = self._build_pool()
+        else:
+            params['allocation_pools'] = self.params['allocation_pools']
+        params = self._add_extra_attrs(params)
+        params = {k: v for k, v in params.items() if v is not None}
+        if self.params['disable_gateway_ip']:
+            params['gateway_ip'] = None
+        return params
+
+    def _build_updates(self, subnet, params):
+        # Sort lists before doing comparisons comparisons
+        if 'dns_nameservers' in params:
+            params['dns_nameservers'].sort()
+            subnet['dns_nameservers'].sort()
+
+        if 'host_routes' in params:
+            params['host_routes'].sort(key=lambda r: sorted(r.items()))
+            subnet['host_routes'].sort(key=lambda r: sorted(r.items()))
+
+        if 'allocation_pools' in params:
+            params['allocation_pools'].sort(key=lambda r: sorted(r.items()))
+            subnet['allocation_pools'].sort(key=lambda r: sorted(r.items()))
+
+        updates = {k: params[k] for k in params if params[k] != subnet[k]}
+        if self.params['disable_gateway_ip'] and subnet.gateway_ip:
+            updates['gateway_ip'] = None
+        return updates
+
+    def _add_extra_attrs(self, params):
+        duplicates = set(self.params['extra_attrs']) & set(params)
+        if duplicates:
+            self.fail_json(msg='Duplicate key(s) {0} in extra_specs'
+                           .format(list(duplicates)))
+        params.update(self.params['extra_attrs'])
+        return params
+
+    def run(self):
+        state = self.params['state']
+        network_name_or_id = self.params['network']
+        project_name_or_id = self.params['project']
+        subnet_pool_name_or_id = self.params['subnet_pool']
+        subnet_name = self.params['name']
+        gateway_ip = self.params['gateway_ip']
+        disable_gateway_ip = self.params['disable_gateway_ip']
+
+        # fail early if incompatible options have been specified
+        if disable_gateway_ip and gateway_ip:
+            self.fail_json(msg='no_gateway_ip is not allowed with gateway_ip')
+
+        subnet_pool_filters = {}
+        filters = {}
+
+        project = None
+        if project_name_or_id:
+            project = self.conn.identity.find_project(project_name_or_id,
+                                                      ignore_missing=False)
+            subnet_pool_filters['project_id'] = project.id
+            filters['project_id'] = project.id
+
+        network = None
+        if network_name_or_id:
+            # At this point filters can only contain project_id
+            network = self.conn.network.find_network(network_name_or_id,
+                                                     ignore_missing=False,
+                                                     **filters)
+            filters['network_id'] = network.id
+
+        subnet_pool = None
+        if subnet_pool_name_or_id:
+            subnet_pool = self.conn.network.find_subnet_pool(
+                subnet_pool_name_or_id,
+                ignore_missing=False,
+                **subnet_pool_filters)
+            filters['subnet_pool_id'] = subnet_pool.id
+
+        subnet = self.conn.network.find_subnet(subnet_name, **filters)
+
+        if self.ansible.check_mode:
+            self.exit_json(changed=self._system_state_change(
+                subnet, network, project, subnet_pool))
+
+        changed = False
+        if state == 'present':
+            params = self._build_params(network, project, subnet_pool)
+            if subnet is None:
+                subnet = self.conn.network.create_subnet(**params)
+                changed = True
+            else:
+                updates = self._build_updates(subnet, params)
+                if updates:
+                    self._validate_update(subnet, updates)
+                    subnet = self.conn.network.update_subnet(subnet, **updates)
+                    changed = True
+            self.exit_json(changed=changed, subnet=subnet, id=subnet.id)
+        elif state == 'absent' and subnet is not None:
+            self.conn.network.delete_subnet(subnet)
+            changed = True
+        self.exit_json(changed=changed)
+
+
+def main():
+    module = SubnetModule()
+    module()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/releasenotes/notes/add-extra-keycloak-realm-options-a8b14740bd999ebb.yaml b/releasenotes/notes/add-extra-keycloak-realm-options-a8b14740bd999ebb.yaml
new file mode 100644
index 0000000..4a7a43b
--- /dev/null
+++ b/releasenotes/notes/add-extra-keycloak-realm-options-a8b14740bd999ebb.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - The Keystone role now supports additional parameters when creating the
+    Keycloak realm to allow for the configuration of options such as password
+    policy, brute force protection, and more.
diff --git a/releasenotes/notes/add-mfa-config-options-6f2d6811bca1a789.yaml b/releasenotes/notes/add-mfa-config-options-6f2d6811bca1a789.yaml
new file mode 100644
index 0000000..cb2d445
--- /dev/null
+++ b/releasenotes/notes/add-mfa-config-options-6f2d6811bca1a789.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - The Keystone role now supports configuring multi-factor authentication for
+    the users within the Atmosphere realm.
diff --git a/releasenotes/notes/allow-configuring-ingress-class-name-0c50f395d9a1b213.yaml b/releasenotes/notes/allow-configuring-ingress-class-name-0c50f395d9a1b213.yaml
new file mode 100644
index 0000000..23172db
--- /dev/null
+++ b/releasenotes/notes/allow-configuring-ingress-class-name-0c50f395d9a1b213.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    All roles that deploy ``Ingress`` resources as part of the deployment
+    process now support the ability to specify the class name to use for the
+    ``Ingress`` resource.  This is done by setting the
+    ``<role>_ingress_class_name`` variable to the desired class name.
diff --git a/releasenotes/notes/allow-using-default-cert-b28067c8a1525e1f.yaml b/releasenotes/notes/allow-using-default-cert-b28067c8a1525e1f.yaml
new file mode 100644
index 0000000..aac6ce0
--- /dev/null
+++ b/releasenotes/notes/allow-using-default-cert-b28067c8a1525e1f.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    It's now possible to use the default TLS certificates configured within the
+    ingress by using the ``ingress_use_default_tls_certificate`` variable which
+    will omit the ``tls`` section from any ``Ingress`` resources managed by
+    Atmosphere.
diff --git a/releasenotes/notes/bump-openstack-collection-382923f617548b01.yaml b/releasenotes/notes/bump-openstack-collection-382923f617548b01.yaml
new file mode 100644
index 0000000..60eb020
--- /dev/null
+++ b/releasenotes/notes/bump-openstack-collection-382923f617548b01.yaml
@@ -0,0 +1,4 @@
+---
+other:
+  - The Atmosphere collection now uses the new major version of the OpenStack
+    collection as a dependency.
diff --git a/releasenotes/notes/horizon-security-improvements-22b2535a85daab75.yaml b/releasenotes/notes/horizon-security-improvements-22b2535a85daab75.yaml
new file mode 100644
index 0000000..2b52c90
--- /dev/null
+++ b/releasenotes/notes/horizon-security-improvements-22b2535a85daab75.yaml
@@ -0,0 +1,8 @@
+---
+security:
+  - The Horizon service now runs as the non-privileged user `horizon` in the
+    container.
+  - The Horizon service ``ALLOWED_HOSTS`` setting is now configured to point
+    to the configured endpoints for the service.
+  - The CORS headers are now configured to only allow requests from the
+    configured endpoints for the service.
diff --git a/requirements.txt b/requirements.txt
index 9157d5b..038719c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
 ansible-core>=2.15.9
 jmespath>=1.0.1
-openstacksdk<0.99.0
+openstacksdk>1
 docker-image-py>=0.1.12
 rjsonnet>=0.5.2
 netaddr>=0.8.0
diff --git a/roles/barbican/defaults/main.yml b/roles/barbican/defaults/main.yml
index 8d2abc6..011e4d6 100644
--- a/roles/barbican/defaults/main.yml
+++ b/roles/barbican/defaults/main.yml
@@ -20,7 +20,11 @@
 barbican_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 barbican_helm_values: {}
 
+# Class name to use for the Ingress
+barbican_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 barbican_ingress_annotations: {}
+
 # Barbican key encryption key
 barbican_kek: "{{ undef(hint='You must specify a Barbican key encryption key') }}"
diff --git a/roles/barbican/tasks/main.yml b/roles/barbican/tasks/main.yml
index d7261dc..d4992b6 100644
--- a/roles/barbican/tasks/main.yml
+++ b/roles/barbican/tasks/main.yml
@@ -30,6 +30,7 @@
     openstack_helm_ingress_service_name: barbican-api
     openstack_helm_ingress_service_port: 9311
     openstack_helm_ingress_annotations: "{{ barbican_ingress_annotations }}"
+    openstack_helm_ingress_class_name: "{{ barbican_ingress_class_name }}"
 
 - name: Create creator role
   openstack.cloud.identity_role:
diff --git a/roles/cinder/defaults/main.yml b/roles/cinder/defaults/main.yml
index 9a2012f..51ac2dc 100644
--- a/roles/cinder/defaults/main.yml
+++ b/roles/cinder/defaults/main.yml
@@ -20,5 +20,8 @@
 cinder_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 cinder_helm_values: {}
 
+# Class name to use for the Ingress
+cinder_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 cinder_ingress_annotations: {}
diff --git a/roles/cinder/tasks/main.yml b/roles/cinder/tasks/main.yml
index aa7b213..cde4251 100644
--- a/roles/cinder/tasks/main.yml
+++ b/roles/cinder/tasks/main.yml
@@ -40,3 +40,4 @@
     openstack_helm_ingress_service_name: cinder-api
     openstack_helm_ingress_service_port: 8776
     openstack_helm_ingress_annotations: "{{ _cinder_ingress_annotations | combine(cinder_ingress_annotations) }}"
+    openstack_helm_ingress_class_name: "{{ cinder_ingress_class_name }}"
diff --git a/roles/designate/defaults/main.yml b/roles/designate/defaults/main.yml
index 7932b41..91bf556 100644
--- a/roles/designate/defaults/main.yml
+++ b/roles/designate/defaults/main.yml
@@ -20,6 +20,9 @@
 designate_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 designate_helm_values: {}
 
+# Class name to use for the Ingress
+designate_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 designate_ingress_annotations: {}
 
diff --git a/roles/designate/tasks/main.yml b/roles/designate/tasks/main.yml
index fe90969..e4c969d 100644
--- a/roles/designate/tasks/main.yml
+++ b/roles/designate/tasks/main.yml
@@ -34,3 +34,4 @@
     openstack_helm_ingress_service_name: designate-api
     openstack_helm_ingress_service_port: 9001
     openstack_helm_ingress_annotations: "{{ designate_ingress_annotations }}"
+    openstack_helm_ingress_class_name: "{{ designate_ingress_class_name }}"
diff --git a/roles/designate/vars/main.yml b/roles/designate/vars/main.yml
index e6bfe9b..1bf3970 100644
--- a/roles/designate/vars/main.yml
+++ b/roles/designate/vars/main.yml
@@ -26,7 +26,7 @@
         max_pool_size: 5
         pool_timeout: 30
       service:central:
-        managed_resource_tenant_id: "{{ _designate_project_info.openstack_projects[0].id }}"
+        managed_resource_tenant_id: "{{ _designate_project_info.projects[0].id }}"
     pools: "{{ designate_pools | to_yaml }}"
   pod:
     replicas:
diff --git a/roles/glance/defaults/main.yml b/roles/glance/defaults/main.yml
index d747588..62c026b 100644
--- a/roles/glance/defaults/main.yml
+++ b/roles/glance/defaults/main.yml
@@ -20,6 +20,9 @@
 glance_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 glance_helm_values: {}
 
+# Class name to use for the Ingress
+glance_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 glance_ingress_annotations: {}
 
diff --git a/roles/glance/tasks/main.yml b/roles/glance/tasks/main.yml
index 32cf42c..c36357f 100644
--- a/roles/glance/tasks/main.yml
+++ b/roles/glance/tasks/main.yml
@@ -30,6 +30,7 @@
     openstack_helm_ingress_service_name: glance-api
     openstack_helm_ingress_service_port: 9292
     openstack_helm_ingress_annotations: "{{ _glance_ingress_annotations | combine(glance_ingress_annotations) }}"
+    openstack_helm_ingress_class_name: "{{ glance_ingress_class_name }}"
 
 - name: Create images
   ansible.builtin.include_role:
diff --git a/roles/glance_image/tasks/main.yml b/roles/glance_image/tasks/main.yml
index a237dc0..1f0059a 100644
--- a/roles/glance_image/tasks/main.yml
+++ b/roles/glance_image/tasks/main.yml
@@ -26,7 +26,7 @@
 
 - name: Download image and upload into Glance
   run_once: true
-  when: _image_info.openstack_image == None
+  when: _image_info.images | length == 0
   block:
     - name: Generate temporary work directory
       ansible.builtin.tempfile:
diff --git a/roles/heat/defaults/main.yml b/roles/heat/defaults/main.yml
index 3ea9ded..6a7173d 100644
--- a/roles/heat/defaults/main.yml
+++ b/roles/heat/defaults/main.yml
@@ -20,6 +20,10 @@
 heat_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 heat_helm_values: {}
 
+# Class name to use for the Ingress
+heat_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
+# List of annotations to apply to the Ingress
 heat_ingress_annotations: {}
 
 # Encryption key for Heat to use for encrypting sensitive data
diff --git a/roles/heat/tasks/main.yml b/roles/heat/tasks/main.yml
index 96c301a..3a2790e 100644
--- a/roles/heat/tasks/main.yml
+++ b/roles/heat/tasks/main.yml
@@ -30,6 +30,7 @@
     openstack_helm_ingress_service_name: heat-api
     openstack_helm_ingress_service_port: 8004
     openstack_helm_ingress_annotations: "{{ _heat_ingress_annotations | combine(heat_ingress_annotations, recursive=True) }}"
+    openstack_helm_ingress_class_name: "{{ heat_ingress_class_name }}"
 
 - name: Create Ingress
   ansible.builtin.include_role:
@@ -39,3 +40,4 @@
     openstack_helm_ingress_service_name: heat-cfn
     openstack_helm_ingress_service_port: 8000
     openstack_helm_ingress_annotations: "{{ _heat_ingress_annotations | combine(heat_ingress_annotations, recursive=True) }}"
+    openstack_helm_ingress_class_name: "{{ heat_ingress_class_name }}"
diff --git a/roles/horizon/defaults/main.yml b/roles/horizon/defaults/main.yml
index 116a87a..441fe4d 100644
--- a/roles/horizon/defaults/main.yml
+++ b/roles/horizon/defaults/main.yml
@@ -20,5 +20,8 @@
 horizon_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 horizon_helm_values: {}
 
+# Class name to use for the Ingress
+horizon_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 horizon_ingress_annotations: {}
diff --git a/roles/horizon/tasks/main.yml b/roles/horizon/tasks/main.yml
index 3cd1653..5c9e4e0 100644
--- a/roles/horizon/tasks/main.yml
+++ b/roles/horizon/tasks/main.yml
@@ -30,3 +30,4 @@
     openstack_helm_ingress_service_name: horizon-int
     openstack_helm_ingress_service_port: 80
     openstack_helm_ingress_annotations: "{{ _horizon_ingress_annotations | combine(horizon_ingress_annotations) }}"
+    openstack_helm_ingress_class_name: "{{ horizon_ingress_class_name }}"
diff --git a/roles/horizon/vars/main.yml b/roles/horizon/vars/main.yml
index 3e4f541..e0499bb 100644
--- a/roles/horizon/vars/main.yml
+++ b/roles/horizon/vars/main.yml
@@ -17,6 +17,16 @@
   images:
     tags: "{{ atmosphere_images | vexxhost.atmosphere.openstack_helm_image_tags('horizon') }}"
   pod:
+    security_context:
+      horizon:
+        pod:
+          fsGroup: 42424
+      db_sync:
+        pod:
+          fsGroup: 42424
+      tests:
+        pod:
+          fsGroup: 42424
     replicas:
       server: 3
   conf:
@@ -24,6 +34,8 @@
       local_settings:
         config:
           disallow_iframe_embed: "True"
+          allowed_hosts:
+            - "{{ openstack_helm_endpoints_horizon_api_host }}"
           secure_proxy_ssl_header: "True"
           horizon_images_upload_mode: direct
           openstack_enable_password_retrieve: "True"
@@ -55,3 +67,5 @@
 _horizon_ingress_annotations:
   nginx.ingress.kubernetes.io/proxy-body-size: "5000m"
   nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
+  nginx.ingress.kubernetes.io/enable-cors: "true"
+  nginx.ingress.kubernetes.io/cors-allow-origin: "{{ openstack_helm_endpoints_horizon_api_host }}"
diff --git a/roles/ingress/defaults/main.yml b/roles/ingress/defaults/main.yml
index 05a2cc3..82ea1e0 100644
--- a/roles/ingress/defaults/main.yml
+++ b/roles/ingress/defaults/main.yml
@@ -38,3 +38,6 @@
 
 # List of annotations to apply to all Ingress resources as default
 ingress_default_annotations: "{{ ingress_global_annotations | default(atmosphere_ingress_annotations) }}"
+
+# Use default TLS certificate
+ingress_use_default_tls_certificate: false
diff --git a/roles/ingress/templates/ingress.yml.j2 b/roles/ingress/templates/ingress.yml.j2
index 04135bc..6da2788 100644
--- a/roles/ingress/templates/ingress.yml.j2
+++ b/roles/ingress/templates/ingress.yml.j2
@@ -21,7 +21,9 @@
                 name: {{ ingress_service_name }}

                 port:

                   number: {{ ingress_service_port }}

+{% if not ingress_use_default_tls_certificate %}

   tls:

     - secretName: {{ ingress_secret_name | default(ingress_service_name ~ '-certs') }}

       hosts:

         - {{ ingress_host }}

+{% endif %}

diff --git a/roles/ironic/defaults/main.yml b/roles/ironic/defaults/main.yml
index 4df68da..3b0ef6d 100644
--- a/roles/ironic/defaults/main.yml
+++ b/roles/ironic/defaults/main.yml
@@ -20,6 +20,9 @@
 ironic_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 ironic_helm_values: {}
 
+# Class name to use for the Ingress
+ironic_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 ironic_ingress_annotations: {}
 
diff --git a/roles/ironic/tasks/main.yml b/roles/ironic/tasks/main.yml
index bd975f1..a70ef8b 100644
--- a/roles/ironic/tasks/main.yml
+++ b/roles/ironic/tasks/main.yml
@@ -94,3 +94,4 @@
     openstack_helm_ingress_service_name: ironic-api
     openstack_helm_ingress_service_port: 6385
     openstack_helm_ingress_annotations: "{{ ironic_ingress_annotations }}"
+    openstack_helm_ingress_class_name: "{{ ironic_ingress_class_name }}"
diff --git a/roles/ironic/tasks/network/create.yml b/roles/ironic/tasks/network/create.yml
index 8032983..a459b74 100644
--- a/roles/ironic/tasks/network/create.yml
+++ b/roles/ironic/tasks/network/create.yml
@@ -25,7 +25,7 @@
 
 - name: Create bare metal network subnet
   run_once: true
-  openstack.cloud.subnet:
+  vexxhost.atmosphere.subnet:
     cloud: atmosphere
     # Subnet settings
     network_name: "{{ ironic_bare_metal_subnet_name }}"
diff --git a/roles/ironic/tasks/network/lookup.yml b/roles/ironic/tasks/network/lookup.yml
index 8838ac9..95a8f71 100644
--- a/roles/ironic/tasks/network/lookup.yml
+++ b/roles/ironic/tasks/network/lookup.yml
@@ -23,11 +23,11 @@
 - name: Assert that we match a single network only
   ansible.builtin.assert:
     that:
-      - ironic_bare_metal_networks_info.openstack_networks | length == 1
-    fail_msg: "Expected exactly one network, but found {{ ironic_bare_metal_networks_info.openstack_networks | length }}"
+      - ironic_bare_metal_networks_info.networks | length == 1
+    fail_msg: "Expected exactly one network, but found {{ ironic_bare_metal_networks_info.networks | length }}"
     success_msg: "Successfully matched a single network"
   run_once: true
 
 - name: Set fact with bare metal network information
   ansible.builtin.set_fact:
-    ironic_bare_metal_network: "{{ ironic_bare_metal_networks_info.openstack_networks[0] }}"
+    ironic_bare_metal_network: "{{ ironic_bare_metal_networks_info.networks[0] }}"
diff --git a/roles/ironic/vars/main.yml b/roles/ironic/vars/main.yml
index 51d32b4..e863699 100644
--- a/roles/ironic/vars/main.yml
+++ b/roles/ironic/vars/main.yml
@@ -50,8 +50,8 @@
         rbac_service_role_elevated_access: true
       conductor:
         clean_step_priority_override: deploy.erase_devices_express:5
-        deploy_kernel: "{{ ironic_python_agent_deploy_kernel.openstack_image.id }}"
-        deploy_ramdisk: "{{ ironic_python_agent_deploy_ramdisk.openstack_image.id }}"
+        deploy_kernel: "{{ ironic_python_agent_deploy_kernel.images.0.id }}"
+        deploy_ramdisk: "{{ ironic_python_agent_deploy_ramdisk.images.0.id }}"
       deploy:
         erase_devices_priority: 0
         erase_devices_metadata_priority: 0
diff --git a/roles/keystone/defaults/main.yml b/roles/keystone/defaults/main.yml
index f71c59f..67031c5 100644
--- a/roles/keystone/defaults/main.yml
+++ b/roles/keystone/defaults/main.yml
@@ -20,6 +20,9 @@
 keystone_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 keystone_helm_values: {}
 
+# Class name to use for the Ingress
+keystone_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 keystone_ingress_annotations: {}
 
@@ -34,6 +37,16 @@
 keystone_keycloak_admin_password: "{{ keycloak_admin_password }}"
 keystone_keycloak_realm: atmosphere
 keystone_keycloak_realm_name: Atmosphere
+# keystone_keycloak_realm_default_password_policy:
+# keystone_keycloak_realm_default_brute_force_protected:
+# keystone_keycloak_realm_default_brute_force_failure_factor:
+# keystone_keycloak_realm_default_brute_force_wait_increment_seconds:
+# keystone_keycloak_realm_default_brute_force_max_failure_wait_seconds:
+# keystone_keycloak_realm_default_brute_force_max_delta_time_seconds:
+# keystone_keycloak_realm_default_minimum_quick_login_wait_seconds:
+# keystone_keycloak_realm_default_quick_login_check_milli_seconds:
+# keystone_keycloak_realm_default_totp_default_action:
+
 keystone_keycloak_client_id: keystone
 # keystone_keycloak_client_secret:
 keystone_keycloak_scopes: "openid email profile"
diff --git a/roles/keystone/tasks/main.yml b/roles/keystone/tasks/main.yml
index a35a9ce..e5abcb0 100644
--- a/roles/keystone/tasks/main.yml
+++ b/roles/keystone/tasks/main.yml
@@ -29,6 +29,36 @@
     realm: "{{ item.keycloak_realm }}"
     display_name: "{{ item.label }}"
     enabled: true
+    password_policy: "{{ item.keycloak_password_policy | default(keystone_keycloak_realm_default_password_policy | default(omit)) }}"
+    brute_force_protected: "{{ item.keycloak_brute_force_protected | default(keystone_keycloak_realm_default_brute_force_protected | default(omit)) }}"
+    failure_factor: "{{ item.keycloak_brute_force_failure_factor | default(keystone_keycloak_realm_default_brute_force_failure_factor | default(omit)) }}"
+    wait_increment_seconds: "{{ item.keycloak_brute_force_wait_increment_seconds | default(keystone_keycloak_realm_default_brute_force_wait_increment_seconds | default(omit)) }}"
+    max_failure_wait_seconds: "{{ item.keycloak_brute_force_max_failure_wait_seconds | default(keystone_keycloak_realm_default_brute_force_max_failure_wait_seconds | default(omit)) }}"
+    max_delta_time_seconds: "{{ item.keycloak_brute_force_max_delta_time_seconds | default(keystone_keycloak_realm_default_brute_force_max_delta_time_seconds | default(omit)) }}"
+    minimum_quick_login_wait_seconds: "{{ item.keycloak_minimum_quick_login_wait_seconds | default(keystone_keycloak_realm_default_minimum_quick_login_wait_seconds | default(omit)) }}"
+    quick_login_check_milli_seconds: "{{ item.keycloak_quick_login_check_milli_seconds | default(keystone_keycloak_realm_default_quick_login_check_milli_seconds | default(omit)) }}"
+  loop: "{{ keystone_domains }}"
+  loop_control:
+    label: "{{ item.name }}"
+
+- name: Setup Keycloak Authentication Required Actions (MFA)
+  community.general.keycloak_authentication_required_actions:
+    # Keycloak settings
+    auth_keycloak_url: "{{ item.keycloak_server_url }}"
+    auth_realm: "{{ item.keycloak_user_realm_name }}"
+    auth_client_id: "{{ item.keycloak_admin_client_id }}"
+    auth_username: "{{ item.keycloak_admin_user }}"
+    auth_password: "{{ item.keycloak_admin_password }}"
+    validate_certs: "{{ cluster_issuer_type != 'self-signed' }}"
+    # Realm settings
+    realm: "{{ item.name }}"
+    required_actions:
+      - alias: "CONFIGURE_TOTP"
+        name: "Configure OTP"
+        providerId: "CONFIGURE_TOTP"
+        defaultAction: "{{ item.keycloak_totp_default_action | default(keystone_keycloak_realm_default_totp_default_action | default(omit)) }}"
+        enabled: true
+    state: present
   loop: "{{ keystone_domains }}"
   loop_control:
     label: "{{ item.name }}"
@@ -78,6 +108,7 @@
     openstack_helm_ingress_service_name: keystone-api
     openstack_helm_ingress_service_port: 5000
     openstack_helm_ingress_annotations: "{{ keystone_ingress_annotations }}"
+    openstack_helm_ingress_class_name: "{{ keystone_ingress_class_name }}"
 
 - name: Validate if ingress is reachable
   ansible.builtin.uri:
@@ -119,6 +150,7 @@
   vexxhost.atmosphere.federation_idp:
     name: "{{ item.domain.name }}"
     domain_id: "{{ item.domain.id }}"
+    is_enabled: true
     remote_ids:
       - "{{ item.item | vexxhost.atmosphere.issuer_from_domain }}"
   loop: "{{ keystone_domains_result.results }}"
diff --git a/roles/magnum/defaults/main.yml b/roles/magnum/defaults/main.yml
index 740fbb0..84355f7 100644
--- a/roles/magnum/defaults/main.yml
+++ b/roles/magnum/defaults/main.yml
@@ -20,6 +20,10 @@
 magnum_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 magnum_helm_values: {}
 
+# Class name to use for the Ingress
+magnum_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+magnum_registry_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 magnum_ingress_annotations: {}
 magnum_registry_ingress_annotations: {}
diff --git a/roles/magnum/tasks/main.yml b/roles/magnum/tasks/main.yml
index fc9b7a0..4805cbc 100644
--- a/roles/magnum/tasks/main.yml
+++ b/roles/magnum/tasks/main.yml
@@ -128,6 +128,7 @@
     openstack_helm_ingress_service_name: magnum-api
     openstack_helm_ingress_service_port: 9511
     openstack_helm_ingress_annotations: "{{ magnum_ingress_annotations }}"
+    openstack_helm_ingress_class_name: "{{ magnum_ingress_class_name }}"
 
 - name: Deploy magnum registry
   run_once: true
@@ -204,6 +205,7 @@
     openstack_helm_ingress_service_name: magnum-registry
     openstack_helm_ingress_service_port: 5000
     openstack_helm_ingress_annotations: "{{ _magnum_registry_ingress_annotations | combine(magnum_registry_ingress_annotations) }}"
+    openstack_helm_ingress_class_name: "{{ magnum_registry_ingress_class_name }}"
 
 - name: Upload images
   ansible.builtin.include_role:
diff --git a/roles/manila/defaults/main.yml b/roles/manila/defaults/main.yml
index f5b791f..ce4b014 100644
--- a/roles/manila/defaults/main.yml
+++ b/roles/manila/defaults/main.yml
@@ -20,6 +20,9 @@
 manila_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 manila_helm_values: {}
 
+# Class name to use for the Ingress
+manila_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 manila_ingress_annotations: {}
 
diff --git a/roles/manila/tasks/generate_resources.yml b/roles/manila/tasks/generate_resources.yml
index 9bfa6c8..08c5278 100644
--- a/roles/manila/tasks/generate_resources.yml
+++ b/roles/manila/tasks/generate_resources.yml
@@ -43,7 +43,7 @@
 - name: Create generic share driver security group tcp rules
   openstack.cloud.security_group_rule:
     cloud: atmosphere
-    security_group: "{{ _manila_service_security_group.id }}"
+    security_group: "{{ _manila_service_security_group.security_group.id }}"
     direction: ingress
     ethertype: IPv4
     protocol: tcp
@@ -58,7 +58,7 @@
 - name: Create generic share driver security group icmp rules
   openstack.cloud.security_group_rule:
     cloud: atmosphere
-    security_group: "{{ _manila_service_security_group.id }}"
+    security_group: "{{ _manila_service_security_group.security_group.id }}"
     direction: ingress
     ethertype: IPv4
     protocol: icmp
diff --git a/roles/manila/tasks/main.yml b/roles/manila/tasks/main.yml
index 8ae97e8..540ce34 100644
--- a/roles/manila/tasks/main.yml
+++ b/roles/manila/tasks/main.yml
@@ -38,6 +38,7 @@
     openstack_helm_ingress_service_name: manila-api
     openstack_helm_ingress_service_port: 8786
     openstack_helm_ingress_annotations: "{{ manila_ingress_annotations }}"
+    openstack_helm_ingress_class_name: "{{ manila_ingress_class_name }}"
 
 - name: Update service tenant quotas
   openstack.cloud.quota:
diff --git a/roles/manila/vars/main.yml b/roles/manila/vars/main.yml
index ce2175d..3c86604 100644
--- a/roles/manila/vars/main.yml
+++ b/roles/manila/vars/main.yml
@@ -58,7 +58,7 @@
         path_to_private_key: /etc/manila/ssh-keys/id_rsa
         path_to_public_key: /etc/manila/ssh-keys/id_rsa.pub
         service_image_name: "{{ manila_image_name }}"
-        service_instance_flavor_id: "{{ _manila_flavor.id }}"
+        service_instance_flavor_id: "{{ _manila_flavor.flavor.id }}"
         service_instance_security_group: manila-service-security-group
       oslo_messaging_notifications:
         driver: noop
diff --git a/roles/neutron/defaults/main.yml b/roles/neutron/defaults/main.yml
index 04d48ac..b8579eb 100644
--- a/roles/neutron/defaults/main.yml
+++ b/roles/neutron/defaults/main.yml
@@ -23,6 +23,9 @@
 # List of networks to provision inside OpenStack
 neutron_networks: []
 
+# Class name to use for the Ingress
+neutron_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 neutron_ingress_annotations: {}
 
diff --git a/roles/neutron/tasks/main.yml b/roles/neutron/tasks/main.yml
index 874ed3c..47ea874 100644
--- a/roles/neutron/tasks/main.yml
+++ b/roles/neutron/tasks/main.yml
@@ -49,6 +49,7 @@
     openstack_helm_ingress_service_name: neutron-server
     openstack_helm_ingress_service_port: 9696
     openstack_helm_ingress_annotations: "{{ neutron_ingress_annotations }}"
+    openstack_helm_ingress_class_name: "{{ neutron_ingress_class_name }}"
 
 - name: Create networks
   when: neutron_networks | length > 0
@@ -87,7 +88,7 @@
       until: _result is not failed
 
     - name: Create subnets
-      openstack.cloud.subnet:
+      vexxhost.atmosphere.subnet:
         cloud: atmosphere
         # Subnet settings
         network_name: "{{ item.0.name }}"
diff --git a/roles/nova/defaults/main.yml b/roles/nova/defaults/main.yml
index e02faac..a951a5f 100644
--- a/roles/nova/defaults/main.yml
+++ b/roles/nova/defaults/main.yml
@@ -26,6 +26,10 @@
 # List of flavors to provision inside Nova
 nova_flavors: []
 
+# Class name to use for the Ingress
+nova_api_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+nova_novnc_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 nova_api_ingress_annotations: {}
 nova_novnc_ingress_annotations: {}
diff --git a/roles/nova/tasks/main.yml b/roles/nova/tasks/main.yml
index ed10aa5..a86aa0c 100644
--- a/roles/nova/tasks/main.yml
+++ b/roles/nova/tasks/main.yml
@@ -49,6 +49,7 @@
     openstack_helm_ingress_service_name: nova-api
     openstack_helm_ingress_service_port: 8774
     openstack_helm_ingress_annotations: "{{ nova_api_ingress_annotations }}"
+    openstack_helm_ingress_class_name: "{{ nova_api_ingress_class_name }}"
 
 - name: Create Ingress
   ansible.builtin.include_role:
@@ -58,6 +59,7 @@
     openstack_helm_ingress_service_name: nova-novncproxy
     openstack_helm_ingress_service_port: 6080
     openstack_helm_ingress_annotations: "{{ _nova_novnc_ingress_annotations | combine(nova_novnc_ingress_annotations) }}"
+    openstack_helm_ingress_class_name: "{{ nova_novnc_ingress_class_name }}"
 
 - name: Create flavors
   when: nova_flavors | length > 0
diff --git a/roles/octavia/defaults/main.yml b/roles/octavia/defaults/main.yml
index 19b31f8..3f6427b 100644
--- a/roles/octavia/defaults/main.yml
+++ b/roles/octavia/defaults/main.yml
@@ -20,6 +20,9 @@
 octavia_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 octavia_helm_values: {}
 
+# Class name to use for the Ingress
+octavia_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 octavia_ingress_annotations: {}
 
diff --git a/roles/octavia/tasks/generate_resources.yml b/roles/octavia/tasks/generate_resources.yml
index 5960aa1..e582be4 100644
--- a/roles/octavia/tasks/generate_resources.yml
+++ b/roles/octavia/tasks/generate_resources.yml
@@ -20,13 +20,13 @@
   register: _octavia_management_network
 
 - name: Create management subnet
-  openstack.cloud.subnet:
+  vexxhost.atmosphere.subnet:
     cloud: atmosphere
     # Subnet settings
     network_name: "{{ octavia_management_network_name }}"
     name: "{{ octavia_management_subnet_name }}"
     cidr: "{{ octavia_management_subnet_cidr }}"
-    no_gateway_ip: true
+    disable_gateway_ip: true
 
 - name: Create health manager security group
   openstack.cloud.security_group:
@@ -37,7 +37,7 @@
 - name: Create health manager security group rules
   openstack.cloud.security_group_rule:
     cloud: atmosphere
-    security_group: "{{ _octavia_health_manager_sg.id }}"
+    security_group: "{{ _octavia_health_manager_sg.security_group.id }}"
     direction: ingress
     ethertype: IPv4
     protocol: "{{ item.protocol }}"
@@ -67,7 +67,7 @@
         if hostvars[item]['octavia_health_manager_ip'] is defined else omit
       }}
     security_groups:
-      - "{{ _octavia_health_manager_sg.id }}"
+      - "{{ _octavia_health_manager_sg.security_group.id }}"
   loop: "{{ groups['controllers'] }}"
   loop_control:
     index_var: _octavia_health_manager_port_index
@@ -110,10 +110,10 @@
 
 - name: Set controller_ip_port_list
   ansible.builtin.set_fact:
-    _octavia_controller_ip_port_list: "{{ (_octavia_controller_ip_port_list | d([]) + [item.openstack_ports[0].fixed_ips[0].ip_address + ':5555']) | unique }}"
+    _octavia_controller_ip_port_list: "{{ (_octavia_controller_ip_port_list | d([]) + [item.ports[0].fixed_ips[0].ip_address + ':5555']) | unique }}"
   loop: "{{ _octavia_health_manager_ports.results }}"
   loop_control:
-    label: "{{ item.openstack_ports[0].name }}"
+    label: "{{ item.ports[0].name }}"
 
 - name: Create amphora security group
   openstack.cloud.security_group:
@@ -124,13 +124,13 @@
 - name: Create amphora security group rules
   openstack.cloud.security_group_rule:
     cloud: atmosphere
-    security_group: "{{ _octavia_amphora_sg.id }}"
+    security_group: "{{ _octavia_amphora_sg.security_group.id }}"
     direction: ingress
     ethertype: IPv4
     protocol: tcp
     port_range_min: "{{ item.0 }}"
     port_range_max: "{{ item.0 }}"
-    remote_ip_prefix: "{{ item.1.openstack_ports[0].fixed_ips[0].ip_address }}/32"
+    remote_ip_prefix: "{{ item.1.ports[0].fixed_ips[0].ip_address }}/32"
   with_nested:
     - [22, 9443]
     - "{{ _octavia_health_manager_ports.results }}"
diff --git a/roles/octavia/tasks/main.yml b/roles/octavia/tasks/main.yml
index b2c94fd..4432856 100644
--- a/roles/octavia/tasks/main.yml
+++ b/roles/octavia/tasks/main.yml
@@ -138,3 +138,4 @@
     openstack_helm_ingress_service_name: octavia-api
     openstack_helm_ingress_service_port: 9876
     openstack_helm_ingress_annotations: "{{ octavia_ingress_annotations }}"
+    openstack_helm_ingress_class_name: "{{ octavia_ingress_class_name }}"
diff --git a/roles/octavia/vars/main.yml b/roles/octavia/vars/main.yml
index 8423aba..1f78f70 100644
--- a/roles/octavia/vars/main.yml
+++ b/roles/octavia/vars/main.yml
@@ -98,10 +98,10 @@
         endpoint_type: internalURL
       controller_worker:
         amp_boot_network_list: "{{ _octavia_management_network.id }}"
-        amp_flavor_id: "{{ _octavia_amphora_flavor.id }}"
-        amp_image_owner_id: "{{ _octavia_amphora_image.openstack_image.owner }}"
-        amp_secgroup_list: "{{ _octavia_amphora_sg.id }}"
-        amp_ssh_key_name: "{{ octavia_amphora_ssh_keypair.key.name }}"
+        amp_flavor_id: "{{ _octavia_amphora_flavor.flavor.id }}"
+        amp_image_owner_id: "{{ _octavia_amphora_image.images.0.owner }}"
+        amp_secgroup_list: "{{ _octavia_amphora_sg.security_group.id }}"
+        amp_ssh_key_name: "{{ octavia_amphora_ssh_keypair.keypair.name }}"
         client_ca: /etc/octavia/certs/client/ca.crt
         volume_driver: volume_cinder_driver
         workers: 4
diff --git a/roles/openstack_helm_ingress/defaults/main.yml b/roles/openstack_helm_ingress/defaults/main.yml
index f3c0133..3e530ab 100644
--- a/roles/openstack_helm_ingress/defaults/main.yml
+++ b/roles/openstack_helm_ingress/defaults/main.yml
@@ -21,3 +21,6 @@
 # this is useful when you want to use a single certificate for all services and
 # use DNS-01 challenge to issue the certificate.
 # openstack_helm_ingress_wildcard_domain: cloud.atmosphere.dev
+
+# Ingress class to use for the Ingress
+openstack_helm_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
diff --git a/roles/openstack_helm_ingress/tasks/main.yml b/roles/openstack_helm_ingress/tasks/main.yml
index bf1d770..ee9e0f7 100644
--- a/roles/openstack_helm_ingress/tasks/main.yml
+++ b/roles/openstack_helm_ingress/tasks/main.yml
@@ -56,3 +56,4 @@
     ingress_service_name: "{{ openstack_helm_ingress_service_name }}"
     ingress_service_port: "{{ openstack_helm_ingress_service_port }}"
     ingress_secret_name: "{{ openstack_helm_ingress_secret_name | default(openstack_helm_ingress_service_name ~ '-certs') }}"
+    ingress_class_name: "{{ openstack_helm_ingress_class_name }}"
diff --git a/roles/openstacksdk/defaults/main.yml b/roles/openstacksdk/defaults/main.yml
index 27dc31a..96db768 100644
--- a/roles/openstacksdk/defaults/main.yml
+++ b/roles/openstacksdk/defaults/main.yml
@@ -12,4 +12,4 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-openstacksdk_version: "0.61.0"
+# openstacksdk_version:
diff --git a/roles/openstacksdk/tasks/main.yml b/roles/openstacksdk/tasks/main.yml
index 1a4c3b4..5f082a1 100644
--- a/roles/openstacksdk/tasks/main.yml
+++ b/roles/openstacksdk/tasks/main.yml
@@ -15,7 +15,7 @@
 - name: Install openstacksdk
   ansible.builtin.pip:
     name: openstacksdk
-    version: "{{ openstacksdk_version }}"
+    version: "{{ openstacksdk_version | default(omit) }}"
 
 - name: Create openstack config directory
   become: true
diff --git a/roles/placement/defaults/main.yml b/roles/placement/defaults/main.yml
index bc72b55..e29baa5 100644
--- a/roles/placement/defaults/main.yml
+++ b/roles/placement/defaults/main.yml
@@ -20,5 +20,8 @@
 placement_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 placement_helm_values: {}
 
+# Class name to use for the Ingress
+placement_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 placement_ingress_annotations: {}
diff --git a/roles/placement/tasks/main.yml b/roles/placement/tasks/main.yml
index cebee35..cb33025 100644
--- a/roles/placement/tasks/main.yml
+++ b/roles/placement/tasks/main.yml
@@ -30,3 +30,4 @@
     openstack_helm_ingress_service_name: placement-api
     openstack_helm_ingress_service_port: 8778
     openstack_helm_ingress_annotations: "{{ placement_ingress_annotations }}"
+    openstack_helm_ingress_class_name: "{{ placement_ingress_class_name }}"
diff --git a/roles/rook_ceph_cluster/defaults/main.yml b/roles/rook_ceph_cluster/defaults/main.yml
index 4c2545b..d5b1c2d 100644
--- a/roles/rook_ceph_cluster/defaults/main.yml
+++ b/roles/rook_ceph_cluster/defaults/main.yml
@@ -22,6 +22,9 @@
 rook_ceph_cluster_helm_kubeconfig: "{{ kubeconfig_path | default('/etc/kubernetes/admin.conf') }}"
 rook_ceph_cluster_helm_values: {}
 
+# Class name to use for the Ingress
+rook_ceph_cluster_ingress_class_name: "{{ atmosphere_ingress_class_name }}"
+
 # List of annotations to apply to the Ingress
 rook_ceph_cluster_radosgw_annotations: {}
 
diff --git a/roles/rook_ceph_cluster/tasks/main.yml b/roles/rook_ceph_cluster/tasks/main.yml
index 92f3842..a957efb 100644
--- a/roles/rook_ceph_cluster/tasks/main.yml
+++ b/roles/rook_ceph_cluster/tasks/main.yml
@@ -96,13 +96,21 @@
     password: "{{ openstack_helm_endpoints.identity.auth.rgw.password }}"
     domain: service
 
+# NOTE(mnaser): https://storyboard.openstack.org/#!/story/2010579
 - name: Grant access to "service" project
-  openstack.cloud.role_assignment:
-    cloud: atmosphere
-    domain: service
-    user: "{{ openstack_helm_endpoints.identity.auth.rgw.username }}"
-    project: service
-    role: admin
+  changed_when: false
+  ansible.builtin.shell: |
+    set -o posix
+    source /etc/profile.d/atmosphere.sh
+    openstack role add \
+      --user-domain service \
+      --project service \
+      --user {{ openstack_helm_endpoints.identity.auth.rgw.username }} \
+      admin
+  args:
+    executable: /bin/bash
+  environment:
+    OS_CLOUD: atmosphere
 
 - name: Create OpenStack service
   openstack.cloud.catalog_service:
@@ -132,3 +140,4 @@
     openstack_helm_ingress_service_name: rook-ceph-rgw-{{ rook_ceph_cluster_name }}
     openstack_helm_ingress_service_port: 80
     openstack_helm_ingress_annotations: "{{ _rook_ceph_cluster_radosgw_annotations | combine(rook_ceph_cluster_radosgw_annotations, recursive=True) }}"
+    openstack_helm_ingress_class_name: "{{ rook_ceph_cluster_ingress_class_name }}"
diff --git a/roles/tempest/tasks/main.yml b/roles/tempest/tasks/main.yml
index 8ea8e8f..3440b2d 100644
--- a/roles/tempest/tasks/main.yml
+++ b/roles/tempest/tasks/main.yml
@@ -34,10 +34,10 @@
         conf:
           tempest:
             compute:
-              image_ref: "{{ _tempest_test_image.openstack_image.id }}"
+              image_ref: "{{ _tempest_test_image.images.0.id }}"
       when:
         - tempest_helm_values.conf.tempest.compute.image_ref is not defined
-        - _tempest_test_image.openstack_image.id is defined
+        - _tempest_test_image.images | length > 0
 
     - name: Get test flavor object
       openstack.cloud.compute_flavor_info:
@@ -53,10 +53,10 @@
         conf:
           tempest:
             compute:
-              flavor_ref: "{{ _tempest_test_flavor.openstack_flavors[0].id }}"
+              flavor_ref: "{{ _tempest_test_flavor.flavors[0].id }}"
       when:
         - tempest_helm_values.conf.tempest.compute.flavor_ref is not defined
-        - _tempest_test_flavor.openstack_flavors[0].id is defined
+        - _tempest_test_flavor.flavors[0].id is defined
 
     - name: Get test network object
       openstack.cloud.networks_info:
@@ -72,10 +72,10 @@
         conf:
           tempest:
             network:
-              public_network_id: "{{ _tempest_test_network.openstack_networks[0].id }}"
+              public_network_id: "{{ _tempest_test_network.networks[0].id }}"
       when:
         - tempest_helm_values.conf.tempest.network.public_network_id is not defined
-        - _tempest_test_network.openstack_networks[0].id is defined
+        - _tempest_test_network.networks[0].id is defined
 
 - name: Deploy Helm chart
   failed_when: false
diff --git a/roles/tempest/vars/main.yml b/roles/tempest/vars/main.yml
index ea508f9..5c92ad6 100644
--- a/roles/tempest/vars/main.yml
+++ b/roles/tempest/vars/main.yml
@@ -33,7 +33,8 @@
         endpoint_type: internal
         fixed_network_name: public
       dashboard:
-        dashboard_url: "http://horizon-int.openstack.svc.cluster.local"
+        dashboard_url: "https://{{ openstack_helm_endpoints_horizon_api_host }}"
+        disable_ssl_certificate_validation: "{{ cluster_issuer_type == 'self-signed' }}"
       identity:
         v3_endpoint_type: internal
       image: