Added automatic secret generation

Sem-Ver: feature
Change-Id: Ie0b853b673e8ae518f57de1f03835152ea0d3890
diff --git a/molecule/default/create.yml b/molecule/default/create.yml
index bc41643..d033c36 100644
--- a/molecule/default/create.yml
+++ b/molecule/default/create.yml
@@ -12,6 +12,10 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+- import_playbook: vexxhost.atmosphere.generate_secrets
+  vars:
+    secrets_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/secrets.yml"
+
 - hosts: localhost
   connection: local
   gather_facts: false
@@ -89,4 +93,4 @@
             address: "{{ compute_ips[0] }}"
           - <<: *instance_config
             instance: "kvm2"
-            address: "{{ compute_ips[1] }}"
+            address: "{{ compute_ips[1] }}"
\ No newline at end of file
diff --git a/molecule/default/destroy.yml b/molecule/default/destroy.yml
index fdcd8ad..64c45e3 100644
--- a/molecule/default/destroy.yml
+++ b/molecule/default/destroy.yml
@@ -18,6 +18,7 @@
   no_log: "{{ molecule_no_log }}"
   vars:
     stack_name: "{{ lookup('env', 'STACK_NAME') | default('atmosphere', True) }}"
+    secrets_path: "{{ lookup('env', 'MOLECULE_EPHEMERAL_DIRECTORY') }}/secrets.yml"
   tasks:
     - os_stack:
         name: "{{ stack_name }}"
@@ -26,3 +27,7 @@
     - file:
         path: "{{ molecule_instance_config }}"
         state: absent
+
+    - copy:
+        dest: "{{ secrets_path }}"
+        content: "{}"
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
index ad7954a..bd08db2 100644
--- a/molecule/default/molecule.yml
+++ b/molecule/default/molecule.yml
@@ -42,8 +42,11 @@
     groups: *kvm_groups
 provisioner:
   name: ansible
-  env:
-    ANSIBLE_PIPELINING: "True"
+  options:
+    extra-vars: "@${MOLECULE_EPHEMERAL_DIRECTORY}/secrets.yml"
+  config_options:
+    ssh_connection:
+      pipelining: true
   inventory:
     group_vars:
       all:
@@ -54,36 +57,16 @@
         kubernetes_keepalived_interface: ens3
         kubernetes_keepalived_vip: 10.96.240.10
         openstack_helm_endpoints_region_name: RegionOne
-        # Memcached
-        openstack_helm_endpoints_memcached_secret_key: 7XsFMtMoUT9c8eCqbBNJycqwIepvLYzfOI2kpYhBUJSl2X5RDHiyolaQqQp36nVR
-        # RabbitMQ
-        openstack_helm_endpoints_rabbitmq_erlang_cookie: openstack-cookie
-        openstack_helm_endpoints_rabbitmq_admin_password: password123
         # Keystone
         openstack_helm_endpoints_keystone_api_host: "identity.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_keystone_mariadb_password: keystone-mariadb-password
-        openstack_helm_endpoints_keystone_rabbitmq_password: keystone-rabbitmq-password
-        openstack_helm_endpoints_keystone_admin_password: keystone-admin-password
         # Glance
         openstack_helm_endpoints_glance_api_host: "image.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_glance_mariadb_password: glance-mariadb-password
-        openstack_helm_endpoints_glance_rabbitmq_password: glance-rabbitmq-password
-        openstack_helm_endpoints_glance_keystone_password: glance-keystone-password
         # Cinder
         openstack_helm_endpoints_cinder_api_host: "volume.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_cinder_mariadb_password: cinder-mariadb-password
-        openstack_helm_endpoints_cinder_rabbitmq_password: cinder-rabbitmq-password
-        openstack_helm_endpoints_cinder_keystone_password: cinder-keystone-password
         # Placement
         openstack_helm_endpoints_placement_api_host: "placement.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_placement_keystone_password: placement-keystone-password
-        openstack_helm_endpoints_placement_mariadb_password: placement-mariadb-password
         # Neutron
         openstack_helm_endpoints_neutron_api_host: "network.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_neutron_mariadb_password: neutron-mariadb-password
-        openstack_helm_endpoints_neutron_rabbitmq_password: neutron-rabbitmq-password
-        openstack_helm_endpoints_neutron_keystone_password: neutron-keystone-password
-        openstack_helm_endpoints_neutron_metadata_secret: neutron-metadata-secret
         openstack_helm_neutron_values:
           conf:
             auto_bridge_add:
@@ -91,38 +74,17 @@
         # Nova
         openstack_helm_endpoints_nova_api_host: "compute.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
         openstack_helm_endpoints_nova_novnc_host: "vnc.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_nova_mariadb_password: nova-mariadb-password
-        openstack_helm_endpoints_nova_rabbitmq_password: nova-rabbitmq-password
-        openstack_helm_endpoints_nova_keystone_password: nova-keystone-password
         # Ironic
         openstack_helm_endpoints_ironic_api_host: "baremetal.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_ironic_mariadb_password: ironic-mariadb-password
-        openstack_helm_endpoints_ironic_rabbitmq_password: ironic-rabbitmq-password
-        openstack_helm_endpoints_ironic_keystone_password: ironic-keystone-password
         # Designate
         openstack_helm_endpoints_designate_api_host: "dns.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_designate_mariadb_password: designate-mariadb-password
-        openstack_helm_endpoints_designate_rabbitmq_password: designate-rabbitmq-password
-        openstack_helm_endpoints_designate_keystone_password: designate-keystone-password
         # Octavia
         openstack_helm_endpoints_octavia_api_host: "load-balancer.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_octavia_mariadb_password: octavia-mariadb-password
-        openstack_helm_endpoints_octavia_rabbitmq_password: octavia-rabbitmq-password
-        openstack_helm_endpoints_octavia_keystone_password: octavia-keystone-password
         # Senlin
         openstack_helm_endpoints_senlin_api_host: "clustering.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_senlin_mariadb_password: senlin-mariadb-password
-        openstack_helm_endpoints_senlin_rabbitmq_password: senlin-rabbitmq-password
-        openstack_helm_endpoints_senlin_keystone_password: senlin-keystone-password
         # Heat
         openstack_helm_endpoints_heat_api_host: "orchestration.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_endpoints_heat_keystone_password: heat-keystone-password
-        openstack_helm_endpoints_heat_trustee_keystone_password: heat-trustee-keystone-password
-        openstack_helm_endpoints_heat_stack_user_keystone_password: heat-stack-user-keystone-password
-        openstack_helm_endpoints_heat_mariadb_password: heat-mariadb-password
-        openstack_helm_endpoints_heat_rabbitmq_password: heat-rabbitmq-password
         openstack_helm_endpoints_heat_cfn_api_host: "cloudformation.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
-        openstack_helm_heat_auth_encryption_key: heat-auth-encryption-key
         # Horizon
         openstack_helm_endpoints_horizon_api_host: "dashboard.{{ hostvars['ctl1']['ansible_host'].replace('.', '-') }}.nip.io"
         openstack_helm_endpoints_horizon_mariadb_password: horizon-mariadb-password
diff --git a/playbooks/generate_secrets.yml b/playbooks/generate_secrets.yml
new file mode 100644
index 0000000..88dbf23
--- /dev/null
+++ b/playbooks/generate_secrets.yml
@@ -0,0 +1,32 @@
+---
+- hosts: localhost
+  gather_facts: false
+  tasks:
+    - name: Ensure the secrets file exists
+      ansible.builtin.file:
+        path: "{{ secrets_path }}"
+        state: touch
+
+    - name: Load the current secrets into a variable
+      ansible.builtin.include_vars:
+        file: "{{ secrets_path }}"
+        name: secrets
+
+    - name: Generate secrets for missing variables
+      ansible.builtin.set_fact:
+        secrets: "{{ secrets| default({}) | combine({item: lookup('password', '/dev/null chars=ascii_lowercase,ascii_uppercase,digits length=32')}) }}"
+      # 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 -v '(_host|region_name)' |
+              cut -d':' -f2
+
+    - name: Write new secrets file to disk
+      ansible.builtin.copy:
+        content: "{{ secrets | to_nice_yaml }}"
+        dest: "{{ secrets_path }}"
diff --git a/releasenotes/notes/add-secret-generation-3653426d798abfc4.yaml b/releasenotes/notes/add-secret-generation-3653426d798abfc4.yaml
new file mode 100644
index 0000000..f0e2aec
--- /dev/null
+++ b/releasenotes/notes/add-secret-generation-3653426d798abfc4.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - Added a playbook to automatically generate all secrets for all roles for
+    those which are not already defined.
+upgrade:
+  - When upgrading to this version, you'll need to make sure that you destroy
+    your existing Molecule testing environment before convering again since
+    it is now using automatically generated secrets instead of hard-coded
+    secrets.  The secrets are stored inside the ``MOLECULE_EPHEMERAL_DIRECTORY``.