From 59afa2826095de1ff30cc308c0da459dcdb42817 Mon Sep 17 00:00:00 2001
From: Thomas Woerner <twoerner@redhat.com>
Date: Thu, 22 Oct 2020 13:02:21 +0200
Subject: [PATCH] New backup role

There is a new backup role in the roles folder:

    roles/ipabackup

This role allows to backup an IPA server, to copy a backup from the
server to the controller, to copy all backups from the server to the
controller, to remove a backup from the server, to remove all backups
from the server, to restore an IPA server locally and from the controller
and also to copy a backup from the controller to the server.

Here is the documentation for the role:

    roles/ipabackup/README.md

New example playbooks have been added:

    playbooks/backup-server.yml
    playbooks/backup-server-to-controller.yml
    playbooks/copy-backup-from-server.yml
    playbooks/copy-all-backups-from-server.yml
    playbooks/remove-backup-from-server.yml
    playbooks/remove-all-backups-from-server.yml
    playbooks/copy-backup-to-server.yml
    playbooks/restore-server-from-controller.yml
    playbooks/restore-server.yml
---
 playbooks/backup-server-to-controller.yml     |  12 +
 playbooks/backup-server.yml                   |   8 +
 playbooks/copy-all-backups-from-server.yml    |  12 +
 playbooks/copy-backup-from-controller.yml     |  12 +
 playbooks/copy-backup-from-server.yml         |  12 +
 playbooks/remove-all-backups-from-server.yml  |  11 +
 playbooks/remove-backup-from-server.yml       |  11 +
 playbooks/restore-server-from-controller.yml  |  13 +
 playbooks/restore-server.yml                  |  12 +
 roles/ipabackup/README.md                     | 336 ++++++++++++++++++
 roles/ipabackup/defaults/main.yml             |  16 +
 roles/ipabackup/meta/main.yml                 |  20 ++
 roles/ipabackup/tasks/backup.yml              |  39 ++
 .../tasks/copy_backup_from_server.yml         |  46 +++
 .../ipabackup/tasks/copy_backup_to_server.yml |  43 +++
 roles/ipabackup/tasks/get_ipabackup_dir.yml   |  12 +
 roles/ipabackup/tasks/main.yml                | 138 +++++++
 .../tasks/remove_backup_from_server.yml       |   5 +
 roles/ipabackup/tasks/restore.yml             | 147 ++++++++
 roles/ipabackup/vars/CentOS-7.yml             |   6 +
 roles/ipabackup/vars/CentOS-8.yml             |   1 +
 roles/ipabackup/vars/Fedora.yml               |   4 +
 roles/ipabackup/vars/OracleLinux-7.yml        |   1 +
 roles/ipabackup/vars/OracleLinux-8.yml        |   1 +
 roles/ipabackup/vars/RedHat-7.3.yml           |   6 +
 roles/ipabackup/vars/RedHat-7.yml             |   6 +
 roles/ipabackup/vars/RedHat-8.yml             |   6 +
 roles/ipabackup/vars/Ubuntu.yml               |   5 +
 roles/ipabackup/vars/default.yml              |   6 +
 29 files changed, 947 insertions(+)
 create mode 100644 playbooks/backup-server-to-controller.yml
 create mode 100644 playbooks/backup-server.yml
 create mode 100644 playbooks/copy-all-backups-from-server.yml
 create mode 100644 playbooks/copy-backup-from-controller.yml
 create mode 100644 playbooks/copy-backup-from-server.yml
 create mode 100644 playbooks/remove-all-backups-from-server.yml
 create mode 100644 playbooks/remove-backup-from-server.yml
 create mode 100644 playbooks/restore-server-from-controller.yml
 create mode 100644 playbooks/restore-server.yml
 create mode 100644 roles/ipabackup/README.md
 create mode 100644 roles/ipabackup/defaults/main.yml
 create mode 100644 roles/ipabackup/meta/main.yml
 create mode 100644 roles/ipabackup/tasks/backup.yml
 create mode 100644 roles/ipabackup/tasks/copy_backup_from_server.yml
 create mode 100644 roles/ipabackup/tasks/copy_backup_to_server.yml
 create mode 100644 roles/ipabackup/tasks/get_ipabackup_dir.yml
 create mode 100644 roles/ipabackup/tasks/main.yml
 create mode 100644 roles/ipabackup/tasks/remove_backup_from_server.yml
 create mode 100644 roles/ipabackup/tasks/restore.yml
 create mode 100644 roles/ipabackup/vars/CentOS-7.yml
 create mode 120000 roles/ipabackup/vars/CentOS-8.yml
 create mode 100644 roles/ipabackup/vars/Fedora.yml
 create mode 120000 roles/ipabackup/vars/OracleLinux-7.yml
 create mode 120000 roles/ipabackup/vars/OracleLinux-8.yml
 create mode 100644 roles/ipabackup/vars/RedHat-7.3.yml
 create mode 100644 roles/ipabackup/vars/RedHat-7.yml
 create mode 100644 roles/ipabackup/vars/RedHat-8.yml
 create mode 100644 roles/ipabackup/vars/Ubuntu.yml
 create mode 100644 roles/ipabackup/vars/default.yml

diff --git a/playbooks/backup-server-to-controller.yml b/playbooks/backup-server-to-controller.yml
new file mode 100644
index 00000000..c7a4348c
--- /dev/null
+++ b/playbooks/backup-server-to-controller.yml
@@ -0,0 +1,12 @@
+---
+- name: Playbook to backup IPA server to controller
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_to_controller: yes
+    # ipabackup_keep_on_server: yes
+
+  roles:
+  - role: ipabackup
+    state: present
diff --git a/playbooks/backup-server.yml b/playbooks/backup-server.yml
new file mode 100644
index 00000000..44ad3327
--- /dev/null
+++ b/playbooks/backup-server.yml
@@ -0,0 +1,8 @@
+---
+- name: Playbook to backup IPA server
+  hosts: ipaserver
+  become: true
+
+  roles:
+  - role: ipabackup
+    state: present
diff --git a/playbooks/copy-all-backups-from-server.yml b/playbooks/copy-all-backups-from-server.yml
new file mode 100644
index 00000000..93ad1abc
--- /dev/null
+++ b/playbooks/copy-all-backups-from-server.yml
@@ -0,0 +1,12 @@
+---
+- name: Playbook to copy all backups from IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: all
+    ipabackup_to_controller: yes
+
+  roles:
+  - role: ipabackup
+    state: copied
diff --git a/playbooks/copy-backup-from-controller.yml b/playbooks/copy-backup-from-controller.yml
new file mode 100644
index 00000000..578050a9
--- /dev/null
+++ b/playbooks/copy-backup-from-controller.yml
@@ -0,0 +1,12 @@
+---
+- name: Playbook to copy a backup from controller to the IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: ipaserver.test.local_ipa-full-2020-10-22-11-11-44
+    ipabackup_from_controller: yes
+
+  roles:
+  - role: ipabackup
+    state: copied
diff --git a/playbooks/copy-backup-from-server.yml b/playbooks/copy-backup-from-server.yml
new file mode 100644
index 00000000..22f117b6
--- /dev/null
+++ b/playbooks/copy-backup-from-server.yml
@@ -0,0 +1,12 @@
+---
+- name: Playbook to copy backup from IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: ipa-full-2020-10-22-11-11-44
+    ipabackup_to_controller: yes
+
+  roles:
+  - role: ipabackup
+    state: copied
diff --git a/playbooks/remove-all-backups-from-server.yml b/playbooks/remove-all-backups-from-server.yml
new file mode 100644
index 00000000..032ea3d8
--- /dev/null
+++ b/playbooks/remove-all-backups-from-server.yml
@@ -0,0 +1,11 @@
+---
+- name: Playbook to remove all backups from IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: all
+
+  roles:
+  - role: ipabackup
+    state: absent
diff --git a/playbooks/remove-backup-from-server.yml b/playbooks/remove-backup-from-server.yml
new file mode 100644
index 00000000..7205174e
--- /dev/null
+++ b/playbooks/remove-backup-from-server.yml
@@ -0,0 +1,11 @@
+---
+- name: Playbook to remove backup from IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: ipa-full-2020-10-22-11-11-44
+
+  roles:
+  - role: ipabackup
+    state: absent
diff --git a/playbooks/restore-server-from-controller.yml b/playbooks/restore-server-from-controller.yml
new file mode 100644
index 00000000..2431bc71
--- /dev/null
+++ b/playbooks/restore-server-from-controller.yml
@@ -0,0 +1,13 @@
+---
+- name: Playbook to restore IPA server from controller
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: ipaserver.el83.local_ipa-full-2020-10-22-11-11-44
+    ipabackup_password: SomeDMpassword
+    ipabackup_from_controller: yes
+
+  roles:
+  - role: ipabackup
+    state: restored
diff --git a/playbooks/restore-server.yml b/playbooks/restore-server.yml
new file mode 100644
index 00000000..fec18486
--- /dev/null
+++ b/playbooks/restore-server.yml
@@ -0,0 +1,12 @@
+---
+- name: Playbook to restore an IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: ipa-full-2020-10-22-11-11-44
+    ipabackup_password: SomeDMpassword
+
+  roles:
+  - role: ipabackup
+    state: restored
diff --git a/roles/ipabackup/README.md b/roles/ipabackup/README.md
new file mode 100644
index 00000000..435c292d
--- /dev/null
+++ b/roles/ipabackup/README.md
@@ -0,0 +1,336 @@
+ipabackup role
+==============
+
+Description
+-----------
+
+This role allows to backup an IPA server, to copy a backup from the server to the controller, to copy all backups from the server to the controller, to remove a backup from the server, to remove all backups from the server, to restore an IPA server locally and from the controller and also to copy a backup from the controller to the server.
+
+
+**Note**: The ansible playbooks and role require a configured ansible environment where the ansible nodes are reachable and are properly set up to have an IP address and a working package manager.
+
+
+Features
+--------
+* Server backup
+* Server backup to controller
+* Copy backup from server to controller
+* Copy all backups from server to controller
+* Remove backup from the server
+* Remove all backups from the server
+* Server restore from server local backup.
+* Server restore from controller.
+* Copy a backup from the controller to the server.
+
+
+Supported FreeIPA Versions
+--------------------------
+
+FreeIPA versions 4.5 and up are supported by the backup role.
+
+
+Supported Distributions
+-----------------------
+
+* RHEL/CentOS 7.6+
+* Fedora 26+
+* Ubuntu
+
+
+Requirements
+------------
+
+**Controller**
+* Ansible version: 2.8+
+
+**Node**
+* Supported FreeIPA version (see above)
+* Supported distribution (needed for package installation only, see above)
+
+
+Usage
+=====
+
+Example inventory file with fixed domain and realm, setting up of the DNS server and using forwarders from /etc/resolv.conf:
+
+```ini
+[ipaserver]
+ipaserver.example.com
+```
+
+Example playbook to create a backup on the IPA server locally:
+
+```yaml
+---
+- name: Playbook to backup IPA server
+  hosts: ipaserver
+  become: true
+
+  roles:
+  - role: ipabackup
+    state: present
+```
+
+
+Example playbook to create a backup of the IPA server that is transferred to the controller using the server name as prefix for the backup and removed on the server:
+
+```yaml
+---
+- name: Playbook to backup IPA server to controller
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_to_controller: yes
+    # ipabackup_keep_on_server: yes
+
+  roles:
+  - role: ipabackup
+    state: present
+```
+
+
+Example playbook to create a backup of the IPA server that is transferred to the controller using the server name as prefix for the backup and kept on the server:
+
+```yaml
+---
+- name: Playbook to backup IPA server to controller
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_to_controller: yes
+    ipabackup_keep_on_server: yes
+
+  roles:
+  - role: ipabackup
+    state: present
+```
+
+
+Copy backup `ipa-full-2020-10-01-10-00-00` from server to controller:
+
+```yaml
+---
+- name: Playbook to copy backup from IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: ipa-full-2020-10-01-10-00-00
+    ipabackup_to_controller: yes
+
+  roles:
+  - role: ipabackup
+    state: copied
+```
+
+
+Copy backups `ipa-full-2020-10-01-10-00-00` and `ipa-full-2020-10-02-10-00-00` from server to controller:
+
+```yaml
+---
+- name: Playbook to copy backup from IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name:
+    - ipa-full-2020-10-01-10-00-00
+    - ipa-full-2020-10-02-10-00-00
+    ipabackup_to_controller: yes
+
+  roles:
+  - role: ipabackup
+    state: copied
+```
+
+
+Copy all backups from server to controller that are following the backup naming scheme:
+
+```yaml
+---
+- name: Playbook to copy all backups from IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: all
+    ipabackup_to_controller: yes
+
+  roles:
+  - role: ipabackup
+    state: copied
+```
+
+
+Remove backup `ipa-full-2020-10-01-10-00-00` from server:
+
+```yaml
+---
+- name: Playbook to remove backup from IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: ipa-full-2020-10-01-10-00-00
+
+  roles:
+  - role: ipabackup
+    state: absent
+```
+
+
+Remove backups `ipa-full-2020-10-01-10-00-00` and `ipa-full-2020-10-02-10-00-00` from server:
+
+```yaml
+---
+- name: Playbook to remove backup from IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name:
+    - ipa-full-2020-10-01-10-00-00
+    - ipa-full-2020-10-02-10-00-00
+
+  roles:
+  - role: ipabackup
+    state: absent
+```
+
+
+Remove all backups from server that are following the backup naming scheme:
+
+```yaml
+---
+- name: Playbook to remove all backups from IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: all
+
+  roles:
+  - role: ipabackup
+    state: absent
+```
+
+
+Example playbook to restore an IPA server locally:
+
+```yaml
+---
+- name: Playbook to restore an IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: ipa-full-2020-10-22-11-11-44
+    ipabackup_password: SomeDMpassword
+
+  roles:
+  - role: ipabackup
+    state: restored
+```
+
+
+Example playbook to restore IPA server from controller:
+
+```yaml
+---
+- name: Playbook to restore IPA server from controller
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: ipaserver.test.local_ipa-full-2020-10-22-11-11-44
+    ipabackup_password: SomeDMpassword
+    ipabackup_from_controller: yes
+
+  roles:
+  - role: ipabackup
+    state: restored
+```
+
+
+Example playbook to copy a backup from controller to the IPA server:
+
+```yaml
+---
+- name: Playbook to copy a backup from controller to the IPA server
+  hosts: ipaserver
+  become: true
+
+  vars:
+    ipabackup_name: ipaserver.test.local_ipa-full-2020-10-22-11-11-44
+    ipabackup_from_controller: yes
+
+  roles:
+  - role: ipabackup
+    state: copied
+```
+
+
+Playbooks
+=========
+
+The example playbooks to do the backup, copy a backup and also to remove a backup, also to do the restore, copy a backup to the server are part of the repository in the playbooks folder.
+
+```
+backup-server.yml
+backup-server-to-controller.yml
+copy-all-backups-from-server.yml
+copy-backup-from-server.yml
+remove-all-backups-from-server.yml
+remove-backup-from-server.yml
+restore-server.yml
+restore-server-from-controller.yml
+copy-backup-from-controller.yml
+```
+
+Please remember to link or copy the playbooks to the base directory of ansible-freeipa if you want to use the roles within the source archive.
+
+
+Variables
+=========
+
+Base Variables
+--------------
+
+Variable | Description | Required
+-------- | ----------- | --------
+ipabackup_backend | The backend to restore within the instance or instances, str | no
+ipabackup_data | Backup only the data with `state: present` and restore only the data with `state: restored`, bool (default: `no`) | no
+ipabackup_disable_role_check | Perform the backup even if this host does not have all the roles used in the cluster. This is not recommended, bool (default: `no`) | no
+ipabackup_gpg | Encrypt the backup, bool (default: `no`) | no
+ipabackup_gpg_keyring | Full path to the GPG keyring without the file extension, only for GPG 1 and up to IPA 4.6 str | no
+ipabackup_instance | The 389-ds instance to restore (defaults to all found), str | no
+ipabackup_log_file | Log to the given file on server for `state: present` and `state: restored` only, string | no
+ipabackup_logs | Include log files in backup, bool (default: `no`) | no
+ipabackup_no_logs | Do not restore log files from the backup, bool (default: `no`) | no
+ipabackup_online | Perform the LDAP backups online for data only with `state: present` and perform the LDAP restore online for data only with `state: restored`. If `ipabackup_data` is not set it will automatically be enabled.  bool (default: `no`) | no
+ipabackup_password | The diretory manager password needed for restoring a backup with `state: restored`, str | no
+state | `present` to make a new backup, `absent` to remove a backup and `copied` to copy a backup from the server to the controller or from the controller to the server, `restored` to restore a backup. string (default: `present`) | yes
+
+
+Special Variables
+-----------------
+
+Variable | Description | Required
+-------- | ----------- | --------
+ipabackup_name | The IPA backup name(s). Only for removal of server local backup(s) with `state: absent`, to copy server local backup(s) to the controller with `state: copied` and `ipabackup_from_server` set, to copy a backup from the controller to the server with `state: copied` and `ipabackup_from_controller` set or to restore a backup with `state: restored` either locally on the server of from the controller with `ipabackup_from_controller` set. If `all` is used all available backups are copied or removed that are following the backup naming scheme. string list | no
+ipabackup_keep_on_server | Keep local copy of backup on server with `state: present` and `ipabackup_to_controller`, bool (default: `no`) | no
+ipabackup_to_controller | Copy backup to controller, prefixes backup with node name, remove backup on server if `ipabackup_keep_on_server` is not set, bool (default: `no`) | no
+ipabackup_controller_path | Pre existing path on controller to store the backup in with `state: present`, path on the controller to copy the backup from with `state: copied` and `ipabackup_from_controller` set also for the restore with `state: restored` and `ipabackup_from_controller` set. If this is not set, the current working dir is used. string | no
+ipabackup_name_prefix | Set prefix to use for backup directory on controller with `state: present` or `state: copied` and `ipabackup_to_controller` set, The default is the server FQDN, string | no
+ipabackup_from_controller | Copy backup from controller to server, restore if `state: restored`, copy backup to server if `state: copied`, bool (default: `no`) | no
+ipabackup_install_packages | Install needed packages to be able to apply the backup with `state: restored`, bool (default: `yes`) | no
+ipabackup_firewalld_zone | The value defines the firewall zone that will be used with `state: restored`. This needs to be an existing runtime and permanent zone, bool (default: `no`) | no
+ipabackup_setup_firewalld | The value defines if the needed services will automatically be opened in the firewall managed by firewalld with `state: restored`, bool (default: `yes`) | no
+
+
+Authors
+=======
+
+Thomas Woerner
diff --git a/roles/ipabackup/defaults/main.yml b/roles/ipabackup/defaults/main.yml
new file mode 100644
index 00000000..2debe18e
--- /dev/null
+++ b/roles/ipabackup/defaults/main.yml
@@ -0,0 +1,16 @@
+---
+# defaults file for ipabackup
+
+ipabackup_gpg: no
+ipabackup_data: no
+ipabackup_logs: no
+ipabackup_online: no
+ipabackup_disable_role_check: no
+ipabackup_no_logs: no
+
+### special ###
+ipabackup_keep_on_server: no
+ipabackup_to_controller: no
+ipabackup_from_controller: no
+ipabackup_install_packages: yes
+ipabackup_setup_firewalld: yes
diff --git a/roles/ipabackup/meta/main.yml b/roles/ipabackup/meta/main.yml
new file mode 100644
index 00000000..db6732af
--- /dev/null
+++ b/roles/ipabackup/meta/main.yml
@@ -0,0 +1,20 @@
+dependencies: []
+
+galaxy_info:
+  author: Thomas Woerner
+  description: A role to backup and restore an IPA server
+  company: Red Hat, Inc
+  license: GPLv3
+  min_ansible_version: 2.8
+  platforms:
+  - name: Fedora
+    versions:
+    - all
+  - name: EL
+    versions:
+    - 7
+    - 8
+  galaxy_tags:
+    - identity
+    - ipa
+    - freeipa
diff --git a/roles/ipabackup/tasks/backup.yml b/roles/ipabackup/tasks/backup.yml
new file mode 100644
index 00000000..f2bce9f9
--- /dev/null
+++ b/roles/ipabackup/tasks/backup.yml
@@ -0,0 +1,39 @@
+---
+# tasks file for ipabackup
+
+- name: Create backup
+  shell: >
+    ipa-backup
+    {{ "--gpg" if ipabackup_gpg | bool }}
+    {{ "--gpg-keyring="+ipabackup_gpg_keyring if ipabackup_gpg_keyring is defined }}
+    {{ "--data" if ipabackup_data | bool }}
+    {{ "--logs" if ipabackup_logs | bool }}
+    {{ "--online" if ipabackup_online | bool }}
+    {{ "--disable-role-check" if ipabackup_disable_role_check | bool }}
+    {{ "--log-file="+ipabackup_log_file if ipabackup_log_file is defined }}
+  register: result_ipabackup
+
+- block:
+  - name: Get ipabackup_item from stderr or stdout output
+    set_fact:
+      ipabackup_item: "{{ item | regex_search('\n.*/([^\n]+)','\\1') | first }}"
+    when: item.find("Backed up to "+ipabackup_dir+"/") > 0
+    with_items:
+    - "{{ result_ipabackup.stderr }}"
+    - "{{ result_ipabackup.stdout }}"
+    loop_control:
+      label: ""
+
+  - name: Fail on missing ipabackup_item
+    fail: msg="Failed to get ipabackup_item"
+    when: ipabackup_item is not defined
+
+  - name: Copy backup to controller
+    include_tasks: "{{ role_path }}/tasks/copy_backup_from_server.yml"
+    when: state|default("present") == "present"
+
+  - name: Remove backup on server
+    include_tasks: "{{ role_path }}/tasks/remove_backup_from_server.yml"
+    when: not ipabackup_keep_on_server
+
+  when: ipabackup_to_controller
diff --git a/roles/ipabackup/tasks/copy_backup_from_server.yml b/roles/ipabackup/tasks/copy_backup_from_server.yml
new file mode 100644
index 00000000..1cfef3de
--- /dev/null
+++ b/roles/ipabackup/tasks/copy_backup_from_server.yml
@@ -0,0 +1,46 @@
+---
+- name: Fail on invalid ipabackup_item
+  fail: msg="ipabackup_item {{ ipabackup_item }} is not valid"
+  when: ipabackup_item is not defined or
+        ipabackup_item | length < 1 or
+        (ipabackup_item.find("ipa-full-") == -1 and
+         ipabackup_item.find("ipa-data-") == -1)
+
+- name: Set controller destination directory
+  set_fact:
+    ipabackup_controller_dir:
+        "{{ ipabackup_controller_path | default(lookup('env','PWD')) }}/{{
+         ipabackup_name_prefix | default(ansible_fqdn) }}_{{
+         ipabackup_item }}/"
+
+- name: Stat backup on server
+  stat:
+    path: "{{ ipabackup_dir }}/{{ ipabackup_item }}"
+  register: result_backup_stat
+
+- name: Fail on missing backup directory
+  fail: msg="Unable to find backup {{ ipabackup_item }}"
+  when: result_backup_stat.stat.isdir is not defined
+
+- name: Get backup files to copy for "{{ ipabackup_item }}"
+  shell:
+    find . -type f | cut -d"/" -f 2
+  args:
+    chdir: "{{ ipabackup_dir }}/{{ ipabackup_item }}"
+  register: result_find_backup_files
+
+- name: Copy server backup files to controller
+  fetch:
+    flat: yes
+    src: "{{ ipabackup_dir }}/{{ ipabackup_item }}/{{ item }}"
+    dest: "{{ ipabackup_controller_dir }}"
+  with_items:
+  - "{{ result_find_backup_files.stdout_lines }}"
+
+- name: Fix file modes for backup on controller
+  file:
+    dest: "{{ ipabackup_controller_dir }}"
+    mode: u=rwX,go=
+    recurse: yes
+  delegate_to: localhost
+  become: no
diff --git a/roles/ipabackup/tasks/copy_backup_to_server.yml b/roles/ipabackup/tasks/copy_backup_to_server.yml
new file mode 100644
index 00000000..73c6ef39
--- /dev/null
+++ b/roles/ipabackup/tasks/copy_backup_to_server.yml
@@ -0,0 +1,43 @@
+---
+- name: Fail on invalid ipabackup_name
+  fail: msg="ipabackup_name {{ ipabackup_name }} is not valid"
+  when: ipabackup_name is not defined or
+        ipabackup_name | length < 1 or
+        (ipabackup_name.find("ipa-full-") == -1 and
+         ipabackup_name.find("ipa-data-") == -1)
+
+- name: Set controller source directory
+  set_fact:
+    ipabackup_controller_dir:
+      "{{ ipabackup_controller_path | default(lookup('env','PWD')) }}"
+
+- name: Set ipabackup_item
+  set_fact:
+    ipabackup_item:
+      "{{ ipabackup_name | regex_search('.*_(ipa-.+)','\\1') | first }}"
+  when: "'_ipa-' in ipabackup_name"
+
+- name: Set ipabackup_item
+  set_fact:
+    ipabackup_item: "{{ ipabackup_name }}"
+  when: "'_ipa-' not in ipabackup_name"
+
+- name: Stat backup to copy
+  stat:
+    path: "{{ ipabackup_controller_dir }}/{{ ipabackup_name }}"
+  register: result_backup_stat
+  delegate_to: localhost
+  become: no
+
+- name: Fail on missing backup to copy
+  fail: msg="Unable to find backup {{ ipabackup_name }}"
+  when: result_backup_stat.stat.isdir is not defined
+
+- name: Copy backup files to server for "{{ ipabackup_item }}"
+  copy:
+    src: "{{ ipabackup_controller_dir }}/{{ ipabackup_name }}/"
+    dest: "{{ ipabackup_dir }}/{{ ipabackup_item }}"
+    owner: root
+    group: root
+    mode: u=rw,go=r
+    directory_mode: u=rwx,go=
diff --git a/roles/ipabackup/tasks/get_ipabackup_dir.yml b/roles/ipabackup/tasks/get_ipabackup_dir.yml
new file mode 100644
index 00000000..41597e8d
--- /dev/null
+++ b/roles/ipabackup/tasks/get_ipabackup_dir.yml
@@ -0,0 +1,12 @@
+---
+- name: Get IPA_BACKUP_DIR dir from ipaplatform
+  command: "{{ ansible_playbook_python }}"
+  args:
+    stdin: |
+      from ipaplatform.paths import paths
+      print(paths.IPA_BACKUP_DIR)
+  register: result_ipaplatform_backup_dir
+
+- name: Set IPA backup dir
+  set_fact:
+    ipabackup_dir: "{{ result_ipaplatform_backup_dir.stdout_lines | first }}"
diff --git a/roles/ipabackup/tasks/main.yml b/roles/ipabackup/tasks/main.yml
new file mode 100644
index 00000000..148913f7
--- /dev/null
+++ b/roles/ipabackup/tasks/main.yml
@@ -0,0 +1,138 @@
+---
+# tasks file for ipabackup
+
+- name: Check for empty vars
+  fail: msg="Variable {{ item }} is empty"
+  when: "item in vars and not vars[item]"
+  with_items: "{{ ipabackup_empty_var_checks }}"
+  vars:
+    ipabackup_empty_var_checks:
+    - ipabackup_backend
+    - ipabackup_gpg_keyring
+    - ipabackup_instance
+    - ipabackup_log_file
+    - ipabackup_password
+    - ipabackup_name
+    - ipabackup_controller_path
+    - ipabackup_name_prefix
+    - ipabackup_firewalld_zone
+
+- name: Set ipabackup_data if ipabackup_data is not set but ipabackup_online is
+  set_fact:
+    ipabackup_data: yes
+  when: ipabackup_online | bool and not ipabackup_data | bool
+
+- name: Fail if ipabackup_from_controller and ipabackup_to_controller are set
+  fail: msg="ipabackup_from_controller and ipabackup_to_controller are set"
+  when: ipabackup_from_controller | bool and ipabackup_to_controller | bool
+
+- name: Get ipabackup_dir from IPA installation
+  include_tasks: "{{ role_path }}/tasks/get_ipabackup_dir.yml"
+
+- name: Backup IPA server
+  include_tasks: "{{ role_path }}/tasks/backup.yml"
+  when: state|default("present") == "present"
+
+- name: Fail for given ipabackup_name if state is not copied, restored or absent
+  fail: msg="ipabackup_name is given and state is not copied, restored or absent"
+  when: state is not defined or
+        (state != "copied" and state != "restored" and state != "absent") and
+        ipabackup_name is defined
+
+- name: Fail on missing ipabackup_name
+  fail: msg="ipabackup_name is not set"
+  when: (ipabackup_name is not defined or not ipabackup_name) and
+        state is defined and
+        (state == "copied" or state == "restored" or state == "absent")
+
+- block:
+  - name: Get list of all backups on IPA server
+    shell:
+      find . -name "ipa-full-*" -o -name "ipa-data-*" | cut -d"/" -f 2
+    args:
+      chdir: "{{ ipabackup_dir }}/"
+    register: result_backup_find_backup_files
+
+  - name: Set ipabackup_names using backup list
+    set_fact:
+      ipabackup_names: "{{ result_backup_find_backup_files.stdout_lines }}"
+
+  when: state is defined and
+        ((state == "copied" and ipabackup_to_controller) or
+         state == "absent") and
+        ipabackup_name is defined and ipabackup_name == "all"
+
+- block:
+  - name: Fail on ipabackup_name all
+    fail: msg="ipabackup_name can not be all in this case"
+    when: ipabackup_name is defined and ipabackup_name == "all"
+
+  - name: Set ipabackup_names from ipabackup_name string
+    set_fact:
+      ipabackup_names: ["{{ ipabackup_name }}"]
+    when: ipabackup_name | type_debug != "list"
+
+  - name: Set ipabackup_names from ipabackup_name list
+    set_fact:
+      ipabackup_names: "{{ ipabackup_name }}"
+    when: ipabackup_name | type_debug == "list"
+  when: ipabackup_names is not defined and ipabackup_name is defined
+
+- name: Set empty ipabackup_names if ipabackup_name is not defined
+  set_fact:
+    ipabackup_names: []
+  when: ipabackup_names is not defined and ipabackup_name is not defined
+
+- block:
+  - name: Copy backup from IPA server
+    include_tasks: "{{ role_path }}/tasks/copy_backup_from_server.yml"
+    vars:
+      ipabackup_item: "{{ main_item | basename }}"
+    with_items:
+    - "{{ ipabackup_names }}"
+    loop_control:
+      loop_var: main_item
+    when: state is defined and state == "copied"
+
+  - name: Remove backup from IPA server
+    include_tasks: "{{ role_path }}/tasks/remove_backup_from_server.yml"
+    vars:
+      ipabackup_item: "{{ main_item | basename }}"
+    with_items:
+    - "{{ ipabackup_names }}"
+    loop_control:
+      loop_var: main_item
+    when: state is defined and state == "absent"
+
+  when: state is defined and
+        ((state == "copied" and ipabackup_to_controller) or state == "absent")
+
+# Fail with more than one entry in ipabackup_names for copy to sever and
+# restore.
+
+- name: Fail to copy or restore more than one backup on the server
+  fail: msg="Only one backup can be copied to the server or restored"
+  when: state is defined and (state == "copied" or state == "restored") and
+        ipabackup_from_controller | bool and ipabackup_names | length != 1
+
+# Use only first item in ipabackup_names for copy to server and for restore.
+
+- block:
+  - name: Copy backup to server
+    include_tasks: "{{ role_path }}/tasks/copy_backup_to_server.yml"
+
+  - name: Restore IPA server after copy
+    include_tasks: "{{ role_path }}/tasks/restore.yml"
+    when: state|default("present") == "restored"
+
+  vars:
+    ipabackup_name: "{{ ipabackup_names[0] }}"
+  when: ipabackup_from_controller or
+        (state|default("present") == "copied" and not ipabackup_to_controller)
+
+- name: Restore IPA server
+  include_tasks: "{{ role_path }}/tasks/restore.yml"
+  vars:
+    ipabackup_item: "{{ ipabackup_names[0] | basename }}"
+  when: not ipabackup_from_controller and
+        state|default("present") == "restored"
diff --git a/roles/ipabackup/tasks/remove_backup_from_server.yml b/roles/ipabackup/tasks/remove_backup_from_server.yml
new file mode 100644
index 00000000..52c071cc
--- /dev/null
+++ b/roles/ipabackup/tasks/remove_backup_from_server.yml
@@ -0,0 +1,5 @@
+---
+- name: Remove backup "{{ ipabackup_item }}"
+  file:
+    path: "{{ ipabackup_dir }}/{{ ipabackup_item }}"
+    state: absent
diff --git a/roles/ipabackup/tasks/restore.yml b/roles/ipabackup/tasks/restore.yml
new file mode 100644
index 00000000..c7a491f6
--- /dev/null
+++ b/roles/ipabackup/tasks/restore.yml
@@ -0,0 +1,147 @@
+---
+# tasks file for ipabackup
+
+### VARIABLES
+
+- name: Import variables specific to distribution
+  include_vars: "{{ item }}"
+  with_first_found:
+    - "{{ role_path }}/vars/{{ ansible_distribution }}-{{ ansible_distribution_version }}.yml"
+    - "{{ role_path }}/vars/{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml"
+    - "{{ role_path }}/vars/{{ ansible_distribution }}.yml"
+    - "{{ role_path }}/vars/default.yml"
+
+### GET SERVICES FROM BACKUP
+
+- name: Stat backup on server
+  stat:
+    path: "{{ ipabackup_dir }}/{{ ipabackup_item }}"
+  register: result_backup_stat
+
+- name: Fail on missing backup directory
+  fail: msg="Unable to find backup {{ ipabackup_item }}"
+  when: result_backup_stat.stat.isdir is not defined
+
+- name: Stat header file in backup "{{ ipabackup_item }}"
+  stat:
+    path: "{{ ipabackup_dir }}/{{ ipabackup_item }}/header"
+  register: result_backup_header_stat
+
+- name: Fail on missing header file in backup
+  fail: msg="Unable to find backup {{ ipabackup_item }} header file"
+  when: result_backup_header_stat.stat.isreg is not defined
+
+- name: Get services from backup
+  shell: >
+    grep "^services = " "{{ ipabackup_dir }}/{{ ipabackup_item }}/header" | cut -d"=" -f2 | tr -d '[:space:]'
+  register: result_services_grep
+
+- name: Set ipabackup_services
+  set_fact:
+    ipabackup_services: "{{ result_services_grep.stdout.split(',') }}"
+    ipabackup_service_dns: DNS
+    ipabackup_service_adtrust: ADTRUST
+    ipabackup_service_ntp: NTP
+
+### INSTALL PACKAGES
+
+- block:
+  - name: Ensure that IPA server packages are installed
+    package:
+      name: "{{ ipaserver_packages }}"
+      state: present
+
+  - name: Ensure that IPA server packages for dns are installed
+    package:
+      name: "{{ ipaserver_packages_dns }}"
+      state: present
+    when: ipabackup_service_dns in ipabackup_services
+
+  - name: Ensure that IPA server packages for adtrust are installed
+    package:
+      name: "{{ ipaserver_packages_adtrust }}"
+      state: present
+    when: ipabackup_service_adtrust in ipabackup_services
+
+  - name: Ensure that firewalld packages are installed
+    package:
+      name: "{{ ipaserver_packages_firewalld }}"
+      state: present
+    when: ipabackup_setup_firewalld | bool
+
+  when: ipabackup_install_packages | bool
+
+### START FIREWALLD
+
+- block:
+  - name: Ensure that firewalld is running
+    systemd:
+      name: firewalld
+      enabled: yes
+      state: started
+
+  - name: Firewalld - Verify runtime zone "{{ ipabackup_firewalld_zone }}"
+    shell: >
+      firewall-cmd
+      --info-zone="{{ ipabackup_firewalld_zone }}"
+      >/dev/null
+    when: ipabackup_firewalld_zone is defined
+
+  - name: Firewalld - Verify permanent zone "{{ ipabackup_firewalld_zone }}"
+    shell: >
+      firewall-cmd
+      --permanent
+      --info-zone="{{ ipabackup_firewalld_zone }}"
+      >/dev/null
+    when: ipabackup_firewalld_zone is defined
+
+  when: ipabackup_setup_firewalld | bool
+
+### RESTORE
+
+- name: Restore backup
+  no_log: True
+  shell: >
+    ipa-restore
+    {{ ipabackup_item }}
+    --unattended
+    {{ "--password="+ipabackup_password if ipabackup_password is defined }}
+    {{ "--data" if ipabackup_data | bool }}
+    {{ "--online" if ipabackup_online | bool }}
+    {{ "--instance="+ipabackup_instance if ipabackup_instance is defined }}
+    {{ "--backend="+ipabackup_backend if ipabackup_backend is defined }}
+    {{ "--no-logs" if ipabackup_no_logs | bool }}
+    {{ "--log-file="+ipabackup_log_file if ipabackup_log_file is defined }}
+  register: result_iparestore
+  ignore_errors: yes
+
+- name: Report error for restore operation
+  debug:
+    msg: "{{ result_iparestore.stderr }}"
+  when: result_iparestore is failed
+  failed_when: yes
+
+### CONFIGURE FIREWALLD
+
+- name: Configure firewalld
+  command: >
+    firewall-cmd
+    --permanent
+    --zone="{{ ipabackup_firewalld_zone if ipabackup_firewalld_zone is defined }}"
+    --add-service=freeipa-ldap
+    --add-service=freeipa-ldaps
+    {{ "--add-service=freeipa-trust" if ipabackup_service_adtrust in ipabackup_services }}
+    {{ "--add-service=dns" if ipabackup_service_dns in ipabackup_services }}
+    {{ "--add-service=ntp" if ipabackup_service_ntp in ipabackup_services }}
+  when: ipabackup_setup_firewalld | bool
+
+- name: Configure firewalld runtime
+  command: >
+    firewall-cmd
+    --zone="{{ ipabackup_firewalld_zone if ipabackup_firewalld_zone is defined }}"
+    --add-service=freeipa-ldap
+    --add-service=freeipa-ldaps
+    {{ "--add-service=freeipa-trust" if ipabackup_service_adtrust in ipabackup_services }}
+    {{ "--add-service=dns" if ipabackup_service_dns in ipabackup_services }}
+    {{ "--add-service=ntp" if ipabackup_service_ntp in ipabackup_services }}
+  when: ipabackup_setup_firewalld | bool
diff --git a/roles/ipabackup/vars/CentOS-7.yml b/roles/ipabackup/vars/CentOS-7.yml
new file mode 100644
index 00000000..11863757
--- /dev/null
+++ b/roles/ipabackup/vars/CentOS-7.yml
@@ -0,0 +1,6 @@
+# defaults file for ipaserver
+# vars/rhel.yml
+ipaserver_packages: [ "ipa-server", "libselinux-python" ]
+ipaserver_packages_dns: [ "ipa-server-dns" ]
+ipaserver_packages_adtrust: [ "ipa-server-trust-ad" ]
+ipaserver_packages_firewalld: [ "firewalld" ]
\ No newline at end of file
diff --git a/roles/ipabackup/vars/CentOS-8.yml b/roles/ipabackup/vars/CentOS-8.yml
new file mode 120000
index 00000000..d49e1cd5
--- /dev/null
+++ b/roles/ipabackup/vars/CentOS-8.yml
@@ -0,0 +1 @@
+RedHat-8.yml
\ No newline at end of file
diff --git a/roles/ipabackup/vars/Fedora.yml b/roles/ipabackup/vars/Fedora.yml
new file mode 100644
index 00000000..77112041
--- /dev/null
+++ b/roles/ipabackup/vars/Fedora.yml
@@ -0,0 +1,4 @@
+ipaserver_packages: [ "freeipa-server" ]
+ipaserver_packages_dns: [ "freeipa-server-dns" ]
+ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
+ipaserver_packages_firewalld: [ "firewalld" ]
diff --git a/roles/ipabackup/vars/OracleLinux-7.yml b/roles/ipabackup/vars/OracleLinux-7.yml
new file mode 120000
index 00000000..852b838d
--- /dev/null
+++ b/roles/ipabackup/vars/OracleLinux-7.yml
@@ -0,0 +1 @@
+RedHat-7.yml
\ No newline at end of file
diff --git a/roles/ipabackup/vars/OracleLinux-8.yml b/roles/ipabackup/vars/OracleLinux-8.yml
new file mode 120000
index 00000000..d49e1cd5
--- /dev/null
+++ b/roles/ipabackup/vars/OracleLinux-8.yml
@@ -0,0 +1 @@
+RedHat-8.yml
\ No newline at end of file
diff --git a/roles/ipabackup/vars/RedHat-7.3.yml b/roles/ipabackup/vars/RedHat-7.3.yml
new file mode 100644
index 00000000..11863757
--- /dev/null
+++ b/roles/ipabackup/vars/RedHat-7.3.yml
@@ -0,0 +1,6 @@
+# defaults file for ipaserver
+# vars/rhel.yml
+ipaserver_packages: [ "ipa-server", "libselinux-python" ]
+ipaserver_packages_dns: [ "ipa-server-dns" ]
+ipaserver_packages_adtrust: [ "ipa-server-trust-ad" ]
+ipaserver_packages_firewalld: [ "firewalld" ]
\ No newline at end of file
diff --git a/roles/ipabackup/vars/RedHat-7.yml b/roles/ipabackup/vars/RedHat-7.yml
new file mode 100644
index 00000000..11863757
--- /dev/null
+++ b/roles/ipabackup/vars/RedHat-7.yml
@@ -0,0 +1,6 @@
+# defaults file for ipaserver
+# vars/rhel.yml
+ipaserver_packages: [ "ipa-server", "libselinux-python" ]
+ipaserver_packages_dns: [ "ipa-server-dns" ]
+ipaserver_packages_adtrust: [ "ipa-server-trust-ad" ]
+ipaserver_packages_firewalld: [ "firewalld" ]
\ No newline at end of file
diff --git a/roles/ipabackup/vars/RedHat-8.yml b/roles/ipabackup/vars/RedHat-8.yml
new file mode 100644
index 00000000..7f5ae464
--- /dev/null
+++ b/roles/ipabackup/vars/RedHat-8.yml
@@ -0,0 +1,6 @@
+# defaults file for ipaserver
+# vars/RedHat-8.yml
+ipaserver_packages: [ "@idm:DL1/server" ]
+ipaserver_packages_dns: [ "@idm:DL1/dns" ]
+ipaserver_packages_adtrust: [ "@idm:DL1/adtrust" ]
+ipaserver_packages_firewalld: [ "firewalld" ]
diff --git a/roles/ipabackup/vars/Ubuntu.yml b/roles/ipabackup/vars/Ubuntu.yml
new file mode 100644
index 00000000..d0e01ea8
--- /dev/null
+++ b/roles/ipabackup/vars/Ubuntu.yml
@@ -0,0 +1,5 @@
+# vars/Ubuntu.yml
+ipaserver_packages: [ "freeipa-server" ]
+ipaserver_packages_dns: [ "freeipa-server-dns" ]
+ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
+ipaserver_packages_firewalld: [ "firewalld" ]
diff --git a/roles/ipabackup/vars/default.yml b/roles/ipabackup/vars/default.yml
new file mode 100644
index 00000000..4d28ac65
--- /dev/null
+++ b/roles/ipabackup/vars/default.yml
@@ -0,0 +1,6 @@
+# defaults file for ipaserver
+# vars/default.yml
+ipaserver_packages: [ "ipa-server" ]
+ipaserver_packages_dns: [ "ipa-server-dns" ]
+ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
+ipaserver_packages_firewalld: [ "firewalld" ]
-- 
GitLab