diff --git a/roles/backup/defaults/main.yml b/roles/backup/defaults/main.yml index dbb48bf8..046fd48a 100644 --- a/roles/backup/defaults/main.yml +++ b/roles/backup/defaults/main.yml @@ -1,3 +1,8 @@ --- deployment_type: "awx" tower_postgres_image: postgres:12 + +# Secret Names +tower_secret_key_secret: "{{ meta.name }}-secret-key" +tower_admin_password_secret: "{{ meta.name }}-admin-password" +# tower_postgres_configuration_secret: "{{ meta.name }}-postgres-configuration" diff --git a/roles/backup/store_secrets.yml b/roles/backup/store_secrets.yml new file mode 100644 index 00000000..bd900004 --- /dev/null +++ b/roles/backup/store_secrets.yml @@ -0,0 +1,7 @@ +--- + +- name: Test Getting and Storing Secrets (OCP) + hosts: localhost + + tasks: + - import_tasks: tasks/secrets.yml diff --git a/roles/backup/tasks/cleanup.yml b/roles/backup/tasks/cleanup.yml new file mode 100644 index 00000000..9976a8c9 --- /dev/null +++ b/roles/backup/tasks/cleanup.yml @@ -0,0 +1,9 @@ +--- + +- name: Delete any existing management pod + community.kubernetes.k8s: + name: "{{ meta.name }}-db-management" + kind: Pod + namespace: "{{ meta.namespace }}" + state: absent + force: true diff --git a/roles/backup/tasks/init.yml b/roles/backup/tasks/init.yml new file mode 100644 index 00000000..87d4cf46 --- /dev/null +++ b/roles/backup/tasks/init.yml @@ -0,0 +1,54 @@ +--- + +- name: Delete any existing management pod + community.kubernetes.k8s: + name: "{{ meta.name }}-db-management" + kind: Pod + namespace: "{{ meta.namespace }}" + state: absent + force: true + wait: true + +# Check to make sure provided pvc exists, error loudly if not. Otherwise, the management pod will just stay in pending state forever. +- name: Check provided PVC exists + k8s_info: + name: "{{ tower_backup_pvc }}" + kind: PersistentVolumeClaim + namespace: "{{ meta.namespace }}" + register: provided_pvc + when: + - tower_backup_pvc != '' or tower_backup_pvc is defined + +- name: Fail early if pvc is defined but does not exist + fail: + msg: "{{ tower_backup_pvc }} does not exist, please create this pvc first." + when: provided_pvc.resources | length == 0 + +# If tower_backup_pvc is defined, use in management-pod.yml.j2 +- name: Set default pvc name + set_fact: + _default_backup_pvc: "{{ meta.name }}-backup-claim" + +- name: Set PVC to use for backup + set_fact: + backup_pvc: "{{ tower_backup_pvc | default(_default_backup_pvc, true)}}" + +# TODO: re-use the old pvc if already created (unless pvc is provided) +# TODO: allow users to configure their own storage class for dynamically creating a pvc? + +- name: Create PVC for backup + community.kubernetes.k8s: + kind: PersistentVolumeClaim + namespace: "{{ meta.namespace }}" + template: "backup_pvc.yml.j2" + when: + - tower_backup_pvc == '' or tower_backup_pvc is not defined + +- name: Create management pod from templated deployment config + community.kubernetes.k8s: + name: "{{ meta.name }}-db-management" + kind: Deployment + namespace: "{{ meta.namespace }}" + state: present + template: "management-pod.yml.j2" + wait: true diff --git a/roles/backup/tasks/main.yml b/roles/backup/tasks/main.yml index 7ad04265..52852290 100644 --- a/roles/backup/tasks/main.yml +++ b/roles/backup/tasks/main.yml @@ -1,163 +1,14 @@ --- -- include_tasks: init.yml +# - include_tasks: init.yml -- include_tasks: postgres.yml +- include_tasks: secrets.yml -- include_tasks: projects.yml +# - include_tasks: postgres.yml -- include_tasks: conf.yml +# +## - include_tasks: conf.yml +# +## - include_tasks: download.yml -- include_tasks: download.yml - -- name: Check for specified PostgreSQL configuration - k8s_info: - kind: Secret - namespace: '{{ meta.namespace }}' - name: '{{ tower_postgres_configuration_secret }}' - register: _custom_pg_config_resources - when: tower_postgres_configuration_secret | length - -- name: Check for default PostgreSQL configuration - k8s_info: - kind: Secret - namespace: '{{ meta.namespace }}' - name: '{{ meta.name }}-postgres-configuration' - register: _default_pg_config_resources - -- name: Set PostgreSQL configuration - set_fact: - pg_config: '{{ _custom_pg_config_resources["resources"] | default([]) | length | ternary(_custom_pg_config_resources, _default_pg_config_resources) }}' - -- name: Store Database Configuration - set_fact: - awx_postgres_user: "{{ pg_config['resources'][0]['data']['username'] | b64decode }}" - awx_postgres_pass: "{{ pg_config['resources'][0]['data']['password'] | b64decode }}" - awx_postgres_database: "{{ pg_config['resources'][0]['data']['database'] | b64decode }}" - awx_postgres_port: "{{ pg_config['resources'][0]['data']['port'] | b64decode }}" - awx_postgres_host: "{{ pg_config['resources'][0]['data']['host'] | b64decode }}" - -- name: Get the postgres pod information - k8s_info: - kind: Pod - namespace: '{{ meta.namespace }}' - label_selectors: - - "app={{ meta.name }}-postgres" - register: postgres_pod - until: "postgres_pod['resources'][0]['status']['phase'] == 'Running'" - delay: 5 - retries: 60 - -- name: Set the resource pod name as a variable. - set_fact: - postgres_pod_name: "{{ postgres_pod['resources'][0]['metadata']['name'] }}" - -- name: Determine the timestamp for the backup once for all nodes - set_fact: - now: '{{ lookup("pipe", "date +%F-%T") }}' - -- name: Delete any existing management pod - community.kubernetes.k8s: - name: "{{ meta.name }}-db-management" - kind: Pod - namespace: "{{ meta.namespace }}" - state: absent - force: true - wait: true - -# Check to make sure provided pvc exists, error loudly if not. Otherwise, the management pod will just stay in pending state forever. -- name: Check provided PVC exists - k8s_info: - name: "{{ tower_backup_pvc }}" - kind: PersistentVolumeClaim - namespace: "{{ meta.namespace }}" - register: provided_pvc - when: - - tower_backup_pvc != '' or tower_backup_pvc is defined - -- name: Fail early if pvc is defined but does not exist - fail: - msg: "{{ tower_backup_pvc }} does not exist, please create this pvc first." - when: provided_pvc.resources | length == 0 - -# If tower_backup_pvc is defined, use in management-pod.yml.j2 -- name: Set default pvc name - set_fact: - _default_backup_pvc: "{{ meta.name }}-backup-claim" - -- name: Set PVC to use for backup - set_fact: - backup_pvc: "{{ tower_backup_pvc | default(_default_backup_pvc, true)}}" - -# TODO: re-use the old pvc if already created (unless pvc is provided) -# TODO: allow users to configure their own storage class for dynamically creating a pvc? - -- name: Create PVC for backup - community.kubernetes.k8s: - kind: PersistentVolumeClaim - namespace: "{{ meta.namespace }}" - template: "backup_pvc.yml.j2" - when: - - tower_backup_pvc == '' or tower_backup_pvc is not defined - -- name: Create management pod from templated deployment config - community.kubernetes.k8s: - name: "{{ meta.name }}-db-management" - kind: Deployment - namespace: "{{ meta.namespace }}" - state: present - template: "management-pod.yml.j2" - wait: true - -- name: Set backup directory name - set_fact: - _backup_dir: "/backups/tower-openshift-backup-{{ now }}" - -- name: Create directory for backup - community.kubernetes.k8s_exec: - namespace: "{{ meta.namespace }}" - pod: "{{ meta.name }}-db-management" - command: >- - mkdir -p {{ _backup_dir }} - -- name: Precreate file for database dump - community.kubernetes.k8s_exec: - namespace: "{{ meta.namespace }}" - pod: "{{ meta.name }}-db-management" - command: >- - touch {{ _backup_dir }}/tower.db - -- name: Set permissions on file for database dump - community.kubernetes.k8s_exec: - namespace: "{{ meta.namespace }}" - pod: "{{ meta.name }}-db-management" - command: >- - chmod 0600 {{ _backup_dir }}/tower.db - -- name: Set pg_dump command - set_fact: - pgdump: >- - pg_dump --clean --create - -h {{ awx_postgres_host }} - -U {{ awx_postgres_user }} - -d {{ awx_postgres_database }} - -p {{ awx_postgres_port }} - -- name: Write pg_dump to backup on PVC - community.kubernetes.k8s_exec: - namespace: "{{ meta.namespace }}" - pod: "{{ meta.name }}-db-management" - command: >- - bash -c "PGPASSWORD={{ awx_postgres_pass }} {{ pgdump }} > {{ _backup_dir }}/tower.db" - register: data_migration - -# TODO: Backup secret key and other secrets - look at trad tower backup pattern -# TODO: Compare final backup tar with one from a trad tower - -- name: Delete any existing management pod - community.kubernetes.k8s: - name: "{{ meta.name }}-db-management" - kind: Pod - namespace: "{{ meta.namespace }}" - state: absent - force: true +- include_tasks: cleanup.yml diff --git a/roles/backup/tasks/postgres.yml b/roles/backup/tasks/postgres.yml new file mode 100644 index 00000000..f17987b8 --- /dev/null +++ b/roles/backup/tasks/postgres.yml @@ -0,0 +1,91 @@ +--- + - name: Check for specified PostgreSQL configuration + k8s_info: + kind: Secret + namespace: '{{ meta.namespace }}' + name: '{{ tower_postgres_configuration_secret }}' + register: _custom_pg_config_resources + when: tower_postgres_configuration_secret | length + + - name: Check for default PostgreSQL configuration + k8s_info: + kind: Secret + namespace: '{{ meta.namespace }}' + name: '{{ meta.name }}-postgres-configuration' + register: _default_pg_config_resources + + - name: Set PostgreSQL configuration + set_fact: + pg_config: '{{ _custom_pg_config_resources["resources"] | default([]) | length | ternary(_custom_pg_config_resources, _default_pg_config_resources) }}' + + - name: Store Database Configuration + set_fact: + awx_postgres_user: "{{ pg_config['resources'][0]['data']['username'] | b64decode }}" + awx_postgres_pass: "{{ pg_config['resources'][0]['data']['password'] | b64decode }}" + awx_postgres_database: "{{ pg_config['resources'][0]['data']['database'] | b64decode }}" + awx_postgres_port: "{{ pg_config['resources'][0]['data']['port'] | b64decode }}" + awx_postgres_host: "{{ pg_config['resources'][0]['data']['host'] | b64decode }}" + + - name: Get the postgres pod information + k8s_info: + kind: Pod + namespace: '{{ meta.namespace }}' + label_selectors: + - "app={{ meta.name }}-{{ deployment_type }}-postgres" + register: postgres_pod + until: "postgres_pod['resources'][0]['status']['phase'] == 'Running'" + delay: 5 + retries: 60 + + - name: Set the resource pod name as a variable. + set_fact: + postgres_pod_name: "{{ postgres_pod['resources'][0]['metadata']['name'] }}" + + - name: Determine the timestamp for the backup once for all nodes + set_fact: + now: '{{ lookup("pipe", "date +%F-%T") }}' + + - name: Set backup directory name + set_fact: + _backup_dir: "/backups/tower-openshift-backup-{{ now }}" + + - name: Create directory for backup + community.kubernetes.k8s_exec: + namespace: "{{ meta.namespace }}" + pod: "{{ meta.name }}-db-management" + command: >- + mkdir -p {{ _backup_dir }} + + - name: Precreate file for database dump + community.kubernetes.k8s_exec: + namespace: "{{ meta.namespace }}" + pod: "{{ meta.name }}-db-management" + command: >- + touch {{ _backup_dir }}/tower.db + + - name: Set permissions on file for database dump + community.kubernetes.k8s_exec: + namespace: "{{ meta.namespace }}" + pod: "{{ meta.name }}-db-management" + command: >- + chmod 0600 {{ _backup_dir }}/tower.db + + - name: Set pg_dump command + set_fact: + pgdump: >- + pg_dump --clean --create + -h {{ awx_postgres_host }} + -U {{ awx_postgres_user }} + -d {{ awx_postgres_database }} + -p {{ awx_postgres_port }} + + - name: Write pg_dump to backup on PVC + community.kubernetes.k8s_exec: + namespace: "{{ meta.namespace }}" + pod: "{{ meta.name }}-db-management" + command: >- + bash -c "PGPASSWORD={{ awx_postgres_pass }} {{ pgdump }} > {{ _backup_dir }}/tower.db" + register: data_migration + + # TODO: Backup secret key and other secrets - look at trad tower backup pattern + # TODO: Compare final backup tar with one from a trad tower diff --git a/roles/backup/tasks/secrets.yml b/roles/backup/tasks/secrets.yml new file mode 100644 index 00000000..98506dbc --- /dev/null +++ b/roles/backup/tasks/secrets.yml @@ -0,0 +1,59 @@ +--- + +# TODO: Get Secret_key value/s + +# TODO: Store Secret_key value/s in a way that can be made into another secret upon restore + +# The general idea here is that the user provides the name for the current deployment, we grab secrets based on that, then when it is restored, we restore to whatever name/namespace is specified at the time of restore + +- name: Make _secrets directory + file: + path: "{{ playbook_dir }}/_secrets" + state: directory + +- name: Get secret_key + k8s_info: + kind: Secret + namespace: '{{ meta.namespace }}' + name: '{{ tower_secret_key_secret }}' + register: _secret_key + +- name: Set secret key + set_fact: + secret_key: "{{ _secret_key['resources'][0]['data']['secret_key'] | b64decode }}" + +- name: Template secret_key definition + template: + src: secret_key.yml.j2 + dest: "{{ playbook_dir }}/_secrets/secrets.yml" + mode: '0600' + # dest: pvc # potentially just do a copy task, loop through definition files + +- name: Get admin_password + k8s_info: + kind: Secret + namespace: '{{ meta.namespace }}' + name: '{{ tower_admin_password_secret }}' + register: _admin_password +# TODO: check if admin_password secret name is provided, and check for that? use defaults.yml + +- name: Set admin_password + set_fact: + admin_password: "{{ _admin_password['resources'][0]['data']['password'] | b64decode }}" + +- name: Template admin_password definition + template: + src: admin_password.yml.j2 + dest: "{{ playbook_dir }}/_secrets/admin_password.yml" + mode: '0600' + + +# TODO: Secrets to back up: tower-secret-key, tower1-admin-password, tower1-app-credentials, tower1-broadcast-websocket, tower1-dockercfg-q8qd2, tower1-postgres-configuration +# Do we need the service-account-token? probably? `tower1-token-hn2hm`, tower1-token-slllw + + +# After copying secret files to the PVC, delete the local tmp copies +- name: Clean up _secrets directory + ansible.builtin.file: + path: "{{ playbook_dir }}/_secrets" + state: absent diff --git a/roles/backup/templates/admin_password.yml.j2 b/roles/backup/templates/admin_password.yml.j2 new file mode 100644 index 00000000..3d1df46e --- /dev/null +++ b/roles/backup/templates/admin_password.yml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: v1 +kind: Secret +metadata: +{% raw %} + name: '{{ meta.name }}' + namespace: '{{ meta.namespace }}' +{% endraw %} +stringData: + password: '{{ admin_password }}' diff --git a/roles/backup/templates/secret_key.yml.j2 b/roles/backup/templates/secret_key.yml.j2 new file mode 100644 index 00000000..febc23ee --- /dev/null +++ b/roles/backup/templates/secret_key.yml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: v1 +kind: Secret +metadata: +{% raw %} + name: '{{ meta.name }}' + namespace: '{{ meta.namespace }}' +{% endraw %} +stringData: + secret_key: '{{ secret_key }}'