From eee1a2866295d01a1160280f686a75298270dc92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Sachse?= <Joerg.Sachse@slub-dresden.de>
Date: Thu, 11 Aug 2022 14:53:35 +0200
Subject: [PATCH] test: introduce GitLab-CI functionality

---
 .ansible-lint                                 |  45 ++++----
 .config/molecule/config.yml                   |   2 +
 .githooks/pre-commit                          |  34 ++----
 .gitignore                                    |  14 +++
 .gitlab-ci.yml                                |  45 ++++++++
 .yamllint                                     |  32 ++++--
 defaults/main.yml                             |   6 +-
 meta/main.yml                                 |  41 +++----
 molecule/default                              |   1 +
 molecule/default/molecule.yml                 |  31 ------
 molecule/default/playbook.yml                 |   5 -
 molecule/default/prepare.yml                  |   9 --
 molecule/default/tests/test_default.py        |  14 ---
 .../playbooks}/INSTALL.rst                    |   6 +-
 molecule/resources/playbooks/README.md        |   3 +
 molecule/resources/playbooks/converge.yml     |  17 +++
 molecule/resources/playbooks/prepare.yml      |  45 ++++++++
 molecule/resources/playbooks/verify.yml       |  10 ++
 molecule/virtualbox_centos7/molecule.yml      |  41 +++++++
 molecule/virtualbox_rocky8/molecule.yml       |  42 +++++++
 requirements.yml                              |  10 --
 tasks/configure_repos.yml                     |  21 +++-
 tasks/configure_ssh_client.yml                |   2 +-
 tasks/configure_users.yml                     |   9 +-
 tasks/install_tsm_client.yml                  | 104 ++++++++++++++----
 tasks/main.yml                                |  10 +-
 vars/main.yml                                 |   4 +-
 27 files changed, 410 insertions(+), 193 deletions(-)
 create mode 100644 .config/molecule/config.yml
 create mode 100644 .gitlab-ci.yml
 create mode 120000 molecule/default
 delete mode 100644 molecule/default/molecule.yml
 delete mode 100644 molecule/default/playbook.yml
 delete mode 100644 molecule/default/prepare.yml
 delete mode 100644 molecule/default/tests/test_default.py
 rename molecule/{default => resources/playbooks}/INSTALL.rst (84%)
 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_centos7/molecule.yml
 create mode 100644 molecule/virtualbox_rocky8/molecule.yml
 delete mode 100644 requirements.yml

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/.githooks/pre-commit b/.githooks/pre-commit
index 02df4d5..b5975f0 100755
--- a/.githooks/pre-commit
+++ b/.githooks/pre-commit
@@ -24,12 +24,12 @@ exec 1>&2
 # Cross platform projects tend to avoid non-ASCII filenames; prevent
 # them from being added to the repository. We exploit the fact that the
 # printable range starts at the space character and ends with tilde.
-if [ "${allownonascii}" != "true" ] &&
+if [ "$allownonascii" != "true" ] &&
 	# Note that the use of brackets around a tr range is ok here, (it's
 	# even required, for portability to Solaris 10's /usr/bin/tr), since
 	# the square bracket bytes happen to fall in the designated range.
-	test "$( git diff --cached --name-only --diff-filter=A -z "${against}" |
-	  LC_ALL=C tr -d '[ -~]\0' | wc -c )" != 0
+	test $(git diff --cached --name-only --diff-filter=A -z $against |
+	  LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
 then
 	cat <<\EOF
 Error: Attempt to add a non-ASCII file name.
@@ -69,16 +69,12 @@ YAML_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".yml$")
 if [[ ${YAML_FILES} != "" ]]; then
 	for file in ${YAML_FILES}; do
 		yamllint "${file}"
-		if ! yamllint "${file}"; then
+		if [[ ${?} -ne 0 ]]; then
 			exit 1
 		fi
 	done
 fi && echo "SUCCESS: Yamllint stage."
 
-### ANSIBLE-LINT stage
-ansible-lint "site.yml" || exit 1
-echo "SUCCESS: Ansible-lint stage."
-
 ### VAULT detection stage
 VAULT_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".vault$")
 if [[ ${VAULT_FILES} != "" ]]; then
@@ -92,15 +88,14 @@ fi
 if [[ ${STAGED_FILES} != "" ]]; then
 	for file in ${STAGED_FILES}; do
 		grep -e "\$ANSIBLE_VAULT;[[:digit:]]\.[[:digit:]];AES256" "${file}"
-		if grep -e "\$ANSIBLE_VAULT;[[:digit:]]\.[[:digit:]];AES256" "${file}"; then
-			echo "ERROR: Ansible-Vault in String found in file '${file}'." && exit 1
-		fi
+		[[ ${?} -eq 0 ]] && echo "ERROR: Ansible-Vault in String found in file '${file}'." && exit 1
 	done
 fi
 echo "SUCCESS: Vault detection stage."
 
 ### URL detection stage
-if "${GREP_CMD}" "${GREP_EXCLUDES}" -e "http[s]*.*git.*SLUB" -e "http[s]*.*git.*slub" -e "git@" "${REPOPATH}"; then
+${GREP_CMD} ${GREP_EXCLUDES} -e "http[s]*.*git.*SLUB" -e "http[s]*.*git.*slub" -e "git@" "${REPOPATH}"
+if [[ ${?} -eq 0 ]]; then
 	echo "ERROR: found internal URLs."
 	exit 1;
 fi
@@ -109,28 +104,21 @@ echo "SUCCESS: URL detection stage."
 ### IP address detection stage
 # This is pretty basic regex matching, but it's a start.
 IP_REGEX='[^a-zA-ZäöÜÄÖÜß/\\\-][0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}'
-if "${GREP_CMD}" "${GREP_EXCLUDES}" -e "${IP_REGEX}" "${REPOPATH}" | grep -v "127.0.0"; then
+${GREP_CMD} ${GREP_EXCLUDES} -e "${IP_REGEX}" "${REPOPATH}" | grep -v "127.0.0"
+if [[ ${?} -eq 0 ]]; then
 	echo "ERROR: found IP address."
 	exit 1;
 fi
 echo "SUCCESS: IP address detection stage."
 
 ### SSH-Key detection stage
-if "${GREP_CMD}" "${GREP_EXCLUDES}" -e "ssh-[dr]sa " "${REPOPATH}"; then
+${GREP_CMD} ${GREP_EXCLUDES} -e "ssh-[dr]sa " "${REPOPATH}"
+if [[ ${?} -eq 0 ]]; then
 	echo "ERROR: found SSH key."
 	exit 1;
 fi
 echo "SUCCESS: SSH Key detection stage."
 
-### SHELLSCRIPT CHECK stage
-SH_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".sh$")
-if [[ ${SH_FILES} != "" ]]; then
-	for file in ${SH_FILES}; do
-		shellcheck "${file}"
-	done
-fi
-echo "SUCCESS: SHELLSCRIPT CHECK stage."
-
 
 
 
diff --git a/.gitignore b/.gitignore
index 2e89c4b..ed78332 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,8 @@ Thumbs.db
 
 *.retry
 *.vault
+inventory.*
+inv.*
 
 # Vim #
 #######
@@ -72,3 +74,15 @@ tags
 
 .vagrant/
 *.box
+
+# Temporary/Build/Backup #
+##########################
+
+backups/
+build/
+
+# CONFIDENTIAL #
+################
+
+ssh_host_*
+
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..6cc3d22
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,45 @@
+---
+# 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_LZA_BOOTSTRAP_RHEL_SERVER}" > ../lza_bootstrap_rhel_server.pass
+    - export ANSIBLE_VAULT_IDENTITY_LIST="../lza_bootstrap_rhel_server.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 against DEFAULT scenario (virtualbox_centos7)
+    - export SCENARIO="virtualbox_centos7"
+    - molecule syntax --scenario-name "${SCENARIO}"
+    - molecule lint --scenario-name "${SCENARIO}"
+    - molecule create --scenario-name "${SCENARIO}"
+    - molecule converge --scenario-name "${SCENARIO}"
+    - molecule idempotence --scenario-name "${SCENARIO}"
+    # - molecule verify --scenario-name "${SCENARIO}"
+    - molecule destroy --scenario-name "${SCENARIO}"
+    
+    # run Molecule tests against Rocky Linux 8 scenario (virtualbox_rocky8)
+    - export SCENARIO="virtualbox_rocky8"
+    - molecule syntax --scenario-name "${SCENARIO}"
+    - molecule lint --scenario-name "${SCENARIO}"
+    - molecule create --scenario-name "${SCENARIO}"
+    - molecule converge --scenario-name "${SCENARIO}"
+    - molecule idempotence --scenario-name "${SCENARIO}"
+    # - molecule verify --scenario-name "${SCENARIO}"
+    - molecule destroy --scenario-name "${SCENARIO}"
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/defaults/main.yml b/defaults/main.yml
index 9dc5e4b..c5ffe80 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -1,6 +1,6 @@
 ---
 # defaults file for ansible_lza_bootstrap_rhel_server
 
-tsm_default_version_short: "v8110"
-tsm_default_version_long: "8.1.10"
-tsm_default_checksum: "sha1:8ed715ad4c934a9891b2357d4877f3095a2c5ac2"
+tsm_default_version_short: "v8115"
+tsm_default_version_long: "8.1.15"
+tsm_default_checksum: "sha256:e24735f2f2f88bedc93371eae507c14dccb8e7be2676794b53ad94674338d8a1"
diff --git a/meta/main.yml b/meta/main.yml
index 2107fd3..a601195 100644
--- a/meta/main.yml
+++ b/meta/main.yml
@@ -1,29 +1,32 @@
 ---
 galaxy_info:
-  author: Jörg Sachse
-  description: role to initialise empty VMs that should become RHEL servers before switching to a non-root user for further management.
+  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: GPLv3
-  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:
-  #
-  # 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 
+  description: This role can be used to initialise empty VMs that should become RHEL servers before switching to a non-root user for further management.
+  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: public domain
+    # 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/
   #
   # platforms: - name: Fedora
   #   versions: - all - 25 - name: SomePlatform versions: - all - 1.0 - 7 - 99.99
   platforms:
-    - name: RedHat
+    - name: EL
       versions:
-        - 7
-  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.
+        - "7"
+        - "8"
 dependencies: []
diff --git a/molecule/default b/molecule/default
new file mode 120000
index 0000000..0a8acd2
--- /dev/null
+++ b/molecule/default
@@ -0,0 +1 @@
+virtualbox_centos7
\ No newline at end of file
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
deleted file mode 100644
index 33d132b..0000000
--- a/molecule/default/molecule.yml
+++ /dev/null
@@ -1,31 +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-bootstrap-redhat
-    box: centos/7
-    memory: 512
-    cpus: 1
-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/molecule_prepare.pass, @$HOME/.ansible/roles/lza_install_common.pass, @$HOME/.ansible/roles/lza_server_hardening.pass, @$HOME/.ansible/roles/lza_bootstrap_rhel_server.pass, @$HOME/.ansible/roles/slub_osquery.pass"
-  vvv: false
-verifier:
-  name: testinfra
-  env:
-    PYTHONWARNINGS: "ignore:.*U.*mode is deprecated:DeprecationWarning"
-  options:
-    v: 1
diff --git a/molecule/default/playbook.yml b/molecule/default/playbook.yml
deleted file mode 100644
index 85cef84..0000000
--- a/molecule/default/playbook.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-- name: Converge
-  hosts: all
-  roles:
-    - role: ansible_lza_bootstrap_rhel_server
diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml
deleted file mode 100644
index 4b18d48..0000000
--- a/molecule/default/prepare.yml
+++ /dev/null
@@ -1,9 +0,0 @@
----
-- name: Prepare
-  hosts: all
-  gather_facts: false
-  tasks:
-    - name: Install python for Ansible
-      raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
-      become: true
-      changed_when: false
diff --git a/molecule/default/tests/test_default.py b/molecule/default/tests/test_default.py
deleted file mode 100644
index eedd64a..0000000
--- a/molecule/default/tests/test_default.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import os
-
-import testinfra.utils.ansible_runner
-
-testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
-    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
-
-
-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 84%
rename from molecule/default/INSTALL.rst
rename to molecule/resources/playbooks/INSTALL.rst
index 4f44b67..0c4bf5c 100644
--- a/molecule/default/INSTALL.rst
+++ b/molecule/resources/playbooks/INSTALL.rst
@@ -1,6 +1,6 @@
-*******
+*********************************
 Vagrant driver installation guide
-*******
+*********************************
 
 Requirements
 ============
@@ -20,4 +20,4 @@ widely recommended `'--user' flag`_ when invoking ``pip``.
 
 .. code-block:: bash
 
-    $ pip install 'molecule[vagrant]'
+    $ 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..ae30651
--- /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_bootstrap_rhel_server", become: true}
diff --git a/molecule/resources/playbooks/prepare.yml b/molecule/resources/playbooks/prepare.yml
new file mode 100644
index 0000000..4288e3e
--- /dev/null
+++ b/molecule/resources/playbooks/prepare.yml
@@ -0,0 +1,45 @@
+---
+- name: Prepare
+  hosts: "*"
+  tasks:
+    - name: add SLUB Debian Repository
+      block:
+        - 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
+      when: ansible_os_family == "Debian"
+    - name: Increase disk size of created VM. The Rocky Linux 8 Vagrant image is too small to fit the IBMSP client on it, so we have to make room before running any tests.
+      block:
+        - name: install growpart
+          ansible.builtin.yum:
+            name: "cloud-utils-growpart"
+          become: true
+        - name: resize partition
+          ansible.builtin.command: "growpart /dev/sda 1"
+          register: growpart
+          changed_when: '"CHANGED:" in growpart.stdout'
+          failed_when:
+            - 'not "it cannot be grown" in growpart.stdout'
+            - 'not growpart.rc == 0'
+          become: true
+        - name: resize XFS filesystem
+          ansible.builtin.command: "xfs_growfs -d /"
+          register: growfs
+          changed_when: '"data blocks changed from" in growfs.stdout'
+          become: true
+      when: ansible_os_family == "RedHat"
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_centos7/molecule.yml b/molecule/virtualbox_centos7/molecule.yml
new file mode 100644
index 0000000..bea3b5d
--- /dev/null
+++ b/molecule/virtualbox_centos7/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
+    box: centos/7
+    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_bootstrap_rhel_server.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/molecule/virtualbox_rocky8/molecule.yml b/molecule/virtualbox_rocky8/molecule.yml
new file mode 100644
index 0000000..78e22c5
--- /dev/null
+++ b/molecule/virtualbox_rocky8/molecule.yml
@@ -0,0 +1,42 @@
+---
+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: mol-lza-bootstrap-rocky8
+    box: rockylinux/8
+    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'"
+      disksize.size: '10GB'
+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_bootstrap_rhel_server.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 629afa7..0000000
--- a/requirements.yml
+++ /dev/null
@@ -1,10 +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/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/tasks/configure_repos.yml b/tasks/configure_repos.yml
index b9bc452..45bc439 100644
--- a/tasks/configure_repos.yml
+++ b/tasks/configure_repos.yml
@@ -2,24 +2,37 @@
 # Systems have to be subscribed with RedHat in order to be able to use software
 # repositories, install software and receive updates/support.
 - name: subscribe system with RedHat
-  redhat_subscription:
+  community.general.redhat_subscription:
     state: present
     username: "{{ vault_rhel_username }}"
     password: "{{ vault_rhel_password }}"
     auto_attach: true
+  # We cannot test this in Molecule, because subscribing requires a valid
+  # license, which we don't have.
+  tags: [molecule-notest]
 - name: activate RHEL repos
-  rhsm_repository:
+  community.general.rhsm_repository:
     name: "{{ item }}"
     state: enabled
   loop:
     - "rhel-7-server-optional-rpms"
     - "rhel-7-server-rh-common-rpms"
     - "rhel-7-server-extras-rpms"
+  # We cannot test this in Molecule because this can only be done on systems
+  # with a valid subscription (see previous task).
+  tags: [molecule-notest]
+
+- name: install additional GPG keys
+  ansible.builtin.rpm_key:
+    key: "{{ item }}"
+  loop:
+    # EPEL
+    - "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-{{ ansible_distribution_major_version }}"
 
 # Additional software is made available through the EPEL (Extra Packages for
 # Enterprise Linux) repository, which is managed by the Fedora Special Interest
 # Group of the same name.
 - name: activate EPEL repos
-  yum:
-    name: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm"
+  ansible.builtin.yum:
+    name: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm"
     state: installed
diff --git a/tasks/configure_ssh_client.yml b/tasks/configure_ssh_client.yml
index 65921a2..8aff3f3 100644
--- a/tasks/configure_ssh_client.yml
+++ b/tasks/configure_ssh_client.yml
@@ -1,6 +1,6 @@
 ---
 - name: add SSH key
-  authorized_key:
+  ansible.builtin.authorized_key:
     comment: "{{ item.ssh_comment | default(omit) }}"
     key: "{{ item.ssh_key }}"
     user: "{{ item.name }}"
diff --git a/tasks/configure_users.yml b/tasks/configure_users.yml
index 8207a21..9b68122 100644
--- a/tasks/configure_users.yml
+++ b/tasks/configure_users.yml
@@ -1,11 +1,11 @@
 ---
 - name: create groups
-  group:
-    name: sudo
+  ansible.builtin.group:
+    name: "sudo"
     system: true
 
 - name: create users
-  user:
+  ansible.builtin.user:
     name: "{{ item.name }}"
     uid: "{{ item.uid | default(omit) }}"
     comment: "{{ item.comment | default(omit) }}"
@@ -14,9 +14,10 @@
     groups: "sudo"
     shell: "{{ item.shell | default('/bin/bash') }}"
   loop: "{{ vault_users }}"
+  no_log: true
 
 - name: grant sudo
-  blockinfile:
+  ansible.builtin.blockinfile:
     path: "/etc/sudoers.d/group_sudo"
     create: true
     owner: "root"
diff --git a/tasks/install_tsm_client.yml b/tasks/install_tsm_client.yml
index 27b2e03..5255c50 100644
--- a/tasks/install_tsm_client.yml
+++ b/tasks/install_tsm_client.yml
@@ -1,47 +1,103 @@
 ---
-- name: download IBM TSM Client setup checksum file
-  get_url:
+- name: Download IBM TSM Client setup checksum file.
+  ansible.builtin.get_url:
     url: "https://public.dhe.ibm.com/storage/tivoli-storage-management/maintenance/client/v8r1/Linux/LinuxX86/BA/{{ tsm_version_short | default(tsm_default_version_short) }}/{{ tsm_version_long | default(tsm_default_version_long) }}.0-TIV-TSMBAC-LinuxX86.tar.sha256sum.txt"
     dest: "/tmp/"
+    mode: "0644"
   delegate_to: 127.0.0.1
 
-- name: extract IBM TSM Client setup archive checksum from file
-  set_fact:
+- name: >
+    Extract IBM TSM Client setup archive checksum from file. We want to use
+    this info later to verify the integrity of our download.
+  ansible.builtin.set_fact:
     tsm_checksum: "{{ lookup('file', '/tmp/'+ tsm_version_long | default(tsm_default_version_long) +'.0-TIV-TSMBAC-LinuxX86.tar.sha256sum.txt').split(' ')[0] | lower }}"
 
-- name: download IBM TSM Client setup files
-  get_url:
+
+
+- name: >
+    Check if IBM TSM Client setup file has already been downloaded. If it has,
+    we can skip the next step, which might otherwise take very long.
+  ansible.builtin.stat:
+    path: "/tmp/{{ tsm_version_long | default(tsm_default_version_long) }}.0-TIV-TSMBAC-LinuxX86.tar"
+    checksum_algorithm: "sha256"
+  register: ibmsp_tar
+
+- name: >
+    Download IBM TSM Client setup file. (Be patient, IBM doesn't want to give
+    us appropriate download speeds. This will take about 6 minutes.)
+  ansible.builtin.get_url:
     url: "https://public.dhe.ibm.com/storage/tivoli-storage-management/maintenance/client/v8r1/Linux/LinuxX86/BA/{{ tsm_version_short | default(tsm_default_version_short) }}/{{ tsm_version_long | default(tsm_default_version_long) }}.0-TIV-TSMBAC-LinuxX86.tar"
     dest: "/tmp/"
+    mode: "0644"
     checksum: "sha256:{{ tsm_checksum | default(omit) }}"
+  when: (( not ibmsp_tar.stat.exists ) or 
+         ( not ibmsp_tar.stat.checksum == tsm_checksum ))
+
+
 
 - name: create temporary extraction dir
-  file:
-    path: "/tmp/tsm/"
+  ansible.builtin.file:
+    path: "/tmp/tsm/{{ tsm_version_short | default(tsm_default_version_short) }}/"
     state: directory
+    mode: "0755"
 
 - name: extract IBM TSM Client setup files
-  unarchive:
+  ansible.builtin.unarchive:
     src: "/tmp/{{ tsm_version_long | default(tsm_default_version_long) }}.0-TIV-TSMBAC-LinuxX86.tar"
-    dest: "/tmp/tsm/"
-    copy: false
+    dest: "/tmp/tsm/{{ tsm_version_short | default(tsm_default_version_short) }}/"
+    remote_src: true
+    owner: "root"
+    group: "root"
+
+- name: >
+    Install GPG keys for IBMSP and GSKit. They can't be grabbed online, so we
+    have to use the files that come with the tar archive.
+  ansible.builtin.rpm_key:
+    key: "/tmp/tsm/{{ tsm_version_short | default(tsm_default_version_short) }}/{{ item }}"
+  loop:
+    - "GSKit.pub2.pgp"
+    - "RPM-GPG-KEY-ibmpkg"
 
-# https://www.ibm.com/support/knowledgecenter/SSEQVQ_8.1.12/client/t_inst_linuxx86client.html
+
+
+- name: >
+    Find all extracted RPM packages, so we don't have to hardcode their names.
+    That's cool, because some of them contain version numbers that we don't
+    want to constantly keep updated. Also, passing a list to
+    ansible.builtin.yum later on is much faster than looping through the
+    packages individually, and it saves us from dependency hell.
+  ansible.builtin.find:
+    path: "/tmp/tsm/{{ tsm_version_short | default(tsm_default_version_short) }}/"
+    pattern: "*.rpm"
+  register: rpm_packages
+  failed_when: rpm_packages.matched == 0
+
+# NOTE:
+#   - We need to install 'lsof' along with those, because it's a requirement
+#     for 'TIVsm-WebUI', but doesn't seem to get resolved correctly. The
+#     workaround is to install it separately first to satisfy the dependency.
+#     Only then we use the second loop iteration to install the rest of the
+#     packages.
+#   - We remove the 'TIVsm-JBB.x86_64.rpm' package from the list of install
+#     candidates, because we don't need it and it requires 'TIVsm-filepath',
+#     which we would have to compile on our own. So that's a big NOPE!
+# OFFICIAL IBM INSTALL DOCUMENTATION:
+#   - https://www.ibm.com/support/knowledgecenter/SSEQVQ_8.1.15/client/t_inst_linuxx86client.html
+#   - https://www.ibm.com/docs/en/spectrum-protect/8.1.15?topic=clients-installing-linux-x86-64-client
+# LIST CREATION MAGIC STOLEN FROM:
+#   - https://stackoverflow.com/a/62197473
+#   - https://stackoverflow.com/a/60721624
 - name: install IBM TSM Client
-  yum:
-    name: [
-      '/tmp/tsm/gskcrypt64-8.0.55.21.linux.x86_64.rpm',
-      '/tmp/tsm/gskssl64-8.0.55.21.linux.x86_64.rpm',
-      '/tmp/tsm/TIVsm-API64.x86_64.rpm',
-      '/tmp/tsm/TIVsm-APIcit.x86_64.rpm',
-      '/tmp/tsm/TIVsm-BA.x86_64.rpm',
-      '/tmp/tsm/TIVsm-BAcit.x86_64.rpm',
-      '/tmp/tsm/TIVsm-BAhdw.x86_64.rpm'
-    ]
-  become: true
+  ansible.builtin.yum:
+    name: "{{ item }}"
+  loop:
+    - "lsof"
+    - "{{ rpm_packages.files | json_query('[*].path') | reject('match', '.*TIVsm-JBB.x86_64.rpm') | list | flatten }}"
+
+
 
 - name: make sure IBM Spectrum Protect backup service is active and running
-  systemd:
+  ansible.builtin.systemd:
     name: "dsmcad.service"
     enabled: true
   become: true
diff --git a/tasks/main.yml b/tasks/main.yml
index 0fce7ce..c4b64bb 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -1,24 +1,24 @@
 ---
 # tasks file for ansible_lza_bootstrap_rhel_server
 - 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:
     - "redhat.vault"
     - "users.vault"
   tags: [always]
 
 - name: create users
-  import_tasks: configure_users.yml
+  ansible.builtin.import_tasks: configure_users.yml
   tags: [users]
 
 - name: prepare SSH access
-  import_tasks: configure_ssh_client.yml
+  ansible.builtin.import_tasks: configure_ssh_client.yml
   tags: [ssh]
 
 - name: activate software repositories
-  import_tasks: configure_repos.yml
+  ansible.builtin.import_tasks: configure_repos.yml
   tags: [repos]
 
 - name: install IBM Spectrum Protect Backup Client (formerly IBM TSM Client)
-  import_tasks: install_tsm_client.yml
+  ansible.builtin.import_tasks: install_tsm_client.yml
   tags: [tsm, ibmsp, backup]
diff --git a/vars/main.yml b/vars/main.yml
index 3d0825f..2abcf30 100644
--- a/vars/main.yml
+++ b/vars/main.yml
@@ -1,5 +1,5 @@
 ---
 # vars file for ansible_lza_bootstrap_rhel_server
 
-tsm_version_short: "v8112"
-tsm_version_long: "8.1.12"
+tsm_version_short: "v8115"
+tsm_version_long: "8.1.15"
-- 
GitLab