ci: gather logs at end of job
diff --git a/.gitignore b/.gitignore
index 8ee9b25..548e34a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 .vscode
 doc/build/*
 doc/source/roles/*/defaults
+molecule/*/artifacts
 molecule/default/group_vars/*
 !molecule/default/group_vars/.gitkeep
 !molecule/default/group_vars/all
diff --git a/atmosphere/tasks/constants.py b/atmosphere/tasks/constants.py
index 4fc9aab..472a220 100644
--- a/atmosphere/tasks/constants.py
+++ b/atmosphere/tasks/constants.py
@@ -440,4 +440,3 @@
 HELM_RELEASE_DESIGNATE_NAME = "designate"
 
 HELM_RELEASE_HEAT_NAME = "heat"
-
diff --git a/molecule/default/destroy.yml b/molecule/default/destroy.yml
index 45b3601..31e1bfc 100644
--- a/molecule/default/destroy.yml
+++ b/molecule/default/destroy.yml
@@ -12,6 +12,95 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+# NOTE(mnaser): We're using this playbook to capture logs for the run since
+#               there is no Molecule phase that runs after the converge phase.
+- hosts: controllers[0]
+  gather_facts: false
+  vars:
+    logs_dir: /tmp/logs
+  tasks:
+    - name: End the play if the infrastructure is not deployed
+      ansible.builtin.meta: end_host
+      when:
+        - ansible_host is not defined
+
+    - name: Retrieve all container logs, current and previous (if they exist)
+      become: true
+      shell: |-
+        set -e
+        PARALLELISM_FACTOR=4
+        function get_namespaces () {
+          kubectl get namespaces -o name | awk -F '/' '{ print $NF }'
+        }
+        function get_pods () {
+          NAMESPACE=$1
+          kubectl get pods -n ${NAMESPACE} -o name | awk -F '/' '{ print $NF }' | xargs -L1 -P 1 -I {} echo ${NAMESPACE} {}
+        }
+        export -f get_pods
+        function get_pod_logs () {
+          NAMESPACE=${1% *}
+          POD=${1#* }
+          INIT_CONTAINERS=$(kubectl get pod $POD -n ${NAMESPACE} -o jsonpath="{.spec.initContainers[*].name}")
+          CONTAINERS=$(kubectl get pod $POD -n ${NAMESPACE} -o jsonpath="{.spec.containers[*].name}")
+          for CONTAINER in ${INIT_CONTAINERS} ${CONTAINERS}; do
+            echo "${NAMESPACE}/${POD}/${CONTAINER}"
+            mkdir -p "{{ logs_dir }}/pod-logs/${NAMESPACE}/${POD}"
+            mkdir -p "{{ logs_dir }}/pod-logs/failed-pods/${NAMESPACE}/${POD}"
+            kubectl logs ${POD} -n ${NAMESPACE} -c ${CONTAINER} > "{{ logs_dir }}/pod-logs/${NAMESPACE}/${POD}/${CONTAINER}.txt"
+            kubectl logs --previous ${POD} -n ${NAMESPACE} -c ${CONTAINER} > "{{ logs_dir }}/pod-logs/failed-pods/${NAMESPACE}/${POD}/${CONTAINER}.txt"
+          done
+          find {{ logs_dir }} -type f -empty -print -delete
+          find {{ logs_dir }} -empty -type d -delete
+        }
+        export -f get_pod_logs
+        get_namespaces | \
+          xargs -r -n 1 -P ${PARALLELISM_FACTOR} -I {} bash -c 'get_pods "$@"' _ {} | \
+          xargs -r -n 2 -P ${PARALLELISM_FACTOR} -I {} bash -c 'get_pod_logs "$@"' _ {}
+      args:
+        executable: /bin/bash
+      ignore_errors: True
+
+    - name: Upload logs to object storage
+      ignore_errors: True
+      when:
+        - lookup('env', 'GITHUB_RUN_ID') is defined
+        - lookup('env', 'GITHUB_RUN_NUMBER') is defined
+      vars:
+        build_id: "{{ lookup('env', 'GITHUB_RUN_ID') }}-{{ lookup('env', 'GITHUB_RUN_NUMBER') }}"
+        container_name: atmosphere-ci-logs
+      block:
+        - name: Authenticate to cloud to get token to use in Swift client
+          delegate_to: localhost
+          openstack.cloud.auth:
+          register: _auth
+
+        - name: Generate storage URL
+          set_fact:
+            storage_url: "{{ ((service_catalog | selectattr('name', 'equalto', 'swift') | first).endpoints | selectattr('interface', 'equalto', 'public') | first).url }}"
+
+        - name: Install Swift client
+          become: true
+          ansible.builtin.apt:
+            name: ['python3-swiftclient', 'tree']
+            state: present
+
+        - name: Generate listing for all files
+          become: true
+          shell: tree -H '.' --charset utf-8 -o {{ logs_dir }}/index.html {{ logs_dir }}
+
+        - name: Upload logs to swift
+          shell: |-
+            set -e
+            swift post -H "X-Container-Read: .r:*,.rlistings" {{ container_name }}
+            swift upload -H "X-Delete-After: 604800" -m 'web-index:index.html' --object-name {{ build_id }} {{ container_name }} {{ logs_dir }}
+          environment:
+            OS_STORAGE_URL: "{{ storage_url }}"
+            OS_AUTH_TOKEN: "{{ auth_token }}"
+
+        - name: Print logs URL
+          debug:
+            msg: "Logs are available at {{ storage_url }}/{{ container_name }}/{{ build_id }}/index.html"
+
 - hosts: localhost
   connection: local
   gather_facts: false