Add ansible-lint job

Sem-Ver: feature
Change-Id: I58c32382b8122c8e56e71c601c64dad411dbb687
diff --git a/releasenotes/notes/add-ansible-lint-c1e961c2fb88dbc7.yaml b/releasenotes/notes/add-ansible-lint-c1e961c2fb88dbc7.yaml
new file mode 100644
index 0000000..f5de6cc
--- /dev/null
+++ b/releasenotes/notes/add-ansible-lint-c1e961c2fb88dbc7.yaml
@@ -0,0 +1,3 @@
+---
+features:
+  - Added ``ansible-lint`` to all of the playbooks and roles.
\ No newline at end of file
diff --git a/roles/ceph_csi_rbd/meta/main.yml b/roles/ceph_csi_rbd/meta/main.yml
index b3836f8..38ad5f0 100644
--- a/roles/ceph_csi_rbd/meta/main.yml
+++ b/roles/ceph_csi_rbd/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for Ceph CSI RBD
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/ceph_csi_rbd/tasks/main.yml b/roles/ceph_csi_rbd/tasks/main.yml
index 43e8498..e8b782f 100644
--- a/roles/ceph_csi_rbd/tasks/main.yml
+++ b/roles/ceph_csi_rbd/tasks/main.yml
@@ -12,7 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- name: collect facts for all monitors
+- name: Collect facts for all monitors
   run_once: true
   delegate_to: "{{ item }}"
   delegate_facts: true
@@ -20,28 +20,33 @@
     gather_subset: network
   loop: "{{ groups[ceph_csi_rbd_mons_group] }}"
 
-- vexxhost.atmosphere.ceph_pool:
+- name: Create Ceph pool
+  vexxhost.atmosphere.ceph_pool:
     name: "{{ ceph_csi_rbd_pool }}"
     application: rbd
-    pg_autoscale_mode: on
+    pg_autoscale_mode: "on"
 
-- vexxhost.atmosphere.ceph_key:
+- name: Create {{ ceph_csi_rbd_user }} user
+  vexxhost.atmosphere.ceph_key:
     name: "{{ ceph_csi_rbd_user }}"
     caps:
       mon: profile rbd
       mgr: profile rbd pool={{ ceph_csi_rbd_pool }}
       osd: profile rbd pool={{ ceph_csi_rbd_pool }}
 
-- vexxhost.atmosphere.ceph_key:
+- name: Retrieve {{ ceph_csi_rbd_user }} keyring
+  vexxhost.atmosphere.ceph_key:
     name: "{{ ceph_csi_rbd_user }}"
     state: info
     output_format: json
   register: _ceph_key
 
-- ansible.builtin.set_fact:
+- name: Store keyring inside fact
+  ansible.builtin.set_fact:
     _ceph_rbd_csi_ceph_keyring: "{{ _ceph_key.stdout | from_json | first }}"
 
-- kubernetes.core.helm:
+- name: Deploy Helm chart
+  kubernetes.core.helm:
     name: ceph-csi-rbd
     chart_ref: ceph/ceph-csi-rbd
     chart_version: 3.5.1
diff --git a/roles/ceph_mgr/tasks/main.yml b/roles/ceph_mgr/tasks/main.yml
index 879928b..725c1ad 100644
--- a/roles/ceph_mgr/tasks/main.yml
+++ b/roles/ceph_mgr/tasks/main.yml
@@ -21,22 +21,30 @@
   ansible.builtin.file:
     path: "/var/lib/ceph/mgr/ceph-{{ inventory_hostname_short }}"
     state: directory
+    owner: ceph
+    group: ceph
+    mode: 0700
 
-- name: create mgr keyring
-  ansible.builtin.shell: |
-    ceph auth get-or-create mgr.{{ inventory_hostname_short }} mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-{{ inventory_hostname_short }}/keyring
-  args:
-    creates: "/var/lib/ceph/mgr/ceph-{{ inventory_hostname_short }}/keyring"
+- name: Create Ceph manager keyring
+  vexxhost.atmosphere.ceph_key:
+    name: "mgr.{{ inventory_hostname_short }}"
+    dest: "/var/lib/ceph/mgr/ceph-{{ inventory_hostname_short }}/keyring"
+    caps:
+      mon: allow profile mgr
+      osd: allow *
+      mds: allow *
+    owner: ceph
+    group: ceph
 
 - name: ensure permissions are fixed
   ansible.builtin.file:
     path: "/var/lib/ceph/mon/ceph-{{ inventory_hostname_short }}"
     owner: ceph
     group: ceph
-    recurse: yes
+    recurse: true
 
 - name: enable and start service
   ansible.builtin.service:
     name: "ceph-mgr@{{ inventory_hostname_short }}"
     state: started
-    enabled: yes
+    enabled: true
diff --git a/roles/ceph_mon/tasks/bootstrap-ceph.yml b/roles/ceph_mon/tasks/bootstrap-ceph.yml
index 15c81cc..19ab74a 100644
--- a/roles/ceph_mon/tasks/bootstrap-ceph.yml
+++ b/roles/ceph_mon/tasks/bootstrap-ceph.yml
@@ -12,45 +12,68 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+# TODO(mnaser): Move to using vexxhost.atmosphere.ceph_key
 - name: create monitor keyring
-  ansible.builtin.shell: |
-    ceph-authtool --create-keyring /tmp/ceph.mon.keyring --gen-key -n mon. --cap mon 'allow *'
+  ansible.builtin.command:
+    ceph-authtool --gen-key --create-keyring
+                  --name mon.
+                  --cap mon 'allow *'
+                  /tmp/ceph.mon.keyring
   args:
     creates: /tmp/ceph.mon.keyring
   when:
     - inventory_hostname == groups[ceph_mon_group][0]
 
+# TODO(mnaser): Move to using vexxhost.atmosphere.ceph_key
 - name: create admin keyring
-  ansible.builtin.shell: |
-    ceph-authtool --create-keyring /etc/ceph/ceph.client.admin.keyring --gen-key -n client.admin --cap mon 'allow *' --cap osd 'allow *' --cap mds 'allow *' --cap mgr 'allow *'
+  ansible.builtin.command:
+    ceph-authtool --gen-key --create-keyring
+                  --name client.admin
+                  --cap mon 'allow *'
+                  --cap osd 'allow *'
+                  --cap mds 'allow *'
+                  --cap mgr 'allow *'
+                  /etc/ceph/ceph.client.admin.keyring
   args:
     creates: /etc/ceph/ceph.client.admin.keyring
   when:
     - inventory_hostname == groups[ceph_mon_group][0]
 
+# TODO(mnaser): Move to using vexxhost.atmosphere.ceph_key
 - name: create bootstrap-osd keyring
-  ansible.builtin.shell: |
-    ceph-authtool --create-keyring /var/lib/ceph/bootstrap-osd/ceph.keyring --gen-key -n client.bootstrap-osd --cap mon 'profile bootstrap-osd' --cap mgr 'allow r'
+  ansible.builtin.command:
+    ceph-authtool --gen-key --create-keyring
+                  --name client.bootstrap-osd
+                  --cap mon 'profile bootstrap-osd'
+                  --cap mgr 'allow r'
+                  /var/lib/ceph/bootstrap-osd/ceph.keyring
   args:
     creates: /var/lib/ceph/bootstrap-osd/ceph.keyring
   when:
     - inventory_hostname == groups[ceph_mon_group][0]
 
+# TODO(mnaser): Move to using vexxhost.atmosphere.ceph_key
 - name: add admin keyring to monitor
-  ansible.builtin.shell: |
-    ceph-authtool /tmp/ceph.mon.keyring --import-keyring /etc/ceph/ceph.client.admin.keyring
+  ansible.builtin.command:
+    ceph-authtool --import-keyring /etc/ceph/ceph.client.admin.keyring
+                  /tmp/ceph.mon.keyring
   when:
     - inventory_hostname == groups[ceph_mon_group][0]
 
+# TODO(mnaser): Move to using vexxhost.atmosphere.ceph_key
 - name: add bootstrap-osd keyring to monitor
-  ansible.builtin.shell: |
-    ceph-authtool /tmp/ceph.mon.keyring --import-keyring /var/lib/ceph/bootstrap-osd/ceph.keyring
+  ansible.builtin.command:
+    ceph-authtool --import-keyring /var/lib/ceph/bootstrap-osd/ceph.keyring
+                  /tmp/ceph.mon.keyring
   when:
     - inventory_hostname == groups[ceph_mon_group][0]
 
 - name: create monmap
-  ansible.builtin.shell: |
-    monmaptool --create --add {{ inventory_hostname_short }} {{ ceph_mon_ip_address }} --fsid {{ ceph_mon_fsid }} /tmp/monmap
+  ansible.builtin.command:
+    monmaptool --create
+               --fsid {{ ceph_mon_fsid }}
+               --add {{ inventory_hostname_short }} {{ ceph_mon_ip_address }}
+               /tmp/monmap
   args:
     creates: /tmp/monmap
   when:
@@ -60,6 +83,9 @@
   ansible.builtin.file:
     path: "/var/lib/ceph/mon/ceph-{{ inventory_hostname_short }}"
     state: directory
+    owner: ceph
+    group: ceph
+    mode: 0700
   when:
     - inventory_hostname == groups[ceph_mon_group][0]
 
@@ -69,6 +95,9 @@
     section: global
     option: mon initial members
     value: "{{ inventory_hostname_short }}"
+    owner: ceph
+    group: ceph
+    mode: 0640
 
 - name: start monitor
   ansible.builtin.include_tasks: start-monitor.yml
diff --git a/roles/ceph_mon/tasks/main.yml b/roles/ceph_mon/tasks/main.yml
index d4117a0..c6d64b5 100644
--- a/roles/ceph_mon/tasks/main.yml
+++ b/roles/ceph_mon/tasks/main.yml
@@ -18,7 +18,7 @@
     install_recommends: false
 
 - name: set ceph monitor ip address
-  set_fact:
+  ansible.builtin.set_fact:
     ceph_mon_ip_address: "{{ ansible_default_ipv4.address }}"
 
 - name: generate basic configuration file
@@ -27,6 +27,9 @@
     section: global
     option: "{{ item.option }}"
     value: "{{ item.value }}"
+    owner: ceph
+    group: ceph
+    mode: 0640
   loop:
     - option: fsid
       value: "{{ ceph_mon_fsid }}"
@@ -70,12 +73,12 @@
   when: inventory_hostname != _ceph_mon_bootstrap_node
 
 - name: get monitor keyring
-  ansible.builtin.shell: ceph auth get mon. -o /tmp/ceph.mon.keyring
+  ansible.builtin.command: ceph auth get mon. -o /tmp/ceph.mon.keyring
   changed_when: false
   when: inventory_hostname != _ceph_mon_bootstrap_node
 
 - name: get monmap keyring
-  ansible.builtin.shell: ceph mon getmap -o /tmp/monmap
+  ansible.builtin.command: ceph mon getmap -o /tmp/monmap
   changed_when: false
   when: inventory_hostname != _ceph_mon_bootstrap_node
 
@@ -84,6 +87,6 @@
   when: inventory_hostname != _ceph_mon_bootstrap_node
 
 - name: enable msgr2
-  ansible.builtin.shell: ceph mon enable-msgr2
+  ansible.builtin.command: ceph mon enable-msgr2
   changed_when: false
   when: inventory_hostname == _ceph_mon_bootstrap_node
diff --git a/roles/ceph_mon/tasks/start-monitor.yml b/roles/ceph_mon/tasks/start-monitor.yml
index c25b9da..d2f46a0 100644
--- a/roles/ceph_mon/tasks/start-monitor.yml
+++ b/roles/ceph_mon/tasks/start-monitor.yml
@@ -23,7 +23,7 @@
     path: "/var/lib/ceph/mon/ceph-{{ inventory_hostname_short }}"
     owner: ceph
     group: ceph
-    recurse: yes
+    recurse: true
 
 # NOTE(mnaser): https://bugs.launchpad.net/ubuntu/+source/ceph/+bug/1917414/comments/30
 - name: workaround for aarch64 systems
@@ -32,6 +32,9 @@
     section: Service
     option: MemoryDenyWriteExecute
     value: false
+    owner: ceph
+    group: ceph
+    mode: 0644
   register: _ceph_aarch64_fix
   when: ansible_architecture == 'aarch64'
 
@@ -39,5 +42,5 @@
   ansible.builtin.service:
     name: "ceph-mon@{{ inventory_hostname_short }}"
     state: started
-    enabled: yes
+    enabled: true
     daemon_reload: "{{ _ceph_aarch64_fix.changed }}"
diff --git a/roles/ceph_osd/tasks/main.yml b/roles/ceph_osd/tasks/main.yml
index 271d880..0ea9046 100644
--- a/roles/ceph_osd/tasks/main.yml
+++ b/roles/ceph_osd/tasks/main.yml
@@ -21,7 +21,7 @@
   delegate_to: "{{ groups[ceph_osd_mons_group][0] }}"
   register: _ceph_fsid
   changed_when: false
-  ansible.builtin.shell: ceph fsid
+  ansible.builtin.command: ceph fsid
 
 - name: collect facts for all monitors
   delegate_to: "{{ item }}"
@@ -37,6 +37,9 @@
     section: global
     option: "{{ item.option }}"
     value: "{{ item.value }}"
+    owner: ceph
+    group: ceph
+    mode: 0640
   loop:
     - option: fsid
       value: "{{ _ceph_fsid.stdout | trim }}"
@@ -47,18 +50,24 @@
   delegate_to: "{{ groups[ceph_osd_mons_group][0] }}"
   register: _ceph_bootstrap_osd_keyring
   changed_when: false
-  ansible.builtin.shell: ceph auth get client.bootstrap-osd
+  ansible.builtin.command: ceph auth get client.bootstrap-osd
 
 - name: install bootstrap-osd keyring
   ansible.builtin.copy:
     content: "{{ _ceph_bootstrap_osd_keyring.stdout }}\n"
     dest: /var/lib/ceph/bootstrap-osd/ceph.keyring
+    owner: ceph
+    group: ceph
+    mode: 0640
 
 - name: workaround to allow usage of loop devices
   ansible.builtin.replace:
     path: /usr/lib/python3/dist-packages/ceph_volume/util/disk.py
     regexp: "'mpath']"
     replace: "'mpath', 'loop']"
+    owner: ceph
+    group: ceph
+    mode: 0640
   when: molecule | default(false)
 
 # NOTE(mnaser): https://bugs.launchpad.net/ubuntu/+source/ceph/+bug/1917414/comments/30
@@ -68,6 +77,9 @@
     section: Service
     option: MemoryDenyWriteExecute
     value: false
+    owner: ceph
+    group: ceph
+    mode: 0644
   register: _ceph_aarch64_fix
   when: ansible_architecture == 'aarch64'
 
@@ -83,5 +95,6 @@
   loop: "{{ ceph_osd_devices }}"
 
 - name: create osds for volumes which are not setup
+  changed_when: true
   ansible.builtin.command: /usr/sbin/ceph-volume lvm create --data {{ item }}
   loop: "{{ _ceph_osd_check.results | selectattr('rc', 'equalto', 1) | map(attribute='item') }}"
diff --git a/roles/cert_manager/meta/main.yml b/roles/cert_manager/meta/main.yml
index 1cf4a6e..23ab375 100644
--- a/roles/cert_manager/meta/main.yml
+++ b/roles/cert_manager/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for cert-manager
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/cilium/meta/main.yml b/roles/cilium/meta/main.yml
index fda3c18..4a45f6f 100644
--- a/roles/cilium/meta/main.yml
+++ b/roles/cilium/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for Cilium
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/containerd/tasks/main.yml b/roles/containerd/tasks/main.yml
index 77374ce..62ffbe2 100644
--- a/roles/containerd/tasks/main.yml
+++ b/roles/containerd/tasks/main.yml
@@ -28,6 +28,9 @@
   ansible.builtin.file:
     path: /etc/containerd
     state: directory
+    owner: root
+    group: root
+    mode: 0755
   notify:
     - Restart containerd
 
@@ -35,11 +38,14 @@
   ansible.builtin.template:
     src: config.toml.j2
     dest: /etc/containerd/config.toml
+    owner: root
+    group: root
+    mode: 0644
   notify:
     - Restart containerd
 
 - name: Force any restarts if necessary
-  meta: flush_handlers
+  ansible.builtin.meta: flush_handlers
 
 - name: Enable and start service
   ansible.builtin.service:
diff --git a/roles/helm_repository/meta/main.yml b/roles/helm_repository/meta/main.yml
index 05e1c2d..b488e3c 100644
--- a/roles/helm_repository/meta/main.yml
+++ b/roles/helm_repository/meta/main.yml
@@ -12,5 +12,15 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for managing Helm repositories
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - helm
diff --git a/roles/ingress_nginx/meta/main.yml b/roles/ingress_nginx/meta/main.yml
index bde6443..54da99c 100644
--- a/roles/ingress_nginx/meta/main.yml
+++ b/roles/ingress_nginx/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for ingress-nginx
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/kube_prometheus_stack/meta/main.yml b/roles/kube_prometheus_stack/meta/main.yml
index 137f7b5..c9b3a64 100644
--- a/roles/kube_prometheus_stack/meta/main.yml
+++ b/roles/kube_prometheus_stack/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for kube-prometheus-stack
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/kube_prometheus_stack/tasks/main.yml b/roles/kube_prometheus_stack/tasks/main.yml
index 22c3e4d..655ae9c 100644
--- a/roles/kube_prometheus_stack/tasks/main.yml
+++ b/roles/kube_prometheus_stack/tasks/main.yml
@@ -12,19 +12,23 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- ansible.builtin.slurp:
+- name: Retrieve "etcd" CA certificate
+  ansible.builtin.slurp:
     src: /etc/kubernetes/pki/etcd/ca.crt
   register: _etcd_ca_crt
 
-- ansible.builtin.slurp:
+- name: Retrieve "etcd" client certificate
+  ansible.builtin.slurp:
     src: /etc/kubernetes/pki/etcd/healthcheck-client.crt
   register: _etcd_healthcheck_client_crt
 
-- ansible.builtin.slurp:
+- name: Retrieve "etcd" client key
+  ansible.builtin.slurp:
     src: /etc/kubernetes/pki/etcd/healthcheck-client.key
   register: _etcd_healthcheck_client_key
 
-- kubernetes.core.helm:
+- name: Deploy Helm chart
+  kubernetes.core.helm:
     name: kube-prometheus-stack
     chart_ref: prometheus-community/kube-prometheus-stack
     chart_version: 30.2.0
@@ -183,12 +187,18 @@
                   labels:
                     severity: critical
                 - alert: CoreDNSErrorsHigh
-                  expr: sum(rate(coredns_dns_responses_total{job="coredns",rcode="SERVFAIL"}[5m])) / sum(rate(coredns_dns_responses_total{job="coredns"}[5m])) > 0.01
+                  expr:
+                    sum(rate(coredns_dns_responses_total{job="coredns",rcode="SERVFAIL"}[5m]))
+                    /
+                    sum(rate(coredns_dns_responses_total{job="coredns"}[5m])) > 0.01
                   for: 10m
                   labels:
                     severity: warning
                 - alert: CoreDNSErrorsHigh
-                  expr: sum(rate(coredns_dns_responses_total{job="coredns",rcode="SERVFAIL"}[5m])) / sum(rate(coredns_dns_responses_total{job="coredns"}[5m])) > 0.03
+                  expr:
+                    sum(rate(coredns_dns_responses_total{job="coredns",rcode="SERVFAIL"}[5m]))
+                    /
+                    sum(rate(coredns_dns_responses_total{job="coredns"}[5m])) > 0.03
                   for: 10m
                   labels:
                     severity: critical
@@ -200,12 +210,18 @@
                   labels:
                     severity: critical
                 - alert: CoreDNSForwardErrorsHigh
-                  expr: sum(rate(coredns_forward_responses_total{job="coredns",rcode="SERVFAIL"}[5m])) / sum(rate(coredns_forward_responses_total{job="coredns"}[5m])) > 0.01
+                  expr:
+                    sum(rate(coredns_forward_responses_total{job="coredns",rcode="SERVFAIL"}[5m]))
+                    /
+                    sum(rate(coredns_forward_responses_total{job="coredns"}[5m])) > 0.01
                   for: 10m
                   labels:
                     severity: warning
                 - alert: CoreDNSForwardErrorsHigh
-                  expr: sum(rate(coredns_forward_responses_total{job="coredns",rcode="SERVFAIL"}[5m])) / sum(rate(coredns_forward_responses_total{job="coredns"}[5m])) > 0.03
+                  expr:
+                    sum(rate(coredns_forward_responses_total{job="coredns",rcode="SERVFAIL"}[5m]))
+                    /
+                    sum(rate(coredns_forward_responses_total{job="coredns"}[5m])) > 0.03
                   for: 10m
                   labels:
                     severity: critical
@@ -256,7 +272,8 @@
                   labels:
                     severity: critical
 
-- kubernetes.core.k8s:
+- name: Create Secret with "etcd" TLS certificates
+  kubernetes.core.k8s:
     state: present
     definition:
       apiVersion: v1
diff --git a/roles/kubernetes/files/haproxy.yaml b/roles/kubernetes/files/haproxy.yaml
index 0c8f04f..0d6ea23 100644
--- a/roles/kubernetes/files/haproxy.yaml
+++ b/roles/kubernetes/files/haproxy.yaml
@@ -5,23 +5,23 @@
   namespace: kube-system
 spec:
   containers:
-  - image: haproxy:2.5
-    name: haproxy
-    livenessProbe:
-      failureThreshold: 8
-      httpGet:
-        host: localhost
-        path: /healthz
-        port: 6443
-        scheme: HTTPS
-    volumeMounts:
-    - mountPath: /usr/local/etc/haproxy/haproxy.cfg
-      name: haproxyconf
-      readOnly: true
+    - image: haproxy:2.5
+      name: haproxy
+      livenessProbe:
+        failureThreshold: 8
+        httpGet:
+          host: localhost
+          path: /healthz
+          port: 6443
+          scheme: HTTPS
+      volumeMounts:
+        - mountPath: /usr/local/etc/haproxy/haproxy.cfg
+          name: haproxyconf
+          readOnly: true
   hostNetwork: true
   volumes:
-  - hostPath:
-      path: /etc/haproxy/haproxy.cfg
-      type: FileOrCreate
-    name: haproxyconf
+    - hostPath:
+        path: /etc/haproxy/haproxy.cfg
+        type: FileOrCreate
+      name: haproxyconf
 status: {}
diff --git a/roles/kubernetes/files/keepalived.yaml b/roles/kubernetes/files/keepalived.yaml
index 643ebbe..5926af8 100644
--- a/roles/kubernetes/files/keepalived.yaml
+++ b/roles/kubernetes/files/keepalived.yaml
@@ -6,27 +6,27 @@
   namespace: kube-system
 spec:
   containers:
-  - name: keepalived
-    image: us-docker.pkg.dev/vexxhost-infra/openstack/keepalived:2.0.19
-    command: ["keepalived", "-f", "/etc/keepalived/keepalived.conf", "--dont-fork", "--log-console", "--log-detail", "--dump-conf"]
-    resources: {}
-    securityContext:
-      capabilities:
-        add:
-        - NET_ADMIN
-        - NET_BROADCAST
-        - NET_RAW
-    volumeMounts:
-    - mountPath: /etc/keepalived/keepalived.conf
-      name: config
-    - mountPath: /etc/keepalived/check_apiserver.sh
-      name: check
+    - name: keepalived
+      image: us-docker.pkg.dev/vexxhost-infra/openstack/keepalived:2.0.19
+      command: ["keepalived", "-f", "/etc/keepalived/keepalived.conf", "--dont-fork", "--log-console", "--log-detail", "--dump-conf"]
+      resources: {}
+      securityContext:
+        capabilities:
+          add:
+            - NET_ADMIN
+            - NET_BROADCAST
+            - NET_RAW
+      volumeMounts:
+        - mountPath: /etc/keepalived/keepalived.conf
+          name: config
+        - mountPath: /etc/keepalived/check_apiserver.sh
+          name: check
   hostNetwork: true
   volumes:
-  - hostPath:
-      path: /etc/keepalived/keepalived.conf
-    name: config
-  - hostPath:
-      path: /etc/keepalived/check_apiserver.sh
-    name: check
+    - hostPath:
+        path: /etc/keepalived/keepalived.conf
+      name: config
+    - hostPath:
+        path: /etc/keepalived/check_apiserver.sh
+      name: check
 status: {}
diff --git a/roles/kubernetes/meta/main.yml b/roles/kubernetes/meta/main.yml
index ca80af7..05a6305 100644
--- a/roles/kubernetes/meta/main.yml
+++ b/roles/kubernetes/meta/main.yml
@@ -12,5 +12,15 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for Kubernetes
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: containerd
diff --git a/roles/kubernetes/tasks/bootstrap-cluster.yml b/roles/kubernetes/tasks/bootstrap-cluster.yml
index 8231267..4696b42 100644
--- a/roles/kubernetes/tasks/bootstrap-cluster.yml
+++ b/roles/kubernetes/tasks/bootstrap-cluster.yml
@@ -20,7 +20,7 @@
       register: _kubernetes_stat
       loop: "{{ groups[kubernetes_control_plane_group] }}"
       delegate_to: "{{ item }}"
-      delegate_facts: True
+      delegate_facts: true
 
 - name: Pick node from pre-existing cluster
   ansible.builtin.set_fact:
@@ -40,6 +40,9 @@
   ansible.builtin.template:
     src: kubeadm.yaml.j2
     dest: /etc/kubernetes/kubeadm.yaml
+    owner: root
+    group: root
+    mode: 0640
   when: inventory_hostname == _kubernetes_bootstrap_node
 
 - name: Initialize cluster
diff --git a/roles/kubernetes/tasks/control-plane.yml b/roles/kubernetes/tasks/control-plane.yml
index cbb8752..829b82f 100644
--- a/roles/kubernetes/tasks/control-plane.yml
+++ b/roles/kubernetes/tasks/control-plane.yml
@@ -18,10 +18,16 @@
       ansible.builtin.file:
         dest: /etc/keepalived
         state: directory
+        owner: root
+        group: root
+        mode: 0755
     - name: Upload configuration
       ansible.builtin.template:
         src: keepalived.conf.j2
         dest: /etc/keepalived/keepalived.conf
+        owner: root
+        group: root
+        mode: 0644
     - name: Upload health check
       ansible.builtin.template:
         src: check_apiserver.sh.j2
@@ -31,6 +37,9 @@
       ansible.builtin.copy:
         src: keepalived.yaml
         dest: /etc/kubernetes/manifests/keepalived.yaml
+        owner: root
+        group: root
+        mode: 0644
 
 - name: Upload configuration for HAproxy
   block:
@@ -38,14 +47,23 @@
       ansible.builtin.file:
         dest: /etc/haproxy
         state: directory
+        owner: root
+        group: root
+        mode: 0755
     - name: Upload configuration
       ansible.builtin.template:
         src: haproxy.cfg.j2
         dest: /etc/haproxy/haproxy.cfg
+        owner: root
+        group: root
+        mode: 0644
     - name: Upload Kubernetes manifest
       ansible.builtin.copy:
         src: haproxy.yaml
         dest: /etc/kubernetes/manifests/haproxy.yaml
+        owner: root
+        group: root
+        mode: 0644
 
 - name: Bootstrap cluster
   include_tasks: bootstrap-cluster.yml
@@ -54,6 +72,9 @@
   ansible.builtin.file:
     path: /root/.kube
     state: directory
+    owner: root
+    group: root
+    mode: 0750
 
 - name: copy admin configuration file
   ansible.builtin.copy:
@@ -75,7 +96,7 @@
   run_once: true
   ansible.builtin.shell: |
     kubectl taint nodes --all node-role.kubernetes.io/master-
-  ignore_errors: true
+  failed_when: false
   changed_when: false
 
 - name: Add labels to control plane nodes
diff --git a/roles/kubernetes/tasks/join-cluster.yml b/roles/kubernetes/tasks/join-cluster.yml
index b65e347..1b3e6dc 100644
--- a/roles/kubernetes/tasks/join-cluster.yml
+++ b/roles/kubernetes/tasks/join-cluster.yml
@@ -20,8 +20,7 @@
 - name: Generate control-plane certificates for joining cluster
   run_once: true
   delegate_to: "{{ _kubernetes_bootstrap_node | default(groups[kubernetes_control_plane_group][0]) }}"
-  ansible.builtin.shell: |
-    kubeadm init phase upload-certs --upload-certs 2>/dev/null | grep -v upload-certs
+  ansible.builtin.command: kubeadm init phase upload-certs --upload-certs
   changed_when: false
   register: _kubeadm_init_upload_certs
   when:
@@ -50,6 +49,9 @@
   ansible.builtin.template:
     src: kubeadm.yaml.j2
     dest: /etc/kubernetes/kubeadm.yaml
+    owner: root
+    group: root
+    mode: 0640
   when:
     - not _stat_etc_kubernetes_kubelet_conf.stat.exists
 
diff --git a/roles/kubernetes/tasks/main.yml b/roles/kubernetes/tasks/main.yml
index 383ce1f..0195921 100644
--- a/roles/kubernetes/tasks/main.yml
+++ b/roles/kubernetes/tasks/main.yml
@@ -16,12 +16,20 @@
   ansible.builtin.copy:
     src: apt-key.gpg
     dest: /usr/share/keyrings/kubernetes-archive-keyring.gpg
+    owner: root
+    group: root
+    mode: 0644
   when:
     - kubernetes_repo_url == _kubernetes_upstream_apt_repository
 
 - name: Add repository
   ansible.builtin.apt_repository:
-    repo: "deb {% if kubernetes_repo_url == _kubernetes_upstream_apt_repository %}[signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg]{% endif %} {{ kubernetes_repo_url }} kubernetes-xenial main"
+    repo:
+      deb
+      {% if kubernetes_repo_url == _kubernetes_upstream_apt_repository %}[signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg]{% endif %}
+      {{ kubernetes_repo_url }}
+      kubernetes-xenial
+      main
     state: present
 
 - name: Setup version pins
@@ -43,6 +51,9 @@
   ansible.builtin.template:
     src: modules-load.conf.j2
     dest: /etc/modules-load.d/k8s.conf
+    owner: root
+    group: root
+    mode: 0644
 
 - name: Enable kernel modules in runtime
   community.general.modprobe:
diff --git a/roles/kubernetes/tasks/nodes.yml b/roles/kubernetes/tasks/nodes.yml
index bc11ac5..5b4f688 100644
--- a/roles/kubernetes/tasks/nodes.yml
+++ b/roles/kubernetes/tasks/nodes.yml
@@ -13,7 +13,7 @@
 # under the License.
 
 - name: Check if Kubernetes is already deployed
-  stat:
+  ansible.builtin.stat:
     path: /etc/kubernetes/kubelet.conf
   register: _kubernetes_kubelet
 
diff --git a/roles/kubernetes/templates/kubeadm.yaml.j2 b/roles/kubernetes/templates/kubeadm.yaml.j2
index cc30dfd..5598b07 100644
--- a/roles/kubernetes/templates/kubeadm.yaml.j2
+++ b/roles/kubernetes/templates/kubeadm.yaml.j2
@@ -26,7 +26,7 @@
 controlPlane:
   localAPIEndpoint:
     bindPort: 16443
-  certificateKey: {{ _kubeadm_init_upload_certs.stdout | trim }}
+  certificateKey: {{ _kubeadm_init_upload_certs.stdout_lines[-1] | trim }}
 {% endif %}
 {% endif %}
 ---
diff --git a/roles/node_feature_discovery/meta/main.yml b/roles/node_feature_discovery/meta/main.yml
index 503ce14..459a22e 100644
--- a/roles/node_feature_discovery/meta/main.yml
+++ b/roles/node_feature_discovery/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for node-feature-discovery
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/node_feature_discovery/tasks/main.yml b/roles/node_feature_discovery/tasks/main.yml
index 213c72f..2b9e8cb 100644
--- a/roles/node_feature_discovery/tasks/main.yml
+++ b/roles/node_feature_discovery/tasks/main.yml
@@ -12,7 +12,8 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-- kubernetes.core.helm:
+- name: Deploy Helm chart
+  kubernetes.core.helm:
     name: node-feature-discovery
     chart_ref: node-feature-discovery/node-feature-discovery
     chart_version: 0.10.0
diff --git a/roles/openstack_helm_cinder/meta/main.yml b/roles/openstack_helm_cinder/meta/main.yml
index 8ed7540..655009f 100644
--- a/roles/openstack_helm_cinder/meta/main.yml
+++ b/roles/openstack_helm_cinder/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for OpenStack Cinder
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_endpoints/tasks/main.yml b/roles/openstack_helm_endpoints/tasks/main.yml
index a73b6f9..a616f0d 100644
--- a/roles/openstack_helm_endpoints/tasks/main.yml
+++ b/roles/openstack_helm_endpoints/tasks/main.yml
@@ -15,13 +15,14 @@
 - name: Get Helm values if chart is provided
   block:
     - name: Get the default values for the Helm chart
-      ansible.builtin.shell: helm show values {{ openstack_helm_endpoints_repo_name }}/{{ openstack_helm_endpoints_chart }}
+      ansible.builtin.command: helm show values {{ openstack_helm_endpoints_repo_name }}/{{ openstack_helm_endpoints_chart }}
       changed_when: false
       register: _helm_show_values
 
     - name: Retrieve list of all the needed endpoints
       ansible.builtin.set_fact:
-        openstack_helm_endpoints_list: "{{ _helm_show_values.stdout | from_yaml | community.general.json_query('keys(endpoints)') | difference(_openstack_helm_endpoints_ignore) }}"
+        openstack_helm_endpoints_list: |-
+          {{ _helm_show_values.stdout | from_yaml | community.general.json_query('keys(endpoints)') | difference(_openstack_helm_endpoints_ignore) }}
   when:
     - openstack_helm_endpoints_list is not defined or openstack_helm_endpoints_list == None
 
diff --git a/roles/openstack_helm_glance/meta/main.yml b/roles/openstack_helm_glance/meta/main.yml
index a7eaa64..e50b7f0 100644
--- a/roles/openstack_helm_glance/meta/main.yml
+++ b/roles/openstack_helm_glance/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for OpenStack Glance
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_heat/meta/main.yml b/roles/openstack_helm_heat/meta/main.yml
index 3dfc746..3309539 100644
--- a/roles/openstack_helm_heat/meta/main.yml
+++ b/roles/openstack_helm_heat/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for OpenStack Heat
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_horizon/meta/main.yml b/roles/openstack_helm_horizon/meta/main.yml
index 8027f7c..b468259 100644
--- a/roles/openstack_helm_horizon/meta/main.yml
+++ b/roles/openstack_helm_horizon/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for OpenStack Horizon
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_horizon/vars/main.yml b/roles/openstack_helm_horizon/vars/main.yml
index b63c869..53ace33 100644
--- a/roles/openstack_helm_horizon/vars/main.yml
+++ b/roles/openstack_helm_horizon/vars/main.yml
@@ -37,7 +37,7 @@
           horizon_images_upload_mode: direct
           openstack_enable_password_retrieve: "True"
           raw:
-            WEBSSO_KEYSTONE_URL: "https://{{ openstack_helm_endpoints['identity']['scheme']['public'] }}://{{ openstack_helm_endpoints['identity']['host_fqdn_override']['public']['host'] }}/v3"
+            WEBSSO_KEYSTONE_URL: https://{{ openstack_helm_endpoints['identity']['host_fqdn_override']['public']['host'] }}/v3
       local_settings_d:
         _50_monasca_ui_settings: "{{ lookup('file', '50-monasca-ui-settings.py') }}"
       extra_panels:
diff --git a/roles/openstack_helm_infra_ceph_provisioners/meta/main.yml b/roles/openstack_helm_infra_ceph_provisioners/meta/main.yml
index 7f7df71..8a0117c 100644
--- a/roles/openstack_helm_infra_ceph_provisioners/meta/main.yml
+++ b/roles/openstack_helm_infra_ceph_provisioners/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for Ceph provisioners
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_infra_ceph_provisioners/tasks/main.yml b/roles/openstack_helm_infra_ceph_provisioners/tasks/main.yml
index b43c3b1..b76d60e 100644
--- a/roles/openstack_helm_infra_ceph_provisioners/tasks/main.yml
+++ b/roles/openstack_helm_infra_ceph_provisioners/tasks/main.yml
@@ -45,7 +45,7 @@
 - name: Generate Ceph endpoint list
   ansible.builtin.set_fact:
     _openstack_helm_infra_ceph_provisioners_ceph_monitors: |
-      {{ 
+      {{
         _openstack_helm_infra_ceph_provisioners_ceph_monitors | default([]) +
           [{'ip': item}]
       }}
diff --git a/roles/openstack_helm_infra_libvirt/meta/main.yml b/roles/openstack_helm_infra_libvirt/meta/main.yml
index 67fb174..249b087 100644
--- a/roles/openstack_helm_infra_libvirt/meta/main.yml
+++ b/roles/openstack_helm_infra_libvirt/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for Libvirt
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_infra_memcached/meta/main.yml b/roles/openstack_helm_infra_memcached/meta/main.yml
index 0157d71..ec3be63 100644
--- a/roles/openstack_helm_infra_memcached/meta/main.yml
+++ b/roles/openstack_helm_infra_memcached/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for Memcached
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_infra_openvswitch/meta/main.yml b/roles/openstack_helm_infra_openvswitch/meta/main.yml
index f31873c..085b6a5 100644
--- a/roles/openstack_helm_infra_openvswitch/meta/main.yml
+++ b/roles/openstack_helm_infra_openvswitch/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for Open vSwitch
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_infra_rabbitmq/meta/main.yml b/roles/openstack_helm_infra_rabbitmq/meta/main.yml
index 53614f8..27cf12a 100644
--- a/roles/openstack_helm_infra_rabbitmq/meta/main.yml
+++ b/roles/openstack_helm_infra_rabbitmq/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for RabbitMQ
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_keystone/meta/main.yml b/roles/openstack_helm_keystone/meta/main.yml
index 136ce7c..8c7d01b 100644
--- a/roles/openstack_helm_keystone/meta/main.yml
+++ b/roles/openstack_helm_keystone/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for OpenStack Keystone
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_migrate_to_percona_xtradb_cluster/tasks/main.yml b/roles/openstack_helm_migrate_to_percona_xtradb_cluster/tasks/main.yml
index 5091f6f..a45f7d0 100644
--- a/roles/openstack_helm_migrate_to_percona_xtradb_cluster/tasks/main.yml
+++ b/roles/openstack_helm_migrate_to_percona_xtradb_cluster/tasks/main.yml
@@ -35,14 +35,20 @@
     name: "{{ openstack_helm_migrate_to_percona_xtradb_cluster_release_name }}"
     release_namespace: "{{ openstack_helm_migrate_to_percona_xtradb_cluster_release_namespace }}"
   register: _openstack_helm_migrate_to_percona_xtradb_cluster_helm_info
-  failed_when: _openstack_helm_migrate_to_percona_xtradb_cluster_helm_info.status['values']['endpoints']['oslo_db'].get('hosts', {}).get('default', '') == 'percona-xtradb-haproxy'
+  failed_when: _oslo_db_endpoints.get('hosts', {}).get('default', '') == 'percona-xtradb-haproxy'
+  vars:
+    _oslo_db_endpoints: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_helm_info.status['values']['endpoints']['oslo_db'] }}"
 
 - name: Set facts for database endpoints
   ansible.builtin.set_fact:
-    _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_ip: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_service.resources[0]['spec']['clusterIP'] }}"
-    _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_password: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_helm_info.status['values']['endpoints']['oslo_db']['auth']['admin']['password'] }}"
-    _openstack_helm_migrate_to_percona_xtradb_cluster_ip: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_service.resources[0]['spec']['clusterIP'] }}"
-    _openstack_helm_migrate_to_percona_xtradb_cluster_password: "{{ openstack_helm_endpoints['oslo_db']['auth']['admin']['password'] }}"
+    _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_ip: |-
+      {{ _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_service.resources[0]['spec']['clusterIP'] }}
+    _openstack_helm_migrate_to_percona_xtradb_cluster_legacy_password: |-
+      {{ _openstack_helm_migrate_to_percona_xtradb_cluster_helm_info.status['values']['endpoints']['oslo_db']['auth']['admin']['password'] }}
+    _openstack_helm_migrate_to_percona_xtradb_cluster_ip: |-
+      {{ _openstack_helm_migrate_to_percona_xtradb_cluster_service.resources[0]['spec']['clusterIP'] }}
+    _openstack_helm_migrate_to_percona_xtradb_cluster_password: |-
+      {{ openstack_helm_endpoints['oslo_db']['auth']['admin']['password'] }}
 
 - name: Ensure PyMySQL packages are installed
   ansible.builtin.pip:
@@ -94,7 +100,11 @@
     login_host: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_ip }}"
     login_user: root
     login_password: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_password }}"
-    name: "{{ (openstack_helm_migrate_to_percona_xtradb_cluster_databases | length > 1) | ternary('all', openstack_helm_migrate_to_percona_xtradb_cluster_databases) }}"
+    name: |-
+      {{
+        (openstack_helm_migrate_to_percona_xtradb_cluster_databases | length > 1) |
+          ternary('all', openstack_helm_migrate_to_percona_xtradb_cluster_databases)
+      }}
     state: import
     target: "{{ _openstack_helm_migrate_to_percona_xtradb_cluster_file.path }}"
   async: 7200
diff --git a/roles/openstack_helm_neutron/meta/main.yml b/roles/openstack_helm_neutron/meta/main.yml
index 6c6c7e1..409b595 100644
--- a/roles/openstack_helm_neutron/meta/main.yml
+++ b/roles/openstack_helm_neutron/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for OpenStack Neutron
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_nova/meta/main.yml b/roles/openstack_helm_nova/meta/main.yml
index 6a725f3..a2003fe 100644
--- a/roles/openstack_helm_nova/meta/main.yml
+++ b/roles/openstack_helm_nova/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for OpenStack Nova
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_nova/vars/main.yml b/roles/openstack_helm_nova/vars/main.yml
index bc22c7c..0ac2aa6 100644
--- a/roles/openstack_helm_nova/vars/main.yml
+++ b/roles/openstack_helm_nova/vars/main.yml
@@ -84,7 +84,14 @@
         allowed_origin: "*"
         allow_headers: "X-Auth-Token,X-OpenStack-Nova-API-Version"
       filter_scheduler:
-        enabled_filters: ComputeFilter, AggregateTypeAffinityFilter, ComputeCapabilitiesFilter, PciPassthroughFilter, ImagePropertiesFilter, ServerGroupAntiAffinityFilter, ServerGroupAffinityFilter
+        enabled_filters:
+          ComputeFilter,
+          AggregateTypeAffinityFilter,
+          ComputeCapabilitiesFilter,
+          PciPassthroughFilter,
+          ImagePropertiesFilter,
+          ServerGroupAntiAffinityFilter,
+          ServerGroupAffinityFilter
         image_properties_default_architecture: x86_64
         max_instances_per_host: 200
       glance:
diff --git a/roles/openstack_helm_placement/meta/main.yml b/roles/openstack_helm_placement/meta/main.yml
index c0fb81a..83f5e78 100644
--- a/roles/openstack_helm_placement/meta/main.yml
+++ b/roles/openstack_helm_placement/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for OpenStack Placement
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/openstack_helm_senlin/meta/main.yml b/roles/openstack_helm_senlin/meta/main.yml
index 32e974e..77da952 100644
--- a/roles/openstack_helm_senlin/meta/main.yml
+++ b/roles/openstack_helm_senlin/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for OpenStack Senlin
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/percona_xtradb_cluster/meta/main.yml b/roles/percona_xtradb_cluster/meta/main.yml
index 6b1f74e..792c6b1 100644
--- a/roles/percona_xtradb_cluster/meta/main.yml
+++ b/roles/percona_xtradb_cluster/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for Percona XtraDB Cluster
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/roles/prometheus_pushgateway/meta/main.yml b/roles/prometheus_pushgateway/meta/main.yml
index 8da9c00..ca5fa1c 100644
--- a/roles/prometheus_pushgateway/meta/main.yml
+++ b/roles/prometheus_pushgateway/meta/main.yml
@@ -12,6 +12,16 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for Prometheus Pushgateway
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
 dependencies:
   - role: helm_repository
     vars:
diff --git a/tox.ini b/tox.ini
index 5ce257a..3c73ab2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -26,6 +26,13 @@
     python3 {toxinidir}/tools/generate-galaxy-yml.py
     ansible-galaxy collection install -f {toxinidir}
 
+[testenv:linters]
+deps =
+    {[testenv:build]deps}
+    ansible-lint
+commands =
+    ansible-lint {toxinidir}/roles {posargs}
+
 [testenv:build]
 deps =
     ansible-core
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
index 816452a..d871c20 100644
--- a/zuul.d/jobs.yaml
+++ b/zuul.d/jobs.yaml
@@ -20,8 +20,18 @@
       - zuul.d/playbooks/ansible-collection-atmosphere-check-commit/run.yml
 
 - job:
-    name: ansible-collection-atmosphere-tox-build
+    name: ansible-collection-atmosphere-tox
     parent: tox
+
+- job:
+    name: ansible-collection-atmosphere-tox-linters
+    parent: ansible-collection-atmosphere-tox
+    vars:
+      tox_envlist: linters
+
+- job:
+    name: ansible-collection-atmosphere-tox-build
+    parent: ansible-collection-atmosphere-tox
     post-run:
       - zuul.d/playbooks/ansible-collection-atmosphere-tox-build/post-run.yml
     vars:
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 4c0068d..1565625 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -18,12 +18,14 @@
         - ansible-collection-atmosphere-check-commit
         - ansible-collection-atmosphere-tox-build
         - ansible-collection-atmosphere-tox-molecule-default
+        - ansible-collection-atmosphere-tox-linters
         - opendev-tox-docs
     gate:
       jobs:
         - ansible-collection-atmosphere-check-commit
         - ansible-collection-atmosphere-tox-build
         - ansible-collection-atmosphere-tox-molecule-default
+        - ansible-collection-atmosphere-tox-linters
         - opendev-tox-docs
     promote:
       jobs: