Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 251 additions & 0 deletions src/playbooks/backup/backup.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
---
- name: Backup Foreman databases and configuration
hosts: quadlet
become: true
gather_facts: true
vars_files:
- "../../vars/defaults.yml"
- "../../vars/flavors/{{ flavor }}.yml"
- "../../vars/foreman.yml"
- "../../vars/database.yml"
- "../../vars/database_iop.yml"

vars:
backup_timestamp: "{{ ansible_date_time.iso8601_basic_short }}"
backup_dir_full: "{{ backup_dir }}/foreman-backup-{{ backup_timestamp }}"
service_stopped: false
postgresql_started: false
foreman_server_fqdn: "{{ ansible_fqdn }}"
# Timeout and retry settings
task_wait_retries: 60
task_wait_delay: 10
postgresql_ready_retries: 10
postgresql_ready_delay: 2
postgresql_stop_retries: 30
postgresql_stop_delay: 1

pre_tasks:
- name: Validate unsupported parameters
ansible.builtin.assert:
that:
- not online | default(false)
- not incremental | default(false)
- tar_volume_size is not defined
fail_msg: "Unsupported parameter used. Online backup, incremental backup, and tar volume size are not yet implemented."
quiet: true

- name: Ensure backup directory exists
ansible.builtin.file:
path: "{{ backup_dir }}"
state: directory
mode: '0755'

- name: Test write permissions
ansible.builtin.file:
path: "{{ backup_dir }}/.write_test"
state: touch
mode: '0644'
register: write_test
changed_when: false

- name: Remove write test file
ansible.builtin.file:
path: "{{ backup_dir }}/.write_test"
state: absent
when: write_test is succeeded
changed_when: false

tasks:
- name: Perform backup operations
block:
- name: Create timestamped backup directory
ansible.builtin.file:
path: "{{ backup_dir_full }}"
state: directory
mode: '0755'

- name: Run preflight checks
ansible.builtin.include_tasks:
file: tasks/preflight.yaml

- name: Stop Foreman services
ansible.builtin.systemd:
name: foreman.target
state: stopped

- name: Mark services as stopped
ansible.builtin.set_fact:
service_stopped: true

- name: Wait for PostgreSQL to fully stop
ansible.builtin.systemd:
name: postgresql.service
register: postgres_status
until: postgres_status.status.ActiveState == 'inactive'
retries: "{{ postgresql_stop_retries }}"
delay: "{{ postgresql_stop_delay }}"
when: database_mode == 'internal'
changed_when: false

- name: Start PostgreSQL for dumps
ansible.builtin.systemd:
name: postgresql.service
state: started
when: database_mode == 'internal'

- name: Mark PostgreSQL as started
ansible.builtin.set_fact:
postgresql_started: true
when: database_mode == 'internal'

- name: Wait for PostgreSQL readiness
ansible.builtin.command:
cmd: pg_isready -h {{ database_host }} -p {{ database_port }}
register: pg_ready
retries: "{{ postgresql_ready_retries }}"
delay: "{{ postgresql_ready_delay }}"
until: pg_ready.rc == 0
changed_when: false

- name: Initialize databases_config with Foreman database
ansible.builtin.set_fact:
databases_config:
- name: foreman
database: "{{ foreman_database_name }}"
host: "{{ foreman_database_host }}"
port: "{{ foreman_database_port }}"
user: "{{ foreman_database_user }}"
password: "{{ foreman_database_password }}"
no_log: true

- name: Add Katello databases (Candlepin and Pulp)
ansible.builtin.set_fact:
databases_config: "{{ databases_config + katello_databases }}"
vars:
katello_databases:
- name: candlepin
database: "{{ candlepin_database_name }}"
host: "{{ candlepin_database_host }}"
port: "{{ candlepin_database_port }}"
user: "{{ candlepin_database_user }}"
password: "{{ candlepin_database_password }}"
- name: pulp
database: "{{ pulp_database_name }}"
host: "{{ pulp_database_host }}"
port: "{{ pulp_database_port }}"
user: "{{ pulp_database_user }}"
password: "{{ pulp_database_password }}"
when: "'katello' in enabled_features"
no_log: true

- name: Add IOP databases
ansible.builtin.set_fact:
databases_config: "{{ databases_config + iop_databases }}"
vars:
iop_databases:
- name: iop_advisor
database: "{{ iop_advisor_database_name }}"
host: "{{ iop_advisor_database_host }}"
port: "{{ iop_advisor_database_port }}"
user: "{{ iop_advisor_database_user }}"
password: "{{ iop_advisor_database_password }}"
- name: iop_inventory
database: "{{ iop_inventory_database_name }}"
host: "{{ iop_inventory_database_host }}"
port: "{{ iop_inventory_database_port }}"
user: "{{ iop_inventory_database_user }}"
password: "{{ iop_inventory_database_password }}"
- name: iop_remediations
database: "{{ iop_remediation_database_name }}"
host: "{{ iop_remediation_database_host }}"
port: "{{ iop_remediation_database_port }}"
user: "{{ iop_remediation_database_user }}"
password: "{{ iop_remediation_database_password }}"
- name: iop_vmaas
database: "{{ iop_vmaas_database_name }}"
host: "{{ iop_vmaas_database_host }}"
port: "{{ iop_vmaas_database_port }}"
user: "{{ iop_vmaas_database_user }}"
password: "{{ iop_vmaas_database_password }}"
- name: iop_vulnerability
database: "{{ iop_vulnerability_database_name }}"
host: "{{ iop_vulnerability_database_host }}"
port: "{{ iop_vulnerability_database_port }}"
user: "{{ iop_vulnerability_database_user }}"
password: "{{ iop_vulnerability_database_password }}"
when: "'iop' in enabled_features"
no_log: true

- name: Build database names list for display
ansible.builtin.set_fact:
databases_to_backup: "{{ databases_config | map(attribute='database') | list }}"

- name: Dump databases
ansible.builtin.include_tasks:
file: tasks/database_dumps.yaml

- name: Backup foremanctl state
ansible.builtin.include_tasks:
file: tasks/foremanctl_state.yaml

- name: Backup pulp content
ansible.builtin.include_tasks:
file: tasks/pulp_content.yaml
when: not skip_pulp_content | default(false)

- name: Generate backup metadata
ansible.builtin.include_tasks:
file: tasks/metadata.yaml

- name: Stop PostgreSQL
ansible.builtin.systemd:
name: postgresql.service
state: stopped
when:
- database_mode == 'internal'
- postgresql_started | default(false)

- name: Mark PostgreSQL as stopped
ansible.builtin.set_fact:
postgresql_started: false
when: database_mode == 'internal'

- name: Start Foreman services
ansible.builtin.systemd:
name: foreman.target
state: started

- name: Mark services as started
ansible.builtin.set_fact:
service_stopped: false

- name: Display backup completion
ansible.builtin.debug:
msg: |
Backup completed successfully.
Location: {{ backup_dir_full }}
Databases: {{ databases_to_backup | join(', ') }}

rescue:
- name: Restore PostgreSQL on failure
ansible.builtin.systemd:
name: postgresql.service
state: stopped
when:
- database_mode == 'internal'
- postgresql_started | default(false)
failed_when: false

- name: Restore Foreman services on failure
ansible.builtin.systemd:
name: foreman.target
state: started
when: service_stopped | default(false)
failed_when: false

- name: Report failure
ansible.builtin.fail:
msg: |
Backup failed: {{ ansible_failed_result.msg | default('Unknown error') }}
Services have been restarted.
Partial backup may exist at: {{ backup_dir_full }}
34 changes: 34 additions & 0 deletions src/playbooks/backup/metadata.obsah.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
help: |
Create offline backup of Foreman databases and configuration

variables:
backup_dir:
parameter: backup_dir
help: Directory where backup files will be stored
type: AbsolutePath
persist: false

online:
help: Perform online backup without stopping services (not yet implemented)
action: store_true
persist: false

skip_pulp_content:
help: Skip Pulp content directory backup (not yet implemented)
action: store_true
persist: false

incremental:
help: Perform incremental backup (not yet implemented)
action: store_true
persist: false

tar_volume_size:
help: Split tar archives at specified size in MB (not yet implemented, for Pulp content only)
persist: false

wait_for_tasks:
help: Wait for running tasks to complete instead of failing immediately
action: store_true
persist: false
36 changes: 36 additions & 0 deletions src/playbooks/backup/tasks/database_dumps.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
# Dump all detected databases using pg_dump -Fc (PostgreSQL custom format, compressed)

- name: Dump databases
ansible.builtin.command:
cmd: >
pg_dump
-h {{ item.host }}
-p {{ item.port }}
-U {{ item.user }}
-Fc
-f {{ backup_dir_full }}/{{ item.name }}.dump
{{ item.database }}
environment:
PGPASSWORD: "{{ item.password }}"
loop: "{{ databases_config }}"
changed_when: true
no_log: true

- name: Calculate total backup size
ansible.builtin.find:
paths: "{{ backup_dir_full }}"
patterns: "*.dump"
register: backup_files

- name: Calculate total size
ansible.builtin.set_fact:
total_size_bytes: "{{ backup_files.files | map(attribute='size') | sum }}"

- name: Display backup summary
ansible.builtin.debug:
msg: |
Database dumps completed:
- Total files: {{ backup_files.matched }}
- Total size: {{ total_size_bytes | int | human_readable }}
- Location: {{ backup_dir_full }}
25 changes: 25 additions & 0 deletions src/playbooks/backup/tasks/foremanctl_state.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
- name: Check if foremanctl state directory exists
ansible.builtin.stat:
path: "{{ obsah_state_path }}"
register: foremanctl_state_dir

- name: Backup foremanctl state directory
community.general.archive:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to support incremental exports, tar --listed-incremental needs to be used. It seems this is not supported by community.general.archive, so it might be better to avoid its use altogether.

- name: Backup config files
  ansible.builtin.command:
    cmd: >
      tar --create --gzip
      --listed-incremental={{ backup_dir_full }}/.config.snar
      --ignore-failed-read
      --file {{ backup_dir_full }}/foremanctl_state.tar.gz
      {{ config_file_paths | join(' ') }}

Also we use config_files but foremanctl-state, bit of an inconsistency there.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no need to do incrementals for the configs. (yes, we do them today in foreman maintain, but I think it's more work than use)

path: "{{ obsah_state_path }}"
dest: "{{ backup_dir_full }}/foremanctl-state.tar.gz"
format: gz
mode: '0644'
when: foremanctl_state_dir.stat.exists
register: state_backup

- name: Verify foremanctl state backup
ansible.builtin.stat:
path: "{{ backup_dir_full }}/foremanctl-state.tar.gz"
register: state_stat
when: foremanctl_state_dir.stat.exists

- name: Display foremanctl state backup info
ansible.builtin.debug:
msg: "Backed up foremanctl state ({{ state_stat.stat.size | human_readable }})"
when: foremanctl_state_dir.stat.exists
Loading
Loading