feat: added docker_image filter + image_list in ansible
diff --git a/.gitignore b/.gitignore
index d40a05a..a4e152a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@
 .coverage
 .kube
 .hypothesis
+tests/output
diff --git a/plugins/filter/docker_image.py b/plugins/filter/docker_image.py
new file mode 100644
index 0000000..bbe43f2
--- /dev/null
+++ b/plugins/filter/docker_image.py
@@ -0,0 +1,83 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from ansible.errors import AnsibleFilterError
+from ansible.module_utils.common.collections import is_string
+
+try:
+    from docker_image import reference
+
+    HAS_DOCKER_IMAGE = True
+except ImportError:
+    HAS_DOCKER_IMAGE = False
+
+DOCUMENTATION = """
+  name: docker_image
+  short_description: Parse a Docker image reference
+  version_added: 0.13.0
+  description:
+    - Parse a Docker image and return different parts of the reference
+    - This lookup module requires "docker-image-py" to be installed on the
+      Ansible controller.
+  options:
+    _input:
+      type: string
+      required: true
+      description:
+          - Docker image reference to parse
+    part:
+      type: string
+      default: 'ref'
+      options: [name, ref]
+      required: true
+      description:
+        - Part of the Docker image reference to return
+  author:
+    - Mohammed Naser <mnaser@vexxhost.com>
+"""
+
+EXAMPLES = """
+  - name: Generate a Docker image reference
+    ansible.builtin.debug:
+      msg: "{{ 'docker.io/library/memcached:1.6.3' | community.general.docker_image('name') }}"
+"""
+
+RETURN = """
+  _value:
+    description: The part of the Docker image reference
+    type: string
+"""
+
+
+def docker_image(value, part="ref"):
+    if not is_string(value):
+        raise AnsibleFilterError(
+            "Invalid value type (%s) for docker_image (%r)" % (type(value), value)
+        )
+
+    if not HAS_DOCKER_IMAGE:
+        raise AnsibleFilterError(
+            "Failed to import docker-image-py module, ensure it is installed on the controller"
+        )
+
+    ref = reference.Reference.parse(value)
+
+    if part == "ref":
+        return ref.string()
+    if part == "name":
+        return ref["name"]
diff --git a/poetry.lock b/poetry.lock
index 4d982f6..ae92ad3 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -365,6 +365,17 @@
 test = ["pytest (>=6)"]
 
 [[package]]
+name = "execnet"
+version = "1.9.0"
+description = "execnet: rapid multi-Python deployment"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.extras]
+testing = ["pre-commit"]
+
+[[package]]
 name = "fasteners"
 version = "0.18"
 description = "A python package that provides useful locks"
@@ -1014,6 +1025,14 @@
 tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"]
 
 [[package]]
+name = "py"
+version = "1.11.0"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[[package]]
 name = "pycodestyle"
 version = "2.9.1"
 description = "Python style guide checker"
@@ -1165,6 +1184,18 @@
 testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
 
 [[package]]
+name = "pytest-forked"
+version = "1.4.0"
+description = "run tests in isolated forked subprocesses"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+py = "*"
+pytest = ">=3.10"
+
+[[package]]
 name = "pytest-kind"
 version = "22.11.1"
 description = "Kubernetes test support with KIND for pytest"
@@ -1190,6 +1221,23 @@
 dev = ["pre-commit", "pytest-asyncio", "tox"]
 
 [[package]]
+name = "pytest-xdist"
+version = "3.1.0"
+description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+execnet = ">=1.1"
+pytest = ">=6.2.0"
+
+[package.extras]
+psutil = ["psutil (>=3.0)"]
+setproctitle = ["setproctitle"]
+testing = ["filelock"]
+
+[[package]]
 name = "python-dateutil"
 version = "2.8.2"
 description = "Extensions to the standard Python datetime module"
@@ -1623,7 +1671,7 @@
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.10"
-content-hash = "505316358da7d5be5cb22c3f8e87eb1fd96183738c8baad35d615eda1be7594c"
+content-hash = "64ad5d919246a0c112a949d86546a4a22e09f3a414dfcc9f7278e292e9224d4b"
 
 [metadata.files]
 aiohttp = [
@@ -1963,6 +2011,10 @@
     {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"},
     {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"},
 ]
+execnet = [
+    {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"},
+    {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"},
+]
 fasteners = [
     {file = "fasteners-0.18-py3-none-any.whl", hash = "sha256:1d4caf5f8db57b0e4107d94fd5a1d02510a450dced6ca77d1839064c1bacf20c"},
     {file = "fasteners-0.18.tar.gz", hash = "sha256:cb7c13ef91e0c7e4fe4af38ecaf6b904ec3f5ce0dda06d34924b6b74b869d953"},
@@ -2473,6 +2525,10 @@
     {file = "prettytable-3.5.0-py3-none-any.whl", hash = "sha256:fe391c3b545800028edf5dbb6a5360893feb398367fcc1cf8d7a5b29ce5c59a1"},
     {file = "prettytable-3.5.0.tar.gz", hash = "sha256:52f682ba4efe29dccb38ff0fe5bac8a23007d0780ff92a8b85af64bc4fc74d72"},
 ]
+py = [
+    {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
+    {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
+]
 pycodestyle = [
     {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
     {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
@@ -2583,6 +2639,10 @@
     {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
     {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
 ]
+pytest-forked = [
+    {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"},
+    {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"},
+]
 pytest-kind = [
     {file = "pytest-kind-22.11.1.tar.gz", hash = "sha256:ae7a4c753fcbbf9e44a0cc587d5219a0b8b2b1e7bcc9cbe14234f745dd5db681"},
     {file = "pytest_kind-22.11.1-py3-none-any.whl", hash = "sha256:35fd99e4f94e0374834b5893ee4a95e33ad5dc1c18080356f01fadf21224e830"},
@@ -2591,6 +2651,10 @@
     {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"},
     {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"},
 ]
+pytest-xdist = [
+    {file = "pytest-xdist-3.1.0.tar.gz", hash = "sha256:40fdb8f3544921c5dfcd486ac080ce22870e71d82ced6d2e78fa97c2addd480c"},
+    {file = "pytest_xdist-3.1.0-py3-none-any.whl", hash = "sha256:70a76f191d8a1d2d6be69fc440cdf85f3e4c03c08b520fd5dc5d338d6cf07d89"},
+]
 python-dateutil = [
     {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
     {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
diff --git a/pyproject.toml b/pyproject.toml
index 71fa6b6..cc7092e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -61,6 +61,8 @@
 python-on-whales = "^0.52.0"
 tomli-w = "^1.0.0"
 responses = "^0.22.0"
+pytest-forked = "^1.4.0"
+pytest-xdist = "^3.1.0"
 
 [tool.poetry.group.docs.dependencies]
 mkdocs-material = "^8.5.7"
diff --git a/roles/defaults/README.md b/roles/defaults/README.md
new file mode 100644
index 0000000..59d24fb
--- /dev/null
+++ b/roles/defaults/README.md
@@ -0,0 +1,9 @@
+# `defaults`
+
+This is a meta role that contains a set of defaults which are used across the
+other roles.  It is setup as a dependency in all of the other roles so that
+these defaults are always available.
+
+## Role Variables
+
+* `atmosphere_images`: List of images which will be used for the deployment.
diff --git a/roles/defaults/defaults/main.yml b/roles/defaults/defaults/main.yml
new file mode 100644
index 0000000..bee0fe0
--- /dev/null
+++ b/roles/defaults/defaults/main.yml
@@ -0,0 +1,159 @@
+atmosphere_images:
+  alertmanager: quay.io/prometheus/alertmanager:v0.24.0
+  atmosphere: quay.io/vexxhost/atmosphere:0.13.0
+  barbican_api: quay.io/vexxhost/barbican:wallaby
+  barbican_db_sync: quay.io/vexxhost/barbican:wallaby
+  bootstrap: quay.io/vexxhost/heat:zed
+  ceph_config_helper: 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
+  cert_manager_webhook: quay.io/jetstack/cert-manager-webhook:v1.7.1
+  cilium_node: quay.io/cilium/cilium:v1.10.7@sha256:e23f55e80e1988db083397987a89967aa204ad6fc32da243b9160fbcea29b0ca
+  cilium_operator: quay.io/cilium/operator-generic:v1.10.7@sha256:d0b491d8d8cb45862ed7f0410f65e7c141832f0f95262643fa5ff1edfcddcafe
+  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
+  cluster_api_controller: registry.k8s.io/cluster-api/cluster-api-controller:v1.3.0
+  cluster_api_kubeadm_bootstrap_controller: registry.k8s.io/cluster-api/kubeadm-bootstrap-controller:v1.3.0
+  cluster_api_kubeadm_control_plane_controller: registry.k8s.io/cluster-api/kubeadm-control-plane-controller:v1.3.0
+  cluster_api_openstack_controller: gcr.io/k8s-staging-capi-openstack/capi-openstack-controller:nightly_main_20221109
+  csi_node_driver_registrar: k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.4.0
+  csi_rbd_attacher: k8s.gcr.io/sig-storage/csi-attacher:v3.4.0
+  csi_rbd_plugin: quay.io/cephcsi/cephcsi:v3.5.1
+  csi_rbd_provisioner: k8s.gcr.io/sig-storage/csi-provisioner:v3.1.0
+  csi_rbd_resizer: k8s.gcr.io/sig-storage/csi-resizer:v1.3.0
+  csi_rbd_snapshotter: k8s.gcr.io/sig-storage/csi-snapshotter:v4.2.0
+  db_drop: quay.io/vexxhost/heat:zed
+  db_init: 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
+  flux_helm_controller: ghcr.io/fluxcd/helm-controller:v0.22.2
+  flux_kustomize_controller: ghcr.io/fluxcd/kustomize-controller:v0.27.0
+  flux_notification_controller: ghcr.io/fluxcd/notification-controller:v0.25.1
+  flux_source_controller: ghcr.io/fluxcd/source-controller:v0.26.1
+  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
+  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: us-docker.pkg.dev/vexxhost-infra/openstack/heat:wallaby
+  heat_cfn: us-docker.pkg.dev/vexxhost-infra/openstack/heat:wallaby
+  heat_cloudwatch: us-docker.pkg.dev/vexxhost-infra/openstack/heat:wallaby
+  heat_db_sync: us-docker.pkg.dev/vexxhost-infra/openstack/heat:wallaby
+  heat_engine_cleaner: us-docker.pkg.dev/vexxhost-infra/openstack/heat:wallaby
+  heat_engine: us-docker.pkg.dev/vexxhost-infra/openstack/heat:wallaby
+  heat_purge_deleted: us-docker.pkg.dev/vexxhost-infra/openstack/heat:wallaby
+  horizon_db_sync: us-docker.pkg.dev/vexxhost-infra/openstack/horizon:wallaby
+  horizon: us-docker.pkg.dev/vexxhost-infra/openstack/horizon:wallaby
+  ingress_nginx_controller: k8s.gcr.io/ingress-nginx/controller:v1.1.1@sha256:0bc88eb15f9e7f84e8e56c14fa5735aaa488b840983f87bd79b1054190e660de
+  ingress_nginx_default_backend: k8s.gcr.io/defaultbackend-amd64:1.5
+  ingress_nginx_kube_webhook_certgen: k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1@sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660
+  keepalived: us-docker.pkg.dev/vexxhost-infra/openstack/keepalived:2.0.19
+  keystone_api: quay.io/vexxhost/keystone:wallaby
+  keystone_credential_cleanup: quay.io/vexxhost/heat:zed
+  keystone_credential_rotate: quay.io/vexxhost/keystone:wallaby
+  keystone_credential_setup: quay.io/vexxhost/keystone:wallaby
+  keystone_db_sync: quay.io/vexxhost/keystone:wallaby
+  keystone_domain_manage: quay.io/vexxhost/heat:zed
+  keystone_fernet_rotate: quay.io/vexxhost/keystone:wallaby
+  keystone_fernet_setup: quay.io/vexxhost/keystone:wallaby
+  ks_endpoints: quay.io/vexxhost/heat:zed
+  ks_service: quay.io/vexxhost/heat:zed
+  ks_user: quay.io/vexxhost/heat:zed
+  kube_apiserver: k8s.gcr.io/kube-apiserver:v1.22.17
+  kube_controller_manager: k8s.gcr.io/kube-controller-manager:v1.22.17
+  kube_coredns: k8s.gcr.io/coredns/coredns:v1.8.4
+  kube_etcd: k8s.gcr.io/etcd:3.5.0-0
+  kube_proxy: k8s.gcr.io/kube-proxy:v1.22.17
+  kube_scheduler: k8s.gcr.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
+  magnum_api: quay.io/vexxhost/magnum@sha256:46e7c910780864f4532ecc85574f159a36794f37aac6be65e4b48c46040ced17
+  magnum_conductor: quay.io/vexxhost/magnum@sha256:46e7c910780864f4532ecc85574f159a36794f37aac6be65e4b48c46040ced17
+  magnum_db_sync: quay.io/vexxhost/magnum@sha256:46e7c910780864f4532ecc85574f159a36794f37aac6be65e4b48c46040ced17
+  magnum_registry: docker.io/library/registry:2.7.1
+  memcached: docker.io/library/memcached:1.6.17
+  neutron_bagpipe_bgp: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_coredns: docker.io/coredns/coredns:1.9.3
+  neutron_db_sync: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_dhcp: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_ironic_agent: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_l2gw: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_l3: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_linuxbridge_agent: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_metadata: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_netns_cleanup_cron: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_openvswitch_agent: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_server: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_sriov_agent_init: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  neutron_sriov_agent: us-docker.pkg.dev/vexxhost-infra/openstack/neutron:wallaby
+  node_feature_discovery: k8s.gcr.io/nfd/node-feature-discovery:v0.11.2
+  nova_api: quay.io/vexxhost/nova:wallaby
+  nova_archive_deleted_rows: quay.io/vexxhost/nova:wallaby
+  nova_cell_setup_init: quay.io/vexxhost/heat:zed
+  nova_cell_setup: quay.io/vexxhost/nova:wallaby
+  nova_compute_ironic: docker.io/kolla/ubuntu-source-nova-compute-ironic:wallaby
+  nova_compute_ssh: quay.io/vexxhost/nova-ssh:wallaby
+  nova_compute: quay.io/vexxhost/nova:wallaby
+  nova_conductor: quay.io/vexxhost/nova:wallaby
+  nova_consoleauth: quay.io/vexxhost/nova:wallaby
+  nova_db_sync: quay.io/vexxhost/nova:wallaby
+  nova_novncproxy_assets: quay.io/vexxhost/nova:wallaby
+  nova_novncproxy: quay.io/vexxhost/nova:wallaby
+  nova_placement: quay.io/vexxhost/nova:wallaby
+  nova_scheduler: quay.io/vexxhost/nova:wallaby
+  nova_service_cleaner: quay.io/vexxhost/cli:latest
+  nova_spiceproxy_assets: quay.io/vexxhost/nova:wallaby
+  nova_spiceproxy: quay.io/vexxhost/nova:wallaby
+  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
+  openvswitch_db_server: quay.io/vexxhost/openvswitch:2.17.3
+  openvswitch_vswitchd: quay.io/vexxhost/openvswitch:2.17.3
+  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:wallaby
+  placement: quay.io/vexxhost/placement:wallaby
+  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
+  prometheus_memcached_exporter: quay.io/prometheus/memcached-exporter:v0.10.0
+  prometheus_mysqld_exporter: quay.io/prometheus/mysqld-exporter:v0.14.0
+  prometheus_node_exporter: quay.io/prometheus/node-exporter:v1.3.1
+  prometheus_openstack_exporter: ghcr.io/openstack-exporter/openstack-exporter:1.6.0
+  prometheus_operator_kube_webhook_certgen: k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.3.0
+  prometheus_operator: quay.io/prometheus-operator/prometheus-operator:v0.60.1
+  prometheus_pushgateway: docker.io/prom/pushgateway:v1.4.2
+  prometheus: quay.io/prometheus/prometheus:v2.39.1
+  rabbit_init: docker.io/library/rabbitmq:3.10.2-management
+  rabbitmq_cluster_operator: docker.io/rabbitmqoperator/cluster-operator:1.13.1
+  rabbitmq_credential_updater: docker.io/rabbitmqoperator/default-user-credential-updater:1.0.2
+  rabbitmq_server: docker.io/library/rabbitmq:3.10.2-management
+  rabbitmq_topology_operator: docker.io/rabbitmqoperator/messaging-topology-operator:1.6.0
+  senlin_api: us-docker.pkg.dev/vexxhost-infra/openstack/senlin:wallaby
+  senlin_conductor: us-docker.pkg.dev/vexxhost-infra/openstack/senlin:wallaby
+  senlin_db_sync: us-docker.pkg.dev/vexxhost-infra/openstack/senlin:wallaby
+  senlin_engine_cleaner: us-docker.pkg.dev/vexxhost-infra/openstack/senlin:wallaby
+  senlin_engine: us-docker.pkg.dev/vexxhost-infra/openstack/senlin:wallaby
+  senlin_health_manager: us-docker.pkg.dev/vexxhost-infra/openstack/senlin:wallaby
+  skopeo: quay.io/skopeo/stable:latest
+  tempest_run_tests: us-docker.pkg.dev/vexxhost-infra/openstack/tempest:30.1.0-4
diff --git a/roles/defaults/meta/main.yml b/roles/defaults/meta/main.yml
new file mode 100644
index 0000000..c2f952b
--- /dev/null
+++ b/roles/defaults/meta/main.yml
@@ -0,0 +1,24 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Meta role for defaults
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  standalone: false
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
diff --git a/tests/unit/plugins/filter/test_docker_image.py b/tests/unit/plugins/filter/test_docker_image.py
new file mode 100644
index 0000000..547d89b
--- /dev/null
+++ b/tests/unit/plugins/filter/test_docker_image.py
@@ -0,0 +1,64 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import pytest
+from ansible_collections.vexxhost.atmosphere.plugins.filter.docker_image import (
+    docker_image,
+)
+
+
+@pytest.mark.parametrize(
+    "test_input,expected",
+    [
+        ("quay.io/vexxhost/glance:zed", "quay.io/vexxhost/glance:zed"),
+        (
+            "us-docker.pkg.dev/vexxhost-infra/openstack/heat:wallaby",
+            "us-docker.pkg.dev/vexxhost-infra/openstack/heat:wallaby",
+        ),
+        (
+            "k8s.gcr.io/sig-storage/csi-snapshotter:v4.2.0",
+            "k8s.gcr.io/sig-storage/csi-snapshotter:v4.2.0",
+        ),
+        ("docker.io/library/haproxy:2.5", "docker.io/library/haproxy:2.5"),
+        (
+            "k8s.gcr.io/ingress-nginx/controller:v1.1.1@sha256:0bc88eb15f9e7f84e8e56c14fa5735aaa488b840983f87bd79b1054190e660de",  # noqa E501
+            "k8s.gcr.io/ingress-nginx/controller:v1.1.1@sha256:0bc88eb15f9e7f84e8e56c14fa5735aaa488b840983f87bd79b1054190e660de",  # noqa E501
+        ),
+    ],
+)
+def test_docker_image_ref(test_input, expected):
+    assert docker_image(test_input, "ref") == expected
+
+
+@pytest.mark.parametrize(
+    "test_input,expected",
+    [
+        ("quay.io/vexxhost/glance:zed", "quay.io/vexxhost/glance"),
+        (
+            "us-docker.pkg.dev/vexxhost-infra/openstack/heat:wallaby",
+            "us-docker.pkg.dev/vexxhost-infra/openstack/heat",
+        ),
+        (
+            "k8s.gcr.io/sig-storage/csi-snapshotter:v4.2.0",
+            "k8s.gcr.io/sig-storage/csi-snapshotter",
+        ),
+        ("docker.io/library/haproxy:2.5", "docker.io/library/haproxy"),
+        (
+            "k8s.gcr.io/ingress-nginx/controller:v1.1.1@sha256:0bc88eb15f9e7f84e8e56c14fa5735aaa488b840983f87bd79b1054190e660de",  # noqa E501
+            "k8s.gcr.io/ingress-nginx/controller",
+        ),
+    ],
+)
+def test_docker_image_name(test_input, expected):
+    assert docker_image(test_input, "name") == expected