chore: pin openstack deps (#391)

* chore: pin openstack deps

This commit adds infrastructure to pin OpenStack images which are
tagged by release in order to ensure the exact image is used in
a specific Atmosphere release.

It also adds an Earthfile which can automatically execute the
pinning.

* fix: allow for missing groups
diff --git a/.ansible-lint b/.ansible-lint
index b46745d..65e8cbd 100644
--- a/.ansible-lint
+++ b/.ansible-lint
@@ -5,6 +5,7 @@
   - charts
   - molecule
   - playbooks
+  - roles/defaults/defaults/main.ym
   - roles/kube_prometheus_stack/files/jsonnet
 
 warn_list:
diff --git a/Earthfile b/Earthfile
new file mode 100644
index 0000000..2f8a461
--- /dev/null
+++ b/Earthfile
@@ -0,0 +1,31 @@
+VERSION 0.7
+FROM python:3.10
+
+poetry:
+  RUN pip3 install poetry
+
+deps:
+  FROM +poetry
+  COPY pyproject.toml poetry.lock ./
+  RUN poetry export -f requirements.txt --without-hashes > requirements.txt
+  RUN pip wheel -r requirements.txt --wheel-dir=/wheels
+  SAVE ARTIFACT requirements.txt
+  SAVE ARTIFACT /wheels
+
+build:
+  FROM +deps
+  RUN python3 -m venv /venv
+  ENV PATH=/venv/bin:$PATH
+  RUN pip install -r requirements.txt
+  SAVE ARTIFACT /venv
+
+docker:
+  COPY +build/venv /venv
+  ENV PATH=/venv/bin:$PATH
+
+pin-images:
+  FROM +docker
+  COPY roles/defaults/defaults/main.yml /defaults.yml
+  COPY build/pin-images.py /usr/local/bin/pin-images
+  RUN /usr/local/bin/pin-images /defaults.yml /pinned.yml
+  SAVE ARTIFACT /pinned.yml AS LOCAL roles/defaults/defaults/main.yml
diff --git a/build/pin-images.py b/build/pin-images.py
new file mode 100755
index 0000000..fd9b385
--- /dev/null
+++ b/build/pin-images.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+
+import argparse
+import functools
+
+from docker_image import reference
+from oslo_config import cfg
+from oslo_log import log as logging
+from ruyaml import YAML
+import requests
+
+LOG = logging.getLogger(__name__)
+CONF = cfg.CONF
+
+
+@functools.cache
+def get_pinned_image(image_src):
+    image_ref = reference.Reference.parse(image_src)
+
+    if image_ref.domain() == "quay.io":
+        r = requests.get(
+            f"https://quay.io/api/v1/repository/{image_ref.path()}/tag/",
+            params={"specificTag": image_ref["tag"]},
+        )
+        r.raise_for_status()
+        digest = r.json()["tags"][0]["manifest_digest"]
+
+    return f"{image_ref.domain()}/{image_ref.path()}@{digest}"
+
+
+def main():
+    logging.register_options(CONF)
+    logging.setup(CONF, "atmosphere-bump-images")
+
+    parser = argparse.ArgumentParser("bump-images")
+    parser.add_argument(
+        "src", help="Path for default values file", type=argparse.FileType("r")
+    )
+    parser.add_argument("dst", help="Path for output file", type=argparse.FileType("w"))
+
+    args = parser.parse_args()
+
+    yaml = YAML(typ="rt")
+    data = yaml.load(args.src)
+
+    for image in data["atmosphere_images"].ca.items:
+        token = data["atmosphere_images"].ca.get(image, 2).value
+        if not token.startswith("# image-source: "):
+            LOG.info("Skipping image %s", image)
+            continue
+
+        image_src = token.replace("# image-source: ", "").strip()
+        pinned_image = get_pinned_image(image_src)
+
+        LOG.info("Pinning image %s from %s to %s", image, image_src, pinned_image)
+        data["atmosphere_images"][image] = pinned_image
+
+    yaml.dump(data, args.dst)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/galaxy.yml b/galaxy.yml
index 62a71eb..ac777b8 100644
--- a/galaxy.yml
+++ b/galaxy.yml
@@ -32,6 +32,7 @@
 issues: https://github.com/vexxhost/atmosphere/issues
 build_ignore:
   - .github
+  - build
   - ci
   - cmd
   - docs
diff --git a/poetry.lock b/poetry.lock
index 4e0bb63..8d68934 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
 
 [[package]]
 name = "ansible-compat"
@@ -482,6 +482,21 @@
 tox = ["tox"]
 
 [[package]]
+name = "debtcollector"
+version = "2.5.0"
+description = "A collection of Python deprecation patterns and strategies that help you collect your technical debt in a non-destructive manner."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "debtcollector-2.5.0-py3-none-any.whl", hash = "sha256:1393a527d2c72f143ffa6a629e9c33face6642634eece475b48cab7b04ba61f3"},
+    {file = "debtcollector-2.5.0.tar.gz", hash = "sha256:dc9d1ad3f745c43f4bbedbca30f9ffe8905a8c028c9926e61077847d5ea257ab"},
+]
+
+[package.dependencies]
+wrapt = ">=1.7.0"
+
+[[package]]
 name = "decorator"
 version = "5.1.1"
 description = "Decorators for Humans"
@@ -497,7 +512,7 @@
 name = "distro"
 version = "1.8.0"
 description = "Distro - an OS platform information API"
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=3.6"
 files = [
@@ -1089,6 +1104,79 @@
 test = ["molecule[test]"]
 
 [[package]]
+name = "msgpack"
+version = "1.0.5"
+description = "MessagePack serializer"
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+    {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"},
+    {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"},
+    {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"},
+    {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"},
+    {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"},
+    {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"},
+    {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"},
+    {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"},
+    {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"},
+    {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"},
+    {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"},
+    {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"},
+    {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"},
+    {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"},
+    {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"},
+    {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"},
+    {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"},
+    {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"},
+    {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"},
+    {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"},
+    {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"},
+    {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"},
+    {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"},
+    {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"},
+    {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"},
+    {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"},
+    {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"},
+    {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"},
+    {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"},
+    {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"},
+    {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"},
+    {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"},
+    {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"},
+    {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"},
+    {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"},
+    {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"},
+    {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"},
+    {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"},
+    {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"},
+    {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"},
+    {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"},
+    {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"},
+    {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"},
+    {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"},
+    {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"},
+    {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"},
+    {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"},
+    {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"},
+    {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"},
+    {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"},
+    {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"},
+    {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"},
+    {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"},
+    {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"},
+    {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"},
+    {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"},
+    {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"},
+    {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"},
+    {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"},
+    {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"},
+    {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"},
+    {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"},
+    {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"},
+]
+
+[[package]]
 name = "munch"
 version = "2.5.0"
 description = "A dot-accessible dictionary (a la JavaScript objects)"
@@ -1203,6 +1291,130 @@
 pbr = ">=2.0.0,<2.1.0 || >2.1.0"
 
 [[package]]
+name = "oslo-config"
+version = "9.1.1"
+description = "Oslo Configuration API"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "oslo.config-9.1.1-py3-none-any.whl", hash = "sha256:7cd56e0b41b04f64dbc42e83e8164d5ef03466390f1216fbda2cb0e1c535c22c"},
+    {file = "oslo.config-9.1.1.tar.gz", hash = "sha256:b07654b53d87792ae8e739962ad729c529c9938a118d891ece9ee31d59716bc9"},
+]
+
+[package.dependencies]
+debtcollector = ">=1.2.0"
+netaddr = ">=0.7.18"
+"oslo.i18n" = ">=3.15.3"
+PyYAML = ">=5.1"
+requests = ">=2.18.0"
+rfc3986 = ">=1.2.0"
+stevedore = ">=1.20.0"
+
+[package.extras]
+rst-generator = ["rst2txt (>=1.1.0)", "sphinx (>=1.8.0,!=2.1.0)"]
+test = ["bandit (>=1.6.0,<1.7.0)", "coverage (>=4.0,!=4.4)", "fixtures (>=3.0.0)", "hacking (>=3.0.1,<3.1.0)", "mypy (>=0.720)", "oslo.log (>=3.36.0)", "oslotest (>=3.2.0)", "pre-commit (>=2.6.0)", "requests-mock (>=1.5.0)", "stestr (>=2.1.0)", "testscenarios (>=0.4)", "testtools (>=2.2.0)"]
+
+[[package]]
+name = "oslo-context"
+version = "5.1.1"
+description = "Oslo Context library"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "oslo.context-5.1.1-py3-none-any.whl", hash = "sha256:3cbe3c42a3e2fc967e740f23e84421bb2d2b5f7579798698dd1d1485dd6baa44"},
+    {file = "oslo.context-5.1.1.tar.gz", hash = "sha256:2f2e79171044efd1807c55713ed2c7f4068b18d73d027819165c4819b287cfaf"},
+]
+
+[package.dependencies]
+debtcollector = ">=1.2.0"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+
+[[package]]
+name = "oslo-i18n"
+version = "6.0.0"
+description = "Oslo i18n library"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "oslo.i18n-6.0.0-py3-none-any.whl", hash = "sha256:080fedf41b05d4dcd23a91d23ee2dea0863996e860a59695856269a42d939fc1"},
+    {file = "oslo.i18n-6.0.0.tar.gz", hash = "sha256:ed10686b75f7c607825177a669155f4e259ce39f6143a375f6359bbcaa4a35cd"},
+]
+
+[package.dependencies]
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+
+[[package]]
+name = "oslo-log"
+version = "5.2.0"
+description = "oslo.log library"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "oslo.log-5.2.0-py3-none-any.whl", hash = "sha256:3f73c96e1cce9a54be26663a7616e3067bc65b112c47916ac0a655d7709f8eff"},
+    {file = "oslo.log-5.2.0.tar.gz", hash = "sha256:6226336d5b6ee1885f057b65dbede84c4a9c5e4e4ae75a0e8e7f383c163ec480"},
+]
+
+[package.dependencies]
+debtcollector = ">=1.19.0"
+"oslo.config" = ">=5.2.0"
+"oslo.context" = ">=2.21.0"
+"oslo.i18n" = ">=3.20.0"
+"oslo.serialization" = ">=2.25.0"
+"oslo.utils" = ">=3.36.0"
+pbr = ">=3.1.1"
+pyinotify = {version = ">=0.9.6", markers = "sys_platform != \"win32\" and sys_platform != \"darwin\" and sys_platform != \"sunos5\""}
+python-dateutil = ">=2.7.0"
+
+[package.extras]
+fixtures = ["fixtures (>=3.0.0)"]
+systemd = ["systemd-python (>=234)"]
+test = ["bandit (>=1.6.0,<1.7.0)", "coverage (>=4.5.1)", "fixtures (>=3.0.0)", "hacking (>=2.0.0,<2.1.0)", "oslotest (>=3.3.0)", "pre-commit (>=2.6.0)", "stestr (>=2.0.0)", "testtools (>=2.3.0)"]
+
+[[package]]
+name = "oslo-serialization"
+version = "5.1.1"
+description = "Oslo Serialization library"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "oslo.serialization-5.1.1-py3-none-any.whl", hash = "sha256:c5dfb97ce8ddd1d2708a9a3f4a091063f6c304940c7cb39f532f7f791441fdca"},
+    {file = "oslo.serialization-5.1.1.tar.gz", hash = "sha256:8abbda8b1763a06071fc28c5d8a9be547ba285f4830e68a70ff88fe11f16bf43"},
+]
+
+[package.dependencies]
+msgpack = ">=0.5.2"
+"oslo.utils" = ">=3.33.0"
+pbr = ">=2.0.0,<2.1.0 || >2.1.0"
+pytz = ">=2013.6"
+
+[[package]]
+name = "oslo-utils"
+version = "6.1.0"
+description = "Oslo Utility library"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "oslo.utils-6.1.0-py3-none-any.whl", hash = "sha256:b34648b1eb311dc3461e84a3bb75ed677fb0a49024def708057881e51759d5d6"},
+    {file = "oslo.utils-6.1.0.tar.gz", hash = "sha256:76bc0108d50aca972b68fec8298e791b5fbcbeb9a51a27c6986b41b0a6a62eeb"},
+]
+
+[package.dependencies]
+debtcollector = ">=1.2.0"
+iso8601 = ">=0.1.11"
+netaddr = ">=0.7.18"
+netifaces = ">=0.10.4"
+"oslo.i18n" = ">=3.15.3"
+packaging = ">=20.4"
+pyparsing = ">=2.1.0"
+pytz = ">=2013.6"
+
+[[package]]
 name = "packaging"
 version = "23.0"
 description = "Core utilities for Python packages"
@@ -1306,6 +1518,17 @@
 plugins = ["importlib-metadata"]
 
 [[package]]
+name = "pyinotify"
+version = "0.9.6"
+description = "Linux filesystem events monitoring"
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+    {file = "pyinotify-0.9.6.tar.gz", hash = "sha256:9c998a5d7606ca835065cdabc013ae6c66eb9ea76a00a1e3bc6e0cfe2b4f71f4"},
+]
+
+[[package]]
 name = "pymdown-extensions"
 version = "9.9.2"
 description = "Extension pack for Python Markdown."
@@ -1321,6 +1544,21 @@
 markdown = ">=3.2"
 
 [[package]]
+name = "pyparsing"
+version = "3.0.9"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+category = "main"
+optional = false
+python-versions = ">=3.6.8"
+files = [
+    {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+    {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+]
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
 name = "pyrsistent"
 version = "0.19.3"
 description = "Persistent/Functional/Immutable data structures"
@@ -1459,7 +1697,7 @@
 name = "python-dateutil"
 version = "2.8.2"
 description = "Extensions to the standard Python datetime module"
-category = "dev"
+category = "main"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
 files = [
@@ -1489,6 +1727,18 @@
 unidecode = ["Unidecode (>=1.1.1)"]
 
 [[package]]
+name = "pytz"
+version = "2023.3"
+description = "World timezone definitions, modern and historical"
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+    {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"},
+    {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"},
+]
+
+[[package]]
 name = "pywin32"
 version = "305"
 description = "Python for Window Extensions"
@@ -1728,6 +1978,21 @@
 test = ["commentjson", "packaging", "pytest"]
 
 [[package]]
+name = "rfc3986"
+version = "2.0.0"
+description = "Validating URI References per RFC 3986"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"},
+    {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"},
+]
+
+[package.extras]
+idna2008 = ["idna"]
+
+[[package]]
 name = "rich"
 version = "13.3.1"
 description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
@@ -1772,6 +2037,25 @@
 ]
 
 [[package]]
+name = "ruyaml"
+version = "0.91.0"
+description = "ruyaml is a fork of ruamel.yaml"
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "ruyaml-0.91.0-py3-none-any.whl", hash = "sha256:50e0ee3389c77ad340e209472e0effd41ae0275246df00cdad0a067532171755"},
+    {file = "ruyaml-0.91.0.tar.gz", hash = "sha256:6ce9de9f4d082d696d3bde264664d1bcdca8f5a9dff9d1a1f1a127969ab871ab"},
+]
+
+[package.dependencies]
+distro = ">=1.3.0"
+setuptools = ">=39.0"
+
+[package.extras]
+docs = ["Sphinx"]
+
+[[package]]
 name = "selinux"
 version = "0.3.0"
 description = "shim selinux module"
@@ -1787,6 +2071,23 @@
 distro = ">=1.3.0"
 
 [[package]]
+name = "setuptools"
+version = "67.7.2"
+description = "Easily download, build, install, upgrade, and uninstall Python packages"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"},
+    {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+
+[[package]]
 name = "six"
 version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
@@ -1927,7 +2228,92 @@
 optional = ["python-socks", "wsaccel"]
 test = ["websockets"]
 
+[[package]]
+name = "wrapt"
+version = "1.15.0"
+description = "Module for decorators, wrappers and monkey patching."
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+files = [
+    {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"},
+    {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"},
+    {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"},
+    {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"},
+    {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"},
+    {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"},
+    {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"},
+    {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"},
+    {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"},
+    {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"},
+    {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"},
+    {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"},
+    {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"},
+    {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"},
+    {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"},
+    {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"},
+    {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"},
+    {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"},
+    {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"},
+    {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"},
+    {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"},
+    {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"},
+    {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"},
+    {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"},
+    {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"},
+    {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"},
+    {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"},
+    {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"},
+    {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"},
+    {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"},
+    {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"},
+    {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"},
+    {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"},
+    {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"},
+    {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"},
+    {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"},
+    {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"},
+    {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"},
+    {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"},
+    {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"},
+    {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"},
+    {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"},
+    {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"},
+    {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"},
+    {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"},
+    {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"},
+    {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"},
+    {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"},
+    {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"},
+    {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"},
+    {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"},
+    {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"},
+    {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"},
+    {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"},
+    {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"},
+    {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"},
+    {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"},
+    {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"},
+    {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"},
+    {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"},
+    {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"},
+    {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"},
+    {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"},
+    {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"},
+    {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"},
+    {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"},
+    {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"},
+    {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"},
+    {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"},
+    {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"},
+    {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"},
+    {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"},
+    {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"},
+    {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"},
+    {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"},
+]
+
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.10"
-content-hash = "5fcf83df96f7f076b107f4f03723a8c1e8f1534c6dcbe8c6b44c9cd97ae1637f"
+content-hash = "8c4b21a949fc30ae6356b7ca43f81da1bd3d07abb713280c57d8bc87178752b5"
diff --git a/pyproject.toml b/pyproject.toml
index b0fad1a..3b265f7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -16,6 +16,8 @@
 docker-image-py = "^0.1.12"
 rjsonnet = "^0.5.2"
 netaddr = "^0.8.0"
+ruyaml = "^0.91.0"
+oslo-log = "^5.2.0"
 
 [tool.poetry.group.dev.dependencies]
 flake8 = "^5.0.4"
diff --git a/roles/defaults/defaults/main.yml b/roles/defaults/defaults/main.yml
index e065164..4dcf75f 100644
--- a/roles/defaults/defaults/main.yml
+++ b/roles/defaults/defaults/main.yml
@@ -20,10 +20,10 @@
 
 atmosphere_images:
   alertmanager: quay.io/prometheus/alertmanager:v0.24.0
-  barbican_api: quay.io/vexxhost/barbican:zed
-  barbican_db_sync: quay.io/vexxhost/barbican:zed
-  bootstrap: quay.io/vexxhost/heat:zed
-  ceph_config_helper: quay.io/vexxhost/libvirtd:yoga-focal
+  barbican_api: quay.io/vexxhost/barbican@sha256:fde302ee731cca6019feaf87400f5a377c3e38f459bc88d4c7677f2967e0939b # image-source: quay.io/vexxhost/barbican:zed
+  barbican_db_sync: quay.io/vexxhost/barbican@sha256:fde302ee731cca6019feaf87400f5a377c3e38f459bc88d4c7677f2967e0939b # image-source: quay.io/vexxhost/barbican:zed
+  bootstrap: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  ceph_config_helper: quay.io/vexxhost/libvirtd@sha256:d400204e0332dc815827e5902038a1c672446c58633ba97ede9e20f8ae9a2349 # image-source: quay.io/vexxhost/libvirtd:yoga-focal
   cert_manager_cainjector: quay.io/jetstack/cert-manager-cainjector:v1.7.1
   cert_manager_cli: quay.io/jetstack/cert-manager-ctl:v1.7.1
   cert_manager_controller: quay.io/jetstack/cert-manager-controller:v1.7.1
@@ -31,14 +31,14 @@
   ceph: quay.io/ceph/ceph:v16.2.11
   cilium_node: quay.io/cilium/cilium:v1.10.20@sha256:c9b3af1f9c405cc8dcb163af0e0ea0a376a9f62304501c9392d26e91178d7869
   cilium_operator: quay.io/cilium/operator-generic:v1.10.20@sha256:f7a07e674687fc4be01168409eeed0986ed7cb19c8d966501507fd24b9f6bd98
-  cinder_api: quay.io/vexxhost/cinder:zed
-  cinder_backup_storage_init: quay.io/vexxhost/cinder:zed
-  cinder_backup: quay.io/vexxhost/cinder:zed
-  cinder_db_sync: quay.io/vexxhost/cinder:zed
-  cinder_scheduler: quay.io/vexxhost/cinder:zed
-  cinder_storage_init: quay.io/vexxhost/cinder:zed
-  cinder_volume_usage_audit: quay.io/vexxhost/cinder:zed
-  cinder_volume: quay.io/vexxhost/cinder:zed
+  cinder_api: quay.io/vexxhost/cinder@sha256:875bc983a9c2a2d1fb6a952d147f2474a169dc77eb9dff4741f3a185c28753fb # image-source: quay.io/vexxhost/cinder:zed
+  cinder_backup_storage_init: quay.io/vexxhost/cinder@sha256:875bc983a9c2a2d1fb6a952d147f2474a169dc77eb9dff4741f3a185c28753fb # image-source: quay.io/vexxhost/cinder:zed
+  cinder_backup: quay.io/vexxhost/cinder@sha256:875bc983a9c2a2d1fb6a952d147f2474a169dc77eb9dff4741f3a185c28753fb # image-source: quay.io/vexxhost/cinder:zed
+  cinder_db_sync: quay.io/vexxhost/cinder@sha256:875bc983a9c2a2d1fb6a952d147f2474a169dc77eb9dff4741f3a185c28753fb # image-source: quay.io/vexxhost/cinder:zed
+  cinder_scheduler: quay.io/vexxhost/cinder@sha256:875bc983a9c2a2d1fb6a952d147f2474a169dc77eb9dff4741f3a185c28753fb # image-source: quay.io/vexxhost/cinder:zed
+  cinder_storage_init: quay.io/vexxhost/cinder@sha256:875bc983a9c2a2d1fb6a952d147f2474a169dc77eb9dff4741f3a185c28753fb # image-source: quay.io/vexxhost/cinder:zed
+  cinder_volume_usage_audit: quay.io/vexxhost/cinder@sha256:875bc983a9c2a2d1fb6a952d147f2474a169dc77eb9dff4741f3a185c28753fb # image-source: quay.io/vexxhost/cinder:zed
+  cinder_volume: quay.io/vexxhost/cinder@sha256:875bc983a9c2a2d1fb6a952d147f2474a169dc77eb9dff4741f3a185c28753fb # image-source: quay.io/vexxhost/cinder:zed
   cluster_api_controller: registry.k8s.io/cluster-api/cluster-api-controller:v1.3.3
   cluster_api_kubeadm_bootstrap_controller: registry.k8s.io/cluster-api/kubeadm-bootstrap-controller:v1.3.3
   cluster_api_kubeadm_control_plane_controller: registry.k8s.io/cluster-api/kubeadm-control-plane-controller:v1.3.3
@@ -49,48 +49,48 @@
   csi_rbd_provisioner: registry.k8s.io/sig-storage/csi-provisioner:v3.1.0
   csi_rbd_resizer: registry.k8s.io/sig-storage/csi-resizer:v1.3.0
   csi_rbd_snapshotter: registry.k8s.io/sig-storage/csi-snapshotter:v4.2.0
-  db_drop: quay.io/vexxhost/heat:zed
-  db_init: quay.io/vexxhost/heat:zed
+  db_drop: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  db_init: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
   dep_check: quay.io/vexxhost/kubernetes-entrypoint:latest
-  designate_api: quay.io/vexxhost/designate:zed
-  designate_central: quay.io/vexxhost/designate:zed
-  designate_db_sync: quay.io/vexxhost/designate:zed
-  designate_mdns: quay.io/vexxhost/designate:zed
-  designate_producer: quay.io/vexxhost/designate:zed
-  designate_sink: quay.io/vexxhost/designate:zed
-  designate_worker: quay.io/vexxhost/designate:zed
-  glance_api: quay.io/vexxhost/glance:zed
-  glance_db_sync: quay.io/vexxhost/glance:zed
-  glance_metadefs_load: quay.io/vexxhost/glance:zed
-  glance_registry: quay.io/vexxhost/glance:zed
-  glance_storage_init: quay.io/vexxhost/glance:zed
+  designate_api: quay.io/vexxhost/designate@sha256:3f5039753555331c7f3ab8fa9f53206f185752d2cb4c3fa0e05c0b5e18540fa0 # image-source: quay.io/vexxhost/designate:zed
+  designate_central: quay.io/vexxhost/designate@sha256:3f5039753555331c7f3ab8fa9f53206f185752d2cb4c3fa0e05c0b5e18540fa0 # image-source: quay.io/vexxhost/designate:zed
+  designate_db_sync: quay.io/vexxhost/designate@sha256:3f5039753555331c7f3ab8fa9f53206f185752d2cb4c3fa0e05c0b5e18540fa0 # image-source: quay.io/vexxhost/designate:zed
+  designate_mdns: quay.io/vexxhost/designate@sha256:3f5039753555331c7f3ab8fa9f53206f185752d2cb4c3fa0e05c0b5e18540fa0 # image-source: quay.io/vexxhost/designate:zed
+  designate_producer: quay.io/vexxhost/designate@sha256:3f5039753555331c7f3ab8fa9f53206f185752d2cb4c3fa0e05c0b5e18540fa0 # image-source: quay.io/vexxhost/designate:zed
+  designate_sink: quay.io/vexxhost/designate@sha256:3f5039753555331c7f3ab8fa9f53206f185752d2cb4c3fa0e05c0b5e18540fa0 # image-source: quay.io/vexxhost/designate:zed
+  designate_worker: quay.io/vexxhost/designate@sha256:3f5039753555331c7f3ab8fa9f53206f185752d2cb4c3fa0e05c0b5e18540fa0 # image-source: quay.io/vexxhost/designate:zed
+  glance_api: quay.io/vexxhost/glance@sha256:32aaf33e83f7285e2ad04cb8e692068e072d9d852b6b2625742995724ec77508 # image-source: quay.io/vexxhost/glance:zed
+  glance_db_sync: quay.io/vexxhost/glance@sha256:32aaf33e83f7285e2ad04cb8e692068e072d9d852b6b2625742995724ec77508 # image-source: quay.io/vexxhost/glance:zed
+  glance_metadefs_load: quay.io/vexxhost/glance@sha256:32aaf33e83f7285e2ad04cb8e692068e072d9d852b6b2625742995724ec77508 # image-source: quay.io/vexxhost/glance:zed
+  glance_registry: quay.io/vexxhost/glance@sha256:32aaf33e83f7285e2ad04cb8e692068e072d9d852b6b2625742995724ec77508 # image-source: quay.io/vexxhost/glance:zed
+  glance_storage_init: quay.io/vexxhost/glance@sha256:32aaf33e83f7285e2ad04cb8e692068e072d9d852b6b2625742995724ec77508 # image-source: quay.io/vexxhost/glance:zed
   grafana_sidecar: quay.io/kiwigrid/k8s-sidecar:1.19.2
   grafana: docker.io/grafana/grafana:9.2.3
   haproxy: docker.io/library/haproxy:2.5
-  heat_api: quay.io/vexxhost/heat:zed
-  heat_cfn: quay.io/vexxhost/heat:zed
-  heat_cloudwatch: quay.io/vexxhost/heat:zed
-  heat_db_sync: quay.io/vexxhost/heat:zed
-  heat_engine_cleaner: quay.io/vexxhost/heat:zed
-  heat_engine: quay.io/vexxhost/heat:zed
-  heat_purge_deleted: quay.io/vexxhost/heat:zed
-  horizon_db_sync: quay.io/vexxhost/horizon:zed
-  horizon: quay.io/vexxhost/horizon:zed
+  heat_api: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  heat_cfn: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  heat_cloudwatch: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  heat_db_sync: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  heat_engine_cleaner: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  heat_engine: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  heat_purge_deleted: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  horizon_db_sync: quay.io/vexxhost/horizon@sha256:152d6cec57d241c19ef1e36abc64ff3c1a73e1c877142c067fac0e08957c96f4 # image-source: quay.io/vexxhost/horizon:zed
+  horizon: quay.io/vexxhost/horizon@sha256:152d6cec57d241c19ef1e36abc64ff3c1a73e1c877142c067fac0e08957c96f4 # image-source: quay.io/vexxhost/horizon:zed
   ingress_nginx_controller: registry.k8s.io/ingress-nginx/controller:v1.1.1@sha256:0bc88eb15f9e7f84e8e56c14fa5735aaa488b840983f87bd79b1054190e660de
   ingress_nginx_default_backend: registry.k8s.io/defaultbackend-amd64:1.5
   ingress_nginx_kube_webhook_certgen: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.1.1@sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660 # noqa: yaml[line-length]
   keepalived: us-docker.pkg.dev/vexxhost-infra/openstack/keepalived:2.0.19
-  keystone_api: quay.io/vexxhost/keystone:zed
-  keystone_credential_cleanup: quay.io/vexxhost/heat:zed
-  keystone_credential_rotate: quay.io/vexxhost/keystone:zed
-  keystone_credential_setup: quay.io/vexxhost/keystone:zed
-  keystone_db_sync: quay.io/vexxhost/keystone:zed
-  keystone_domain_manage: quay.io/vexxhost/heat:zed
-  keystone_fernet_rotate: quay.io/vexxhost/keystone:zed
-  keystone_fernet_setup: quay.io/vexxhost/keystone:zed
-  ks_endpoints: quay.io/vexxhost/heat:zed
-  ks_service: quay.io/vexxhost/heat:zed
-  ks_user: quay.io/vexxhost/heat:zed
+  keystone_api: quay.io/vexxhost/keystone@sha256:8214ccc92d7973451509701c5728d5af614762be3b2b89b29e486f28eb68167f # image-source: quay.io/vexxhost/keystone:zed
+  keystone_credential_cleanup: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  keystone_credential_rotate: quay.io/vexxhost/keystone@sha256:8214ccc92d7973451509701c5728d5af614762be3b2b89b29e486f28eb68167f # image-source: quay.io/vexxhost/keystone:zed
+  keystone_credential_setup: quay.io/vexxhost/keystone@sha256:8214ccc92d7973451509701c5728d5af614762be3b2b89b29e486f28eb68167f # image-source: quay.io/vexxhost/keystone:zed
+  keystone_db_sync: quay.io/vexxhost/keystone@sha256:8214ccc92d7973451509701c5728d5af614762be3b2b89b29e486f28eb68167f # image-source: quay.io/vexxhost/keystone:zed
+  keystone_domain_manage: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  keystone_fernet_rotate: quay.io/vexxhost/keystone@sha256:8214ccc92d7973451509701c5728d5af614762be3b2b89b29e486f28eb68167f # image-source: quay.io/vexxhost/keystone:zed
+  keystone_fernet_setup: quay.io/vexxhost/keystone@sha256:8214ccc92d7973451509701c5728d5af614762be3b2b89b29e486f28eb68167f # image-source: quay.io/vexxhost/keystone:zed
+  ks_endpoints: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  ks_service: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  ks_user: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
   kube_apiserver: registry.k8s.io/kube-apiserver:v1.22.17
   kube_controller_manager: registry.k8s.io/kube-controller-manager:v1.22.17
   kube_coredns: registry.k8s.io/coredns/coredns:v1.8.4
@@ -98,68 +98,68 @@
   kube_proxy: registry.k8s.io/kube-proxy:v1.22.17
   kube_scheduler: registry.k8s.io/kube-scheduler:v1.22.17
   kube_state_metrics: registry.k8s.io/kube-state-metrics/kube-state-metrics:v2.6.0
-  libvirt: quay.io/vexxhost/libvirtd:yoga-focal
+  libvirt: quay.io/vexxhost/libvirtd@sha256:d400204e0332dc815827e5902038a1c672446c58633ba97ede9e20f8ae9a2349 # image-source: quay.io/vexxhost/libvirtd:yoga-focal
   local_path_provisioner_helper: docker.io/library/busybox:1.36.0
   local_path_provisioner: docker.io/rancher/local-path-provisioner:v0.0.24
   loki: docker.io/grafana/loki:2.7.3
   loki_gateway: docker.io/nginxinc/nginx-unprivileged:1.19-alpine
-  magnum_api: quay.io/vexxhost/magnum:zed
-  magnum_cluster_api_proxy: quay.io/vexxhost/magnum:zed
-  magnum_conductor: quay.io/vexxhost/magnum:zed
-  magnum_db_sync: quay.io/vexxhost/magnum:zed
+  magnum_api: quay.io/vexxhost/magnum@sha256:941d0acfbb92f9d83c6b8e4876637d9f1f12ea780b0bff36eb6d4622decb852e # image-source: quay.io/vexxhost/magnum:zed
+  magnum_cluster_api_proxy: quay.io/vexxhost/magnum@sha256:941d0acfbb92f9d83c6b8e4876637d9f1f12ea780b0bff36eb6d4622decb852e # image-source: quay.io/vexxhost/magnum:zed
+  magnum_conductor: quay.io/vexxhost/magnum@sha256:941d0acfbb92f9d83c6b8e4876637d9f1f12ea780b0bff36eb6d4622decb852e # image-source: quay.io/vexxhost/magnum:zed
+  magnum_db_sync: quay.io/vexxhost/magnum@sha256:941d0acfbb92f9d83c6b8e4876637d9f1f12ea780b0bff36eb6d4622decb852e # image-source: quay.io/vexxhost/magnum:zed
   magnum_registry: docker.io/library/registry:2.7.1
-  manila_api: quay.io/vexxhost/manila:zed
-  manila_data: quay.io/vexxhost/manila:zed
-  manila_db_sync: quay.io/vexxhost/manila:zed
-  manila_scheduler: quay.io/vexxhost/manila:zed
-  manila_share: quay.io/vexxhost/manila:zed
+  manila_api: quay.io/vexxhost/manila@sha256:78a258f0a632b0778d91767c4ec7b6f84e6ef1de7cf1e51e726374caa295b8f6 # image-source: quay.io/vexxhost/manila:zed
+  manila_data: quay.io/vexxhost/manila@sha256:78a258f0a632b0778d91767c4ec7b6f84e6ef1de7cf1e51e726374caa295b8f6 # image-source: quay.io/vexxhost/manila:zed
+  manila_db_sync: quay.io/vexxhost/manila@sha256:78a258f0a632b0778d91767c4ec7b6f84e6ef1de7cf1e51e726374caa295b8f6 # image-source: quay.io/vexxhost/manila:zed
+  manila_scheduler: quay.io/vexxhost/manila@sha256:78a258f0a632b0778d91767c4ec7b6f84e6ef1de7cf1e51e726374caa295b8f6 # image-source: quay.io/vexxhost/manila:zed
+  manila_share: quay.io/vexxhost/manila@sha256:78a258f0a632b0778d91767c4ec7b6f84e6ef1de7cf1e51e726374caa295b8f6 # image-source: quay.io/vexxhost/manila:zed
   memcached: docker.io/library/memcached:1.6.17
-  neutron_bagpipe_bgp: quay.io/vexxhost/neutron:zed
+  neutron_bagpipe_bgp: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
   neutron_coredns: docker.io/coredns/coredns:1.9.3
-  neutron_db_sync: quay.io/vexxhost/neutron:zed
-  neutron_dhcp: quay.io/vexxhost/neutron:zed
-  neutron_ironic_agent: quay.io/vexxhost/neutron:zed
-  neutron_l2gw: quay.io/vexxhost/neutron:zed
-  neutron_l3: quay.io/vexxhost/neutron:zed
-  neutron_linuxbridge_agent: quay.io/vexxhost/neutron:zed
-  neutron_metadata: quay.io/vexxhost/neutron:zed
-  neutron_netns_cleanup_cron: quay.io/vexxhost/neutron:zed
-  neutron_openvswitch_agent: quay.io/vexxhost/neutron:zed
-  neutron_server: quay.io/vexxhost/neutron:zed
-  neutron_sriov_agent_init: quay.io/vexxhost/neutron:zed
-  neutron_sriov_agent: quay.io/vexxhost/neutron:zed
+  neutron_db_sync: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_dhcp: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_ironic_agent: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_l2gw: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_l3: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_linuxbridge_agent: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_metadata: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_netns_cleanup_cron: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_openvswitch_agent: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_server: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_sriov_agent_init: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
+  neutron_sriov_agent: quay.io/vexxhost/neutron@sha256:1f7e7d9d3db9100b55243b33bd215aebec89939b8249411153fc4035db62ba93 # image-source: quay.io/vexxhost/neutron:zed
   node_feature_discovery: registry.k8s.io/nfd/node-feature-discovery:v0.11.2
-  nova_api: quay.io/vexxhost/nova:zed
-  nova_archive_deleted_rows: quay.io/vexxhost/nova:zed
-  nova_cell_setup_init: quay.io/vexxhost/heat:zed
-  nova_cell_setup: quay.io/vexxhost/nova:zed
+  nova_api: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  nova_archive_deleted_rows: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  nova_cell_setup_init: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  nova_cell_setup: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
   nova_compute_ironic: quay.io/openstack.kolla/nova-compute-ironic:zed-ubuntu-jammy
   nova_compute_ssh: quay.io/vexxhost/nova-ssh:latest
-  nova_compute: quay.io/vexxhost/nova:zed
-  nova_conductor: quay.io/vexxhost/nova:zed
-  nova_consoleauth: quay.io/vexxhost/nova:zed
-  nova_db_sync: quay.io/vexxhost/nova:zed
-  nova_novncproxy_assets: quay.io/vexxhost/nova:zed
-  nova_novncproxy: quay.io/vexxhost/nova:zed
-  nova_placement: quay.io/vexxhost/nova:zed
-  nova_scheduler: quay.io/vexxhost/nova:zed
+  nova_compute: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  nova_conductor: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  nova_consoleauth: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  nova_db_sync: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  nova_novncproxy_assets: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  nova_novncproxy: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  nova_placement: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  nova_scheduler: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
   nova_service_cleaner: quay.io/vexxhost/cli:latest
-  nova_spiceproxy_assets: quay.io/vexxhost/nova:zed
-  nova_spiceproxy: quay.io/vexxhost/nova:zed
-  octavia_api: quay.io/vexxhost/octavia:zed
-  octavia_db_sync: quay.io/vexxhost/octavia:zed
-  octavia_health_manager_init: quay.io/vexxhost/heat:zed
-  octavia_health_manager: quay.io/vexxhost/octavia:zed
-  octavia_housekeeping: quay.io/vexxhost/octavia:zed
-  octavia_worker: quay.io/vexxhost/octavia:zed
+  nova_spiceproxy_assets: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  nova_spiceproxy: quay.io/vexxhost/nova@sha256:bd4a877013053e134a33edc18546f99923a2057072328a4323683eb315acd308 # image-source: quay.io/vexxhost/nova:zed
+  octavia_api: quay.io/vexxhost/octavia@sha256:9065365ed1d731e5130dcf7d600bc8dc8ffa158093c53dd151eddfe49a29a5ee # image-source: quay.io/vexxhost/octavia:zed
+  octavia_db_sync: quay.io/vexxhost/octavia@sha256:9065365ed1d731e5130dcf7d600bc8dc8ffa158093c53dd151eddfe49a29a5ee # image-source: quay.io/vexxhost/octavia:zed
+  octavia_health_manager_init: quay.io/vexxhost/heat@sha256:755225f9a63c0968f1ceeda3a2f06c66dd8d247ff00308f549e66496aa8f59d0 # image-source: quay.io/vexxhost/heat:zed
+  octavia_health_manager: quay.io/vexxhost/octavia@sha256:9065365ed1d731e5130dcf7d600bc8dc8ffa158093c53dd151eddfe49a29a5ee # image-source: quay.io/vexxhost/octavia:zed
+  octavia_housekeeping: quay.io/vexxhost/octavia@sha256:9065365ed1d731e5130dcf7d600bc8dc8ffa158093c53dd151eddfe49a29a5ee # image-source: quay.io/vexxhost/octavia:zed
+  octavia_worker: quay.io/vexxhost/octavia@sha256:9065365ed1d731e5130dcf7d600bc8dc8ffa158093c53dd151eddfe49a29a5ee # image-source: quay.io/vexxhost/octavia:zed
   openvswitch_db_server: quay.io/vexxhost/openvswitch:2.17.3
   openvswitch_vswitchd: quay.io/vexxhost/openvswitch:2.17.3
   pause: registry.k8s.io/pause:3.8
   percona_xtradb_cluster_haproxy: docker.io/percona/percona-xtradb-cluster-operator:1.10.0-haproxy
   percona_xtradb_cluster_operator: docker.io/percona/percona-xtradb-cluster-operator:1.10.0
   percona_xtradb_cluster: docker.io/percona/percona-xtradb-cluster:5.7.39-31.61
-  placement_db_sync: quay.io/vexxhost/placement:zed
-  placement: quay.io/vexxhost/placement:zed
+  placement_db_sync: quay.io/vexxhost/placement@sha256:ef2f95bdc3c88504b3a2297bf314728788126008f9bafba337e53d48a7dd19c3 # image-source: quay.io/vexxhost/placement:zed
+  placement: quay.io/vexxhost/placement@sha256:ef2f95bdc3c88504b3a2297bf314728788126008f9bafba337e53d48a7dd19c3 # image-source: quay.io/vexxhost/placement:zed
   prometheus_config_reloader: quay.io/prometheus-operator/prometheus-config-reloader:v0.60.1
   prometheus_ethtool_exporter: quay.io/vexxhost/ethtool-exporter:5f05120a743a71adcbceb9f8ee1d43ecc7c4183a
   prometheus_ipmi_exporter: us-docker.pkg.dev/vexxhost-infra/openstack/ipmi-exporter:1.4.0
@@ -177,12 +177,12 @@
   rabbitmq_server: docker.io/library/rabbitmq:3.10.2-management
   rabbitmq_topology_operator: docker.io/rabbitmqoperator/messaging-topology-operator:1.6.0
   rook_ceph: docker.io/rook/ceph:v1.10.10
-  senlin_api: quay.io/vexxhost/senlin:zed
-  senlin_conductor: quay.io/vexxhost/senlin:zed
-  senlin_db_sync: quay.io/vexxhost/senlin:zed
-  senlin_engine_cleaner: quay.io/vexxhost/senlin:zed
-  senlin_engine: quay.io/vexxhost/senlin:zed
-  senlin_health_manager: quay.io/vexxhost/senlin:zed
+  senlin_api: quay.io/vexxhost/senlin@sha256:66e064a6337fd86f7936d2724ae45136ee8429b486e236fb34e03874034fed70 # image-source: quay.io/vexxhost/senlin:zed
+  senlin_conductor: quay.io/vexxhost/senlin@sha256:66e064a6337fd86f7936d2724ae45136ee8429b486e236fb34e03874034fed70 # image-source: quay.io/vexxhost/senlin:zed
+  senlin_db_sync: quay.io/vexxhost/senlin@sha256:66e064a6337fd86f7936d2724ae45136ee8429b486e236fb34e03874034fed70 # image-source: quay.io/vexxhost/senlin:zed
+  senlin_engine_cleaner: quay.io/vexxhost/senlin@sha256:66e064a6337fd86f7936d2724ae45136ee8429b486e236fb34e03874034fed70 # image-source: quay.io/vexxhost/senlin:zed
+  senlin_engine: quay.io/vexxhost/senlin@sha256:66e064a6337fd86f7936d2724ae45136ee8429b486e236fb34e03874034fed70 # image-source: quay.io/vexxhost/senlin:zed
+  senlin_health_manager: quay.io/vexxhost/senlin@sha256:66e064a6337fd86f7936d2724ae45136ee8429b486e236fb34e03874034fed70 # image-source: quay.io/vexxhost/senlin:zed
   skopeo: quay.io/skopeo/stable:latest
   tempest_run_tests: us-docker.pkg.dev/vexxhost-infra/openstack/tempest:30.1.0-4
   vector: docker.io/timberio/vector:0.27.0-debian
diff --git a/roles/kubernetes_node_labels/defaults/main.yml b/roles/kubernetes_node_labels/defaults/main.yml
index 0893806..6d1784d 100644
--- a/roles/kubernetes_node_labels/defaults/main.yml
+++ b/roles/kubernetes_node_labels/defaults/main.yml
@@ -16,11 +16,11 @@
 
 kubernetes_node_labels: |
   {% set res = {} -%}
-  {% if inventory_hostname in groups['controllers'] %}
+  {% if inventory_hostname in groups.get('controllers', []) %}
   {%   set _ = res.update({'openstack-control-plane': 'enabled'}) %}
   {%   set _ = res.update({'openvswitch': 'enabled'}) %}
   {% endif %}
-  {% if inventory_hostname in groups['computes'] %}
+  {% if inventory_hostname in groups.get('computes', []) %}
   {%   set _ = res.update({'openstack-compute-node': 'enabled'}) %}
   {%   set _ = res.update({'openvswitch': 'enabled'}) %}
   {% endif %}