Added support for cold & live migrations

This adds out of the box support for cold and live migrations
so that everything works just fine without any manual intervention.

Sem-Ver: feature
Change-Id: Ic421cd4f09231c18a3f304826af142ba81dae871
diff --git a/playbooks/generate_workspace.yml b/playbooks/generate_workspace.yml
index 4211fa0..9dec792 100644
--- a/playbooks/generate_workspace.yml
+++ b/playbooks/generate_workspace.yml
@@ -264,9 +264,49 @@
       with_lines: >
         ls {{ playbook_dir }}/../roles/*/defaults/main.yml |
           xargs grep undef |
-            egrep -v '(_host|region_name)' |
+            egrep -v '(_host|region_name|_ssh_key)' |
               cut -d':' -f2
 
+    - name: Generate temporary files for generating keys for missing variables
+      ansible.builtin.tempfile:
+        state: file
+        prefix: "{{ item }}"
+      register: _ssh_key_file
+      # NOTE(mnaser): We don't want to override existing secrets, so we generate
+      #               a new one if and only if it doesn't exist
+      when: item not in secrets
+      # NOTE(mnaser): This is absolutely hideous but there's no clean way of
+      #               doing this using `with_fileglob` or `with_filetree`
+      with_lines: >
+        ls {{ playbook_dir }}/../roles/*/defaults/main.yml |
+          xargs grep undef |
+            egrep '(_ssh_key)' |
+              cut -d':' -f2
+
+    - name: Generate SSH keys for missing variables
+      community.crypto.openssh_keypair:
+        path: "{{ item.path }}"
+        regenerate: full_idempotence
+      register: _openssh_keypair
+      loop: "{{ _ssh_key_file.results }}"
+      loop_control:
+        label: "{{ item.item }}"
+
+    - name: Set values for SSH keys
+      ansible.builtin.set_fact:
+        secrets: "{{ secrets | default({}) | combine({item.item: lookup('file', item.path)}) }}"
+      loop: "{{ _ssh_key_file.results }}"
+      loop_control:
+        label: "{{ item.item }}"
+
+    - name: Delete the temporary files generated for SSH keys
+      ansible.builtin.file:
+        path: "{{ item.path }}"
+        state: absent
+      loop: "{{ _ssh_key_file.results }}"
+      loop_control:
+        label: "{{ item.item }}"
+
     - name: Write new secrets file to disk
       ansible.builtin.copy:
         content: "{{ secrets | to_nice_yaml }}"
diff --git a/releasenotes/notes/add-ssh-keys-d3e86fce24365343.yaml b/releasenotes/notes/add-ssh-keys-d3e86fce24365343.yaml
new file mode 100644
index 0000000..e71e1f7
--- /dev/null
+++ b/releasenotes/notes/add-ssh-keys-d3e86fce24365343.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - Added automatic SSH key generation for workspace, as well as cold & live
+    migration support by enabling SSH keys.
diff --git a/roles/openstack_helm_nova/defaults/main.yml b/roles/openstack_helm_nova/defaults/main.yml
index f956af9..47f5bdb 100644
--- a/roles/openstack_helm_nova/defaults/main.yml
+++ b/roles/openstack_helm_nova/defaults/main.yml
@@ -71,6 +71,12 @@
 openstack_helm_nova_values: {}
 
                                                                    # ]]]
+# .. envvar:: openstack_helm_nova_ssh_key [[[
+#
+# Private SSH key used for cold & live migration
+openstack_helm_nova_ssh_key: "{{ undef(hint='You must specifiy an SSH key for Nova.') }}"
+
+                                                                   # ]]]
 # .. envvar:: openstack_helm_nova_flavors [[[
 #
 # List of flavors to provision inside Nova
diff --git a/roles/openstack_helm_nova/tasks/main.yml b/roles/openstack_helm_nova/tasks/main.yml
index db1f2eb..b48b1db 100644
--- a/roles/openstack_helm_nova/tasks/main.yml
+++ b/roles/openstack_helm_nova/tasks/main.yml
@@ -52,6 +52,36 @@
   when:
     - openstack_helm_nova_migrate_from_mariadb | bool
 
+- name: Generate public key for SSH private key
+  become: false
+  delegate_to: localhost
+  block:
+    - name: Generate temporary file for SSH public key
+      changed_when: false
+      ansible.builtin.tempfile:
+        state: file
+        prefix: nova_ssh_key_
+      register: _nova_ssh_key_tempfile
+    # NOTE(mnaser): It's important to add a trailing newline at the end of this
+    #               string or else `ssh-keygen` will not be happy.`
+    - name: Write contents of current private SSH key
+      changed_when: false
+      ansible.builtin.copy:
+        dest: "{{ _nova_ssh_key_tempfile.path }}"
+        content: "{{ openstack_helm_nova_ssh_key }}\n"
+    - name: Generate public key for SSH private key
+      changed_when: false
+      community.crypto.openssh_keypair:
+        path: "{{ _nova_ssh_key_tempfile.path }}"
+        regenerate: never
+      register: _nova_ssh_publickey
+  always:
+    - name: Delete temporary file for public SSH key
+      changed_when: false
+      ansible.builtin.file:
+        path: "{{ _nova_ssh_key_tempfile.path }}"
+        state: absent
+
 - name: Deploy Helm chart
   kubernetes.core.helm:
     name: "{{ openstack_helm_nova_chart_name }}"
diff --git a/roles/openstack_helm_nova/vars/main.yml b/roles/openstack_helm_nova/vars/main.yml
index 0ac2aa6..d2223fc 100644
--- a/roles/openstack_helm_nova/vars/main.yml
+++ b/roles/openstack_helm_nova/vars/main.yml
@@ -48,6 +48,11 @@
       nova_spiceproxy_assets: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
       nova_spiceproxy: "{{ openstack_helm_nova_image_repository }}/nova:{{ openstack_helm_nova_image_tag }}"
       rabbit_init: "{{ openstack_helm_nova_image_repository }}/rabbitmq:3.8.23-management"
+  network:
+    ssh:
+      enabled: true
+      public_key: "{{ _nova_ssh_publickey.public_key }}"
+      private_key: "{{ openstack_helm_nova_ssh_key }}"
   bootstrap:
     structured:
       flavors: