From 664fad088694f819e52d5cd69751663f4e5b5827 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Sachse?= <Joerg.Sachse@slub-dresden.de>
Date: Tue, 9 Aug 2022 13:42:44 +0200
Subject: [PATCH] test: introduce GitLab-CI functionality

---
 .ansible-lint                                 | 45 +++++-----
 .config/molecule/config.yml                   |  2 +
 .gitignore                                    | 13 ++-
 .gitlab-ci.yml                                | 34 ++++++++
 .yamllint                                     | 32 ++++---
 ansible.cfg                                   |  3 +-
 handlers/main.yml                             | 12 +--
 meta/main.yml                                 | 44 +++++-----
 molecule/README.md                            | 85 -------------------
 molecule/default                              |  1 +
 molecule/default/converge.yml                 |  5 --
 molecule/default/molecule.yml                 | 36 --------
 molecule/default/prepare.yml                  | 40 ---------
 molecule/default/tests/conftest.py            | 20 -----
 molecule/default/tests/test_default.py        |  6 --
 .../playbooks}/INSTALL.rst                    | 11 +--
 molecule/resources/playbooks/README.md        |  3 +
 molecule/resources/playbooks/converge.yml     | 17 ++++
 molecule/resources/playbooks/prepare.yml      | 26 ++++++
 molecule/resources/playbooks/verify.yml       | 10 +++
 molecule/virtualbox/molecule.yml              | 41 +++++++++
 requirements.yml                              | 12 ---
 requirements.yml.example                      |  4 +
 site.yml                                      |  3 -
 tasks/configure_iptables_externalusers.yml    |  2 +-
 tasks/configure_sftp_server.yml               | 34 ++++----
 tasks/configure_ssh_keys.yml                  | 12 ++-
 tasks/create_users_groups.yml                 | 21 ++---
 tasks/install_ibmsp_client.yml                | 23 ++---
 tasks/main.yml                                | 14 +--
 30 files changed, 284 insertions(+), 327 deletions(-)
 create mode 100644 .config/molecule/config.yml
 create mode 100644 .gitlab-ci.yml
 delete mode 100644 molecule/README.md
 create mode 120000 molecule/default
 delete mode 100644 molecule/default/converge.yml
 delete mode 100644 molecule/default/molecule.yml
 delete mode 100644 molecule/default/prepare.yml
 delete mode 100644 molecule/default/tests/conftest.py
 delete mode 100644 molecule/default/tests/test_default.py
 rename molecule/{default => resources/playbooks}/INSTALL.rst (66%)
 create mode 100644 molecule/resources/playbooks/README.md
 create mode 100644 molecule/resources/playbooks/converge.yml
 create mode 100644 molecule/resources/playbooks/prepare.yml
 create mode 100644 molecule/resources/playbooks/verify.yml
 create mode 100644 molecule/virtualbox/molecule.yml
 delete mode 100644 requirements.yml
 create mode 100644 requirements.yml.example

diff --git a/.ansible-lint b/.ansible-lint
index 9c9323e..f18a647 100644
--- a/.ansible-lint
+++ b/.ansible-lint
@@ -6,7 +6,7 @@
 # and not relative to the CWD of execution. CLI arguments passed to the --exclude
 # option will be parsed relative to the CWD of execution.
 exclude_paths:
-  - .cache/ # implicit unless exclude_paths is defined in config
+  - .cache/    # implicit unless exclude_paths is defined in config
   - .git/
   - .githooks/
   - backups/
@@ -15,24 +15,19 @@ exclude_paths:
 # verbosity: 1
 
 # Mock modules or roles in order to pass ansible-playbook --syntax-check
-#mock_modules:
-#  - zuul_return
-#  # note the foo.bar is invalid as being neither a module or a collection
-#  - fake_namespace.fake_collection.fake_module
-#  - fake_namespace.fake_collection.fake_module.fake_submodule
-#mock_roles:
-#  - mocked_role
-#  - author.role_name # old standalone galaxy role
-#  - fake_namespace.fake_collection.fake_role # role within a collection
+# mock_modules:
+#   - zuul_return
+#   # note the foo.bar is invalid as being neither a module or a collection
+#   - fake_namespace.fake_collection.fake_module
+#   - fake_namespace.fake_collection.fake_module.fake_submodule
+# mock_roles:
+#   - mocked_role
+#   - author.role_name # old standalone galaxy role
+#   - fake_namespace.fake_collection.fake_role # role within a collection
 
 # Enable checking of loop variable prefixes in roles
 loop_var_prefix: "{role}_"
 
-# Enforce variable names to follow pattern below, in addition to Ansible own
-# requirements, like avoiding python identifiers. To disable add `var-naming`
-# to skip_list.
-var_naming_pattern: "^[a-z_][a-z0-9_]*$"
-
 use_default_rules: true
 # Load custom rules from this specific folder
 # rulesdir:
@@ -46,9 +41,9 @@ skip_list:
 # Any rule that has the 'opt-in' tag will not be loaded unless its 'id' is
 # mentioned in the enable_list:
 enable_list:
-  - empty-string-compare # opt-in
-  - no-log-password # opt-in
-  - no-same-owner # opt-in
+  - empty-string-compare    # opt-in
+  - no-log-password         # opt-in
+  - no-same-owner           # opt-in
   # add yaml here if you want to avoid ignoring yaml checks when yamllint
   # library is missing. Normally its absence just skips using that rule.
   - yaml
@@ -60,19 +55,19 @@ enable_list:
 warn_list:
   - skip_this_tag
   - git-latest
-  - experimental # experimental is included in the implicit list
+  - experimental    # experimental is included in the implicit list
   # - role-name
 
 # Offline mode disables installation of requirements.yml
 offline: false
 
 # Define required Ansible's variables to satisfy syntax check
-#extra_vars:
-#  foo: bar
-#  multiline_string_variable: |
-#    line1
-#    line2
-#  complex_variable: ":{;\t$()"
+# extra_vars:
+#   foo: bar
+#   multiline_string_variable: |
+#     line1
+#     line2
+#   complex_variable: ":{;\t$()"
 
 # Uncomment to enforce action validation with tasks, usually is not
 # needed as Ansible syntax check also covers it.
diff --git a/.config/molecule/config.yml b/.config/molecule/config.yml
new file mode 100644
index 0000000..ece7ff6
--- /dev/null
+++ b/.config/molecule/config.yml
@@ -0,0 +1,2 @@
+---
+prerun: false
diff --git a/.gitignore b/.gitignore
index 4ac23e9..ed78332 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,8 @@ Thumbs.db
 
 *.retry
 *.vault
+inventory.*
+inv.*
 
 # Vim #
 #######
@@ -73,7 +75,14 @@ tags
 .vagrant/
 *.box
 
-# Backups #
-###########
+# Temporary/Build/Backup #
+##########################
 
 backups/
+build/
+
+# CONFIDENTIAL #
+################
+
+ssh_host_*
+
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..0e67b77
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,34 @@
+---
+# A pipeline is composed of independent jobs that run scripts, grouped into stages.
+# Stages run in sequential order, but jobs within stages run in parallel.
+#
+# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages
+
+stages:          # List of stages for jobs, and their order of execution
+  - test
+
+default:
+  before_script:
+    - source /opt/molecule/bin/activate
+    - ansible --version
+    - molecule --version
+
+test-job:
+  stage: test
+  tags:
+    - "shell"
+  script:
+    # make sure that Ansible Vaults are present and can be decrypted
+    - echo "${VAULT_REPORTING}" > ../lza_reporting.pass
+    - echo "${VAULT_INSTALL_COMMON}" > ../lza_install_common.pass
+    - export ANSIBLE_VAULT_PASSWORD_FILE="../lza_reporting.pass, ../lza_install_common.pass
+    - rm -rf ../ansible_vaults/
+    - git clone https://gitlab+deploy-token-25:${VAULT_ACCESS_TOKEN}@git.slub-dresden.de/slub-referat-2-3/ansible_vaults.git ../ansible_vaults/; \
+    # run Molecule tests
+    - molecule syntax --scenario-name default
+    - molecule lint --scenario-name default
+    - molecule create --scenario-name default
+    - molecule converge --scenario-name default
+    - molecule idempotence --scenario-name default
+    # - molecule verify --scenario-name default
+    - molecule destroy --scenario-name default
diff --git a/.yamllint b/.yamllint
index 7c0f15c..8827676 100644
--- a/.yamllint
+++ b/.yamllint
@@ -1,7 +1,5 @@
 ---
-# based on documentation available at
-# https://yamllint.readthedocs.io/en/stable/rules.html
-
+# Based on ansible-lint config
 extends: default
 
 rules:
@@ -11,13 +9,25 @@ rules:
   brackets:
     max-spaces-inside: 1
     level: error
-  comments:
-    min-spaces-from-content: 4
+  colons:
+    max-spaces-after: -1
+    level: error
+  commas:
+    max-spaces-after: -1
+    level: error
+  comments: disable
   comments-indentation: disable
-  document-end: disable
-  document-start:
-    level: warning
-  octal-values:
-    forbid-explicit-octal: false
+  document-start: disable
+  empty-lines:
+    max: 3
+    level: error
+  hyphens:
+    level: error
+  indentation: disable
+  key-duplicates: enable
   line-length: disable
-  truthy: enable
+  new-line-at-end-of-file: disable
+  new-lines:
+    type: unix
+  trailing-spaces: disable
+  truthy: disable
diff --git a/ansible.cfg b/ansible.cfg
index 21d00f5..8d086fe 100644
--- a/ansible.cfg
+++ b/ansible.cfg
@@ -1,7 +1,8 @@
 [defaults]
 # If set, configures the path to the Vault password file as an alternative to
 # specifying --vault-password-file on the command line.
-vault_identity_list = ../lza_reporting.pass, ../lza_install_common.pass, ../lza_server_hardening.pass, ../slub_osquery.pass
+#vault_identity_list = ../lza_reporting.pass, ../lza_install_common.pass, ../lza_server_hardening.pass, ../slub_osquery.pass
+vault_identity_list = ../lza_reporting.pass
 
 # Path to default inventory file
 # Administrators can override this by using the "-i <inventoryfile>" CLI
diff --git a/handlers/main.yml b/handlers/main.yml
index 9095551..5dc8e1e 100644
--- a/handlers/main.yml
+++ b/handlers/main.yml
@@ -1,29 +1,29 @@
 ---
 - name: Ordner für iptables-Config erstellen
-  file:
+  ansible.builtin.file:
     path: "/etc/iptables"
     state: directory
     owner: "root"
     group: "root"
-    mode: 0755
+    mode: "0755"
   listen: "save iptables rules"
 
 - name: install netfilter-persistent to be able to save iptables rules
-  apt:
+  ansible.builtin.apt:
     name: "netfilter-persistent"
     state: present
   listen: "save iptables rules"
 
 - name: save iptables rules
-  command: "netfilter-persistent save"
+  ansible.builtin.command: "netfilter-persistent save"
   listen: "save iptables rules"
 
 - name: restart sshd
-  systemd:
+  ansible.builtin.systemd:
     name: "sshd"
     state: restarted
 
 - name: restart dsmcad
-  systemd:
+  ansible.builtin.systemd:
     name: "dsmcad.service"
     state: restarted
diff --git a/meta/main.yml b/meta/main.yml
index a93cf97..2d0b594 100644
--- a/meta/main.yml
+++ b/meta/main.yml
@@ -1,16 +1,24 @@
 ---
 galaxy_info:
-  author: Jörg Sachse
-  description: role to install reporting server for the SLUBarchiv digital preservation repository
+  author: Jörg Sachse (<Joerg.Sachse@slub-dresden.de>)
   company: SLUB Dresden
-  # If the issue tracker for your role is not on github, uncomment the next line and provide a value issue_tracker_url: http://example.com/issue/tracker Some suggested licenses: - BSD
-  # (default) - MIT - GPLv2 - GPLv3 - Apache - CC-BY
-  license: public domain
-  min_ansible_version: 2.8
-  # If this a Container Enabled role, provide the minimum Ansible Container version. min_ansible_container_version: Optionally specify the branch Galaxy will use when accessing the GitHub repo
-  # for this role. During role install, if no tags are available, Galaxy will use this branch. During import Galaxy will access files on this branch. If Travis integration is configured, only
-  # notifications for this branch will be accepted. Otherwise, in all cases, the repo's default branch (usually master) will be used. github_branch:
-  #
+  description: role to install reporting server for the SLUBarchiv digital preservation repository
+  galaxy_tags: []
+    # List tags for your role here, one per line. A tag is a keyword that describes and categorizes the role. Users find roles by searching for tags. Be sure to remove the '[]' above, if you
+    # add tags to this list.
+    #
+    # NOTE: A tag is limited to a single word comprised of alphanumeric characters.
+    #       Maximum 20 tags per role.
+  # issue_tracker_url: "https://example.com/"
+    # If the issue tracker for your role is not on github, uncomment the next line and provide a value issue_tracker_url: http://example.com/issue/tracker
+  license: GPLv3
+    # Some suggested licenses: - BSD
+    # (default) - MIT - GPLv2 - GPLv3 - Apache - CC-BY
+  min_ansible_version: "2.5"
+    # If this a Container Enabled role, provide the minimum Ansible Container version. min_ansible_container_version: Optionally specify the branch Galaxy will use when accessing the GitHub repo
+    # for this role. During role install, if no tags are available, Galaxy will use this branch. During import Galaxy will access files on this branch. If Travis integration is configured, only
+    # notifications for this branch will be accepted. Otherwise, in all cases, the repo's default branch (usually master) will be used. github_branch:
+  namespace: "slub"
   # Provide a list of supported platforms, and for each platform a list of versions. If you don't wish to enumerate all versions for a particular platform, use 'all'. To view available
   # platforms and versions (or releases), visit: https://galaxy.ansible.com/api/v1/platforms/
   #
@@ -19,16 +27,6 @@ galaxy_info:
   platforms:
     - name: Debian
       versions:
-        - 9
-        - 10
-  galaxy_tags: []
-  # List tags for your role here, one per line. A tag is a keyword that describes and categorizes the role. Users find roles by searching for tags. Be sure to remove the '[]' above, if you
-  # add tags to this list.
-  #
-  # NOTE: A tag is limited to a single word comprised of alphanumeric characters.
-  #       Maximum 20 tags per role.
-dependencies:
-# - ansible_lza_install_common
-# - ansible_lza_server_hardening
-# List your role dependencies here, one per line. Be sure to remove the '[]' above,
-# if you add dependencies to this list.
+        - "buster"
+        - "bullseye"
+dependencies: []
diff --git a/molecule/README.md b/molecule/README.md
deleted file mode 100644
index 33a7eb5..0000000
--- a/molecule/README.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# Testing with Molecule
-
-## Prerequisites
-
-In order to be able to use the tests, you need to have some software packages installed. You may need sudo privileges for some of these operations.
-
-        ### install VirtualBox
-        # do NOT use distribution packages
-        # process documented at https://www.virtualbox.org/wiki/Linux_Downloads
-        #
-        # add repository URL
-        sudo echo "deb [arch=amd64] https://download.virtualbox.org/virtualbox/debian stretch contrib" > /etc/apt/sources.d/virtualbox.list
-        # add GPG key
-        wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -
-        # update sources
-        sudo apt update
-        # install VirtualBox
-        sudo apt-get install virtualbox-6.1
-
-        ### install Vagrant
-        # do NOT use distribution packages
-        # 
-        # download Debian package from Hashicorp
-        wget https://releases.hashicorp.com/vagrant/2.2.9/vagrant_2.2.9_x86_64.deb
-        # install package
-        sudo dpkg -i vagrant_2.2.9_x86_64.deb
-
-        ### install Molecule et. al.
-        # prepare directories
-        mkdir ~/python-envs/ && cd ~/python-env/
-        # create Python Virtual Environment with Python3 interpreter (Python2 is deprecated!)
-        virtualenv -p python3 molecule-env
-        # enter the Virtual Environment in your current shell (other shells will be unaffected)
-        source molecule-env/bin/activate
-        # install packages
-        pip3 install molecule ansible testinfra ansible-lint molecule-vagrant molecule-docker
-
-        # leave the Virtual Environment only when you're done
-        deactivate
-
-You can find suitable documentation at the respective vendors' websites.
-* [Vagrant Installation Guide](https://www.vagrantup.com/docs/installation/)
-* [VirtualBox Installation Guide](https://www.virtualbox.org/wiki/Downloads)
-* [Molecule Installation Guide](https://molecule.readthedocs.io/en/stable/installation.html)
-
-## Initialising a new Molecule scenario
-
-If you have already created a role without the Molecule test framework or you want to add test scenarios, you can use:
-	molecule init scenario --scenario-name <my_scenario> --driver [azure|delegated|docker|ec2|gce|linode|lxc|lxd|openstack|vagrant] --verifier-name [ansible|testinfra]
-
-If you need any help with the options, please use:
-	molecule init role --help
-
-## Running Tests
-
-Molecule helps with creating a test infrastructure, running tests against it and removing the test infrastructure afterwards.
-
-Various test environments are separated into so-called "scenarios" that can be based on different OSses, drivers, verifiers or might just differ in a minor detail. 
-
-In the simplest configuration, the `molecule/` directory only contains one `default/` directory that contains the default scenario. This scenario is run if no other scenario is chosen using the `-s` CLI option.
-
-This is the basic usage of Molecule:
-	# create test infrastructure
-	cd <role_directory>
-	molecule create
-	# run playbooks against test infrastructure
-	molecule converge
-	# run Testinfra tests
-	molecule verify
-	# remove test infrastructure
-	molecule destroy
-
-	# run all steps at once:
-	molecule test
-
-It has proven helpful to use Vagrant to create a snapshot of the VM after the creation phase has completed.
-	# First, get UUID of the VM
-	vagrant global-status
-	# Then, create the snapshot
-	vagrant snapshot save <uuid> <snapshot_name>
-	# To restore the snapshot, use
-	vagrant snapshot restore <uuid> <snapshot_name>
-	# And to remove the snapshot, use
-	vagrant snapshot delete <uuid> <snapshot_name>
-
diff --git a/molecule/default b/molecule/default
new file mode 120000
index 0000000..3841ab1
--- /dev/null
+++ b/molecule/default
@@ -0,0 +1 @@
+./virtualbox
\ No newline at end of file
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
deleted file mode 100644
index 4d8c3d9..0000000
--- a/molecule/default/converge.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-- name: Converge
-  hosts: all
-  roles:
-    - {role: "ansible_lza_reporting", become: true}
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
deleted file mode 100644
index 70d04d7..0000000
--- a/molecule/default/molecule.yml
+++ /dev/null
@@ -1,36 +0,0 @@
----
-dependency:
-  name: galaxy
-driver:
-  name: vagrant
-  provider:
-    name: virtualbox
-lint: |
-  set -e
-  yamllint .
-  ansible-lint -x formatting
-  flake8 --ignore=E501
-platforms:
-  - name: molecule-test-reporting
-    box: debian/buster64
-    memory: 512
-    cpus: 1
-provisioner:
-  name: ansible
-  log: true
-  config_options:
-    defaults:
-      vault_identity_list: "@$HOME/.ansible/roles/molecule_prepare.pass, @$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/lza_server_hardening.pass, @$HOME/.ansible/roles/lza_reporting.pass"
-  lint: |
-    set -e
-    ansible-lint
-  vvv: false
-verifier:
-  name: testinfra
-  env:
-    PYTHONWARNINGS: "ignore:.*U.*mode is deprecated:DeprecationWarning"
-  lint: |
-    set -e
-    flake8
-  options:
-    v: 1
diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml
deleted file mode 100644
index d903f56..0000000
--- a/molecule/default/prepare.yml
+++ /dev/null
@@ -1,40 +0,0 @@
----
-- name: Prepare
-  hosts: all
-  gather_facts: true
-  pre_tasks:
-    - name: include vars
-      include_vars: "../../../ansible_vaults/molecule_prepare/{{ item }}"
-      loop:
-        - "prepare.vault"
-    - name: Install python for Ansible
-      raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
-      become: true
-      changed_when: false
-    - name: create users (as deployed in production)
-      user:
-        name: "{{ item.name }}"
-        uid: "{{ item.uid }}"
-        create_home: "yes"
-        shell: "/bin/bash"
-      loop: "{{ vault_molecule_users | flatten(levels=1) }}"
-      become: true
-    - name: add nonfree repos
-      apt_repository:
-        repo: "deb http://ftp2.de.debian.org/debian/ buster main non-free contrib"
-        state: present
-        update-cache: "yes"
-      become: true
-    - name: Install required packages
-      apt:
-        name: [
-          'aptitude',
-          'gpg',
-          'less',
-          'libuser'
-        ]
-        state: present
-      become: true
-  roles:
-    - {role: ansible_lza_install_common, become: true}
-    - {role: ansible_lza_server_hardening, become: true}
diff --git a/molecule/default/tests/conftest.py b/molecule/default/tests/conftest.py
deleted file mode 100644
index ba0f1e8..0000000
--- a/molecule/default/tests/conftest.py
+++ /dev/null
@@ -1,20 +0,0 @@
-"""PyTest Fixtures."""
-from __future__ import absolute_import
-import os
-import pytest
-
-
-def pytest_runtest_setup(item):
-    """Run tests only when under molecule with testinfra installed."""
-    try:
-        import testinfra
-    except ImportError:
-        pytest.skip("Test requires testinfra", allow_module_level=True)
-    if "MOLECULE_INVENTORY_FILE" in os.environ:
-        pytest.testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
-            os.environ["MOLECULE_INVENTORY_FILE"]
-        ).get_hosts("all")
-    else:
-        pytest.skip(
-            "Test should run only from inside molecule.", allow_module_level=True
-        )
diff --git a/molecule/default/tests/test_default.py b/molecule/default/tests/test_default.py
deleted file mode 100644
index 39da80a..0000000
--- a/molecule/default/tests/test_default.py
+++ /dev/null
@@ -1,6 +0,0 @@
-def test_hosts_file(host):
-    f = host.file('/etc/hosts')
-
-    assert f.exists
-    assert f.user == 'root'
-    assert f.group == 'root'
diff --git a/molecule/default/INSTALL.rst b/molecule/resources/playbooks/INSTALL.rst
similarity index 66%
rename from molecule/default/INSTALL.rst
rename to molecule/resources/playbooks/INSTALL.rst
index 6a44bde..0c4bf5c 100644
--- a/molecule/default/INSTALL.rst
+++ b/molecule/resources/playbooks/INSTALL.rst
@@ -1,11 +1,12 @@
-*******
-Docker driver installation guide
-*******
+*********************************
+Vagrant driver installation guide
+*********************************
 
 Requirements
 ============
 
-* Docker Engine
+* Vagrant
+* Virtualbox, Parallels, VMware Fusion, VMware Workstation or VMware Desktop
 
 Install
 =======
@@ -19,4 +20,4 @@ widely recommended `'--user' flag`_ when invoking ``pip``.
 
 .. code-block:: bash
 
-    $ pip install 'molecule[docker]'
+    $ pip install 'molecule_vagrant'
diff --git a/molecule/resources/playbooks/README.md b/molecule/resources/playbooks/README.md
new file mode 100644
index 0000000..0c91883
--- /dev/null
+++ b/molecule/resources/playbooks/README.md
@@ -0,0 +1,3 @@
+This drectory contains shared playbooks and a shared Dockerfile.
+
+Visit https://molecule.readthedocs.io/en/latest/examples.html#sharing-across-scenarios for details on sharing playbooks, tests etc. across multiple scenarios.
diff --git a/molecule/resources/playbooks/converge.yml b/molecule/resources/playbooks/converge.yml
new file mode 100644
index 0000000..d515e4b
--- /dev/null
+++ b/molecule/resources/playbooks/converge.yml
@@ -0,0 +1,17 @@
+---
+- name: Converge
+  hosts: all
+  pre_tasks:
+    - name: update apt cache
+      ansible.builtin.apt:
+        update_cache: true
+        upgrade: dist
+      become: true
+      when: ansible_os_family == "Debian"
+    - name: update yum cache
+      ansible.builtin.yum:
+        update_cache: true
+      become: true
+      when: ansible_os_family == "RedHat"
+  roles:
+    - {role: "ansible_lza_reporting", become: true}
diff --git a/molecule/resources/playbooks/prepare.yml b/molecule/resources/playbooks/prepare.yml
new file mode 100644
index 0000000..83a23bc
--- /dev/null
+++ b/molecule/resources/playbooks/prepare.yml
@@ -0,0 +1,26 @@
+---
+- name: Prepare
+  hosts: "*"
+  tasks:
+    - name: install GPG
+      ansible.builtin.apt:
+        name: "gnupg"
+        state: latest
+        update_cache: true
+      become: true
+    - name: add GPG key for SLUB Debian repository
+      ansible.builtin.apt_key:
+        url: "https://sdvdebianrepo.slub-dresden.de/deb-repository/pub.gpg.key"
+        state: present
+      become: true
+    - name: add repo URL to sources.list
+      ansible.builtin.apt_repository:
+        repo: "deb https://sdvdebianrepo.slub-dresden.de/deb-repository bullseye main"
+        state: present
+        update_cache: true
+        mode: "0644"
+      become: true
+    - name: create 'lza' group
+      ansible.builtin.group:
+        name: "lza"
+      become: true
diff --git a/molecule/resources/playbooks/verify.yml b/molecule/resources/playbooks/verify.yml
new file mode 100644
index 0000000..e707420
--- /dev/null
+++ b/molecule/resources/playbooks/verify.yml
@@ -0,0 +1,10 @@
+---
+# This is an example playbook to execute Ansible tests.
+
+- name: Verify
+  hosts: all
+  gather_facts: false
+  tasks:
+  - name: Example assertion
+    ansible.builtin.assert:
+      that: true
diff --git a/molecule/virtualbox/molecule.yml b/molecule/virtualbox/molecule.yml
new file mode 100644
index 0000000..61c1663
--- /dev/null
+++ b/molecule/virtualbox/molecule.yml
@@ -0,0 +1,41 @@
+---
+dependency:
+  name: galaxy
+  enabled: false
+driver:
+  name: vagrant
+lint: |
+  set -e
+  yamllint .
+  ansible-lint -x no-loop-var-prefix,command-instead-of-module,package-latest
+platforms:
+  # Check out the documentation at
+  # https://github.com/ansible-community/molecule-vagrant#documentation
+  # for more platform parameters.
+  - name: vm-runner-reporting
+    box: debian/bullseye64
+    memory: 1024
+    # List of raw Vagrant `config` options.
+    # provider_raw_config_args:
+    #   - "customize [ 'modifyvm', :id, '--natdnshostresolver1', 'on' ]"
+    # Dictionary of `config` options.
+    config_options:
+      ssh.keep_alive: yes
+      ssh.remote_user: "'lza'"
+provisioner:
+  name: ansible
+  log: true
+  config_options:
+    defaults:
+      # https://stackoverflow.com/questions/57435811/ansible-molecule-pass-multiple-vault-ids
+      #vault_identity_list: "@$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/passfile_1.pass"
+      vault_identity_list: "../lza_reporting.pass, ../lza_install_common.pass"
+  vvv: false
+  playbooks:
+    # create: ../resources/playbooks/create.yml
+    # destroy: ../resources/playbooks/destroy.yml
+    converge: ../resources/playbooks/converge.yml
+    prepare: ../resources/playbooks/prepare.yml
+    verify: ../resources/playbooks/verify.yml
+verifier:
+  name: ansible
diff --git a/requirements.yml b/requirements.yml
deleted file mode 100644
index 58de273..0000000
--- a/requirements.yml
+++ /dev/null
@@ -1,12 +0,0 @@
----
-# https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#installing-multiple-roles-from-a-file
-- src: git+https://git.slub-dresden.de/slub-referat-2-3/ansible_vaults.git
-  scm: git
-- src: git+https://git.slub-dresden.de/digital-preservation/ansible_lza_install_common.git
-  scm: git
-- src: git+https://git.slub-dresden.de/digital-preservation/ansible_lza_server_hardening.git
-  scm: git
-- src: git+https://git.slub-dresden.de/digital-preservation/ansible_slub_osquery.git
-  scm: git
-#- src: git+
-#  scm: git
diff --git a/requirements.yml.example b/requirements.yml.example
new file mode 100644
index 0000000..97bfe9c
--- /dev/null
+++ b/requirements.yml.example
@@ -0,0 +1,4 @@
+---
+# https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#installing-multiple-roles-from-a-file
+- src: git+https://git.slub-dresden.de/slub-referat-2-3/ansible_vaults.git
+  scm: git
diff --git a/site.yml b/site.yml
index 16ec20b..de89a0d 100644
--- a/site.yml
+++ b/site.yml
@@ -25,7 +25,4 @@
   force_handlers: true
 
   roles:
-    - { role: ansible_lza_install_common, become: true }
-    - { role: ansible_lza_server_hardening, become: true }
-    - { role: ansible_slub_osquery, become: true }
     - { role: ansible_lza_reporting, become: true }
diff --git a/tasks/configure_iptables_externalusers.yml b/tasks/configure_iptables_externalusers.yml
index 784766b..f979f95 100644
--- a/tasks/configure_iptables_externalusers.yml
+++ b/tasks/configure_iptables_externalusers.yml
@@ -1,6 +1,6 @@
 ---
 - name: iptables-Regeln setzen (ZIH der TU Dresden)
-  iptables:
+  ansible.builtin.iptables:
     action: insert
     chain: INPUT
     comment: "{{ item.comment }}"
diff --git a/tasks/configure_sftp_server.yml b/tasks/configure_sftp_server.yml
index a04639e..a19983a 100644
--- a/tasks/configure_sftp_server.yml
+++ b/tasks/configure_sftp_server.yml
@@ -1,10 +1,10 @@
 ---
 - name: purge default SFTP-Server
-  lineinfile:
+  ansible.builtin.lineinfile:
     path: "/etc/ssh/sshd_config"
     regex: '^(Subsystem\t*sftp\t*/usr/lib/openssh/sftp-server)'
     line: '# \1'
-    backrefs: "yes"
+    backrefs: true
     state: present
   notify:
     - restart sshd
@@ -12,59 +12,59 @@
 - name: Konfiguration fuer SFTP-Server einspielen
   block:
     - name: Konfiguration fuer SFTP-Server einspielen (1/3)
-      blockinfile:
+      ansible.builtin.blockinfile:
         path: "/etc/ssh/sshd_config"
-        backup: "yes"
+        backup: true
         insertafter: EOF
         marker: "### {mark} ANSIBLE MANAGED BLOCK - SFTP SERVER"
         block: |
           ### SLUBArchiv SFTP server for external producers
           Subsystem sftp internal-sftp
-          Match User {{vault_sftp_user}}
+          Match User {{ vault_sftp_user }}
             ChrootDirectory /home/%u
             ForceCommand internal-sftp
             X11Forwarding no
             AllowTcpForwarding no
           Match all
-      register: externalFTP
+      register: external_ftp
       notify:
         - restart sshd
 
     # http://sysadmin.circularvale.com/server-config/setting-a-umask-for-chrooted-sftp-users/
     - name: Konfiguration fuer SFTP-Server einspielen (2/3)
-      lineinfile:
+      ansible.builtin.lineinfile:
         dest: "/etc/pam.d/sshd"
         line: "session optional pam_umask.so umask=0002"
-        backup: "yes"
+        backup: true
       notify:
         - restart sshd
 
     - name: Mountpoints fuer SFTP konfigurieren (3/3)
       block:
         - name: Homeverzeichnis konfigurieren
-          file:
-            path: "/home/{{vault_sftp_user}}/"
+          ansible.builtin.file:
+            path: "/home/{{ vault_sftp_user }}/"
             state: directory
             owner: "root"
             group: "root"
             mode: "0755"
         - name: Uploadverzeichnis konfigurieren
-          file:
-            path: "/home/{{vault_sftp_user}}/reports/"
+          ansible.builtin.file:
+            path: "/home/{{ vault_sftp_user }}/reports/"
             state: directory
-            owner: "{{vault_sftp_user}}"
-            group: "{{vault_sftp_user}}"
+            owner: "{{ vault_sftp_user }}"
+            group: "{{ vault_sftp_user }}"
             mode: "0770"
 
 - name: Konfiguration fuer Default-SFTP-Server einspielen
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/ssh/sshd_config"
-    backup: "yes"
+    backup: true
     insertafter: EOF
     marker: "### {mark} ANSIBLE MANAGED BLOCK - SFTP SERVER"
     block: |
       ### default SFTP server for servers that don't have external producers
       Subsystem       sftp    /usr/lib/openssh/sftp-server
-  when: not externalFTP
+  when: not external_ftp
   notify:
     - restart sshd
diff --git a/tasks/configure_ssh_keys.yml b/tasks/configure_ssh_keys.yml
index b6787fb..024fdfe 100644
--- a/tasks/configure_ssh_keys.yml
+++ b/tasks/configure_ssh_keys.yml
@@ -1,15 +1,23 @@
 ---
 # für User "zih" (benötigt für Zugriff auf SFTP-Server)
 - name: SSH-Keys für Public-Key Authentication einspielen (ZIH)
-  authorized_key:
+  ansible.builtin.authorized_key:
     user: "{{ item.user }}"
     comment: "{{ item.comment }}"
     key: "{{ item.key }}"
     state: "{{ item.state | default('present') }}"
   loop: "{{ vault_authorized_keys | flatten(levels=1) }}"
 
+- name: create ~/.ssh directory for the deploykey files
+  ansible.builtin.file:
+    path: "~/.ssh/"
+    state: directory
+    owner: "root"
+    group: "root"
+    mode: 0600
+
 - name: copy deploykey files to managed servers
-  copy:
+  ansible.builtin.copy:
     src: "{{ role_path }}/../ansible_vaults/{{ role_name }}/{{ item }}"
     dest: "~/.ssh/{{ item }}"
     owner: "root"
diff --git a/tasks/create_users_groups.yml b/tasks/create_users_groups.yml
index 3c8896c..51c5857 100644
--- a/tasks/create_users_groups.yml
+++ b/tasks/create_users_groups.yml
@@ -1,33 +1,33 @@
 ---
 #  - name: DEBUG output
-#    debug:
+#    ansible.builtin.debug:
 #      msg: "Key: {{ item.key }}, Values: {{ item.value }}END"
 #      verbosity: 1
-#    with_dict: "{{ mygroups }}"
+#    loop: "{{ mygroups | flatten(levels=1) }}"
 #    tags: [ users ]
 #  - name: DEBUG output
-#    debug:
+#    ansible.builtin.debug:
 #      msg: "{{ item }}END"
 #      verbosity: 1
-#    with_dict: "{{ users }}"
+#    loop: "{{ users | flatten(levels=1) }}"
 
 - name: create groups
-  group:
+  ansible.builtin.group:
     name: "{{ item.name }}"
-    gid: "{{ item.gid }}"
+    gid: "{{ item.gid | default(omit) }}"
     state: "{{ item.state | default('present') }}"
   loop: "{{ vault_groups | flatten(levels=1) }}"
 
 # do NOT run this before skel configuration has been rolled out!
 #  - name: create individual primary user group
-#    group:
+#    ansible.builtin.group:
 #      name: "{{ item.key }}"
 #      state: present
 #      gid: "{{ item.value.uid }}"
-#    with_dict: "{{ users }}"
+#    loop: "{{ users | flatten(levels=1) }}"
 
 - name: create users
-  user:
+  ansible.builtin.user:
     comment: "{{ item.value.comment | default(omit) }}"
     create_home: "{{ item.value.create_home | default ('yes') }}"
     generate_ssh_key: "{{ item.value.generate_ssh_key | default(omit) }}"
@@ -40,4 +40,5 @@
     state: "{{ item.value.state | default('present') }}"
     uid: "{{ item.value.uid | default(omit) }}"
     update_password: "on_create"
-  with_dict: "{{ vault_users }}"
+  loop: "{{ vault_users | dict2items }}"
+  no_log: true
diff --git a/tasks/install_ibmsp_client.yml b/tasks/install_ibmsp_client.yml
index 204c50e..07d0c20 100644
--- a/tasks/install_ibmsp_client.yml
+++ b/tasks/install_ibmsp_client.yml
@@ -1,6 +1,6 @@
 ---
 - name: IBMSP-Client Pakete installieren
-  apt:
+  ansible.builtin.apt:
     name: [
       # GSKit Packages
       'gskcrypt64',
@@ -20,7 +20,7 @@
     state: latest
 
 - name: link IBMSP Client kernel modules
-  file:
+  ansible.builtin.file:
     src: "/usr/local/ibm/gsk8_64/lib64/{{ item }}"
     path: "/usr/lib/{{ item }}"
     state: link
@@ -40,9 +40,10 @@
     - "libgsk8valn_64.so"
 
 - name: write IBMSP config files
-  template:
+  ansible.builtin.template:
     src: "opt/tivoli/tsm/client/ba/bin/{{ item }}.j2"
     dest: "/opt/tivoli/tsm/client/ba/bin/{{ item }}"
+    mode: "0644"
   loop:
     - "dsm.opt"
     - "dsm.sys"
@@ -53,16 +54,18 @@
 # - dsmc CLI arg ducumentation can be found at:
 #   https://publib.boulder.ibm.com/tividd/td/TSMC/GC32-0789-01/en_US/HTML/ans5000016.htm#HDRCMD6036
 - name: write password file
-  command:
+  ansible.builtin.command:
     #use: dsmc set password       <Old PW>               <New PW>
     cmd: "dsmc set password {{ ansible_hostname }} {{ ansible_hostname }}"
     creates: "/etc/adsm/TSM.sth"
   register: dsmc_result
   failed_when: (dsmc_result.rc != 0) and (dsmc_result.rc != 8)
   notify: restart dsmcad
+  # We cannot test this in Molecule, because it requires a serverside node to be created first, which we have no access to.
+  tags: [molecule-notest]
 
 - name: Start dsmcad service. This service triggers regular checks for backup schedules on the IBMSP server.
-  systemd:
+  ansible.builtin.systemd:
     name: "dsmcad.service"
     state: started
 
@@ -70,22 +73,22 @@
 - name: install check-backup scripts
   block:
     - name: install Git (required by Ansible builtin Git module)
-      apt:
+      ansible.builtin.apt:
         name: "git"
         state: latest
     - name: install logrotate (required by check-backup script)
-      apt:
+      ansible.builtin.apt:
         name: "logrotate"
         state: latest
     - name: check out check-backup Git repo
-      git:
+      ansible.builtin.git:
         repo: "git@git.slub-dresden.de:slub-referat-2-3/check-backup.git"
         dest: "/tmp/check-backup/"
         key_file: "~/.ssh/id_ed25519_deploykey"
         accept_hostkey: true
         force: true
     - name: copy binaries and config to the system
-      copy:
+      ansible.builtin.copy:
         src: "/tmp/check-backup/Linux{{ item.name }}"
         dest: "{{ item.name }}"
         mode: "{{ item.mode }}"
@@ -98,7 +101,7 @@
         - name: "/usr/local/bin/check-backup.sh"
           mode: "0555"
     - name: template script config
-      template:
+      ansible.builtin.template:
         src: "etc/check-backup.cfg.j2"
         dest: "/etc/check-backup.cfg"
         owner: "root"
diff --git a/tasks/main.yml b/tasks/main.yml
index e65c46e..d6672e3 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -1,10 +1,10 @@
 ---
 ### PREPARATION ###
 - name: include vars main
-  include_vars: main.yml
+  ansible.builtin.include_vars: main.yml
   tags: [always]
 - name: include Ansible Vaults
-  include_vars: "{{ role_path }}/../ansible_vaults/{{ role_name }}/{{ item }}"
+  ansible.builtin.include_vars: "{{ role_path }}/../ansible_vaults/{{ role_name }}/{{ item }}"
   loop:
     - "ibmsp.vault"
     - "reporting.vault"
@@ -13,24 +13,24 @@
 
 ### GRUPPEN & BENUTZER ERSTELLEN ###
 - name: create users and groups
-  import_tasks: "create_users_groups.yml"
+  ansible.builtin.import_tasks: "create_users_groups.yml"
   tags: [users]
 
 ### SSH-KEYS VERWALTEN ###
 - name: configure SSH PublicKeys
-  import_tasks: "configure_ssh_keys.yml"
+  ansible.builtin.import_tasks: "configure_ssh_keys.yml"
   tags: [ssh]
 
 ### SFTP SERVER KONFIGURIEREN ###
 - name: Konfiguration fuer SFTP-Server einspielen
-  import_tasks: "configure_sftp_server.yml"
+  ansible.builtin.import_tasks: "configure_sftp_server.yml"
   tags: [sftp]
 
 ### IPTABLES REGELN SETZEN ###
 - name: iptables-Regeln für externe Produzenten setzen
-  import_tasks: "configure_iptables_externalusers.yml"
+  ansible.builtin.import_tasks: "configure_iptables_externalusers.yml"
   tags: [iptables]
 
 - name: install IBM Spectrum Protect (IBMSP) Backup Client
-  import_tasks: "install_ibmsp_client.yml"
+  ansible.builtin.import_tasks: "install_ibmsp_client.yml"
   tags: [backup, tsm, ibmsp]
-- 
GitLab