diff --git a/.gitignore b/.gitignore index 7007b164..de97de82 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ playbooks/test.yml roles/test context/ particle/.vagrant +extensions/molecule/**/*.retry # mkdocs documentation /docs/CHANGELOG.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71196ae8..223ea865 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -704,6 +704,63 @@ Some files under `plugins/modules/` are not authored by Linuxfabrik but vendored * Drop when: LFOps raises its minimum ansible-core to >= 2.18; switch to `community.general.lvm_pv` and update `roles/lvm` accordingly. +### Testing + +Molecule is used as the framework to test the LFOps playbooks (*not roles*). The test scenarios and configurations live in `extension/molecule` and are structured as follows: + +``` +extensions +└── molecule + ├── apps -- test scenario, named after the playbook name + │ ├── install -- if needed, sub-scenario + │ │ ├── converge.yml + │ │ ├── inventory -- scenario-specific inventory with variables that are needed for the playbook under test and optionally additional hosts (e.g. for a cluster test setup). Overwrites the shared inventory (extensions/molecule/inventory) + │ │ │ ├── group_vars + │ │ │ │ └── systems_under_test.yml + │ │ │ └── hosts.yml + │ │ ├── molecule.yml -- scenario marker; required even if empty. Can also be used to overwrite, which playbooks are used by Molecule (e.g. to switch between VM and container provisioning playbooks) + │ │ └── verify.yml + │ └── remove -- additional sub-scenario + │ └── ... + ├── config.yml -- valid for all scenarios, can be overwritten in each scenario's molecule.yml (same content and structure) + ├── default -- we are not using the "default" scenario, but molecule needs this to run at all. could be used to share config (e.g. prepare.yml) across *all* scenarios + │ └── molecule.yml + ├── inventory -- shared inventory across all scenarios and therefore available in all scenarios. Contains a basic set of VMs/containers that are commonly used. + │ ├── hosts.yml -- Required, even if empty, that Ansible can detect this inventory + │ └── host_vars + │ ├── debian11-container.yml + │ ├── debian11-vm.yml + │ └── ... + ├── monitoring_plugins -- scenario with no sub-scenarios + │ ├── converge.yml + │ ├── inventory + │ │ └── ... + │ ├── molecule.yml + │ └── verify.yml + ├── playbooks -- shared playbooks used by Molecule for running the scenarios + │ ├── create-container.yml + │ ├── destroy-container.yml + │ └── ... + └── requirements.yml +``` + +Tests can be run against a subset of targets by providing them as a comma-separated list via the project-specific `LFOPS_TEST_TARGETS` environment variable: + +```shell +# for VMs (the hypervisor host needs to be included as well; here `localhost`) +LFOPS_TEST_TARGETS='localhost,rocky*' molecule test --scenario-name apps/install + +# for containers +LFOPS_TEST_TARGETS='rocky*' molecule test --scenario-name apps/install +``` + + +Known Limitations: + +* VM-based testing currently requires passwordless sudo on the Ansible controller. +* Ansible Navigator does not work out of the box. + + ### Credits * diff --git a/extensions/molecule/apps/install/converge.yml b/extensions/molecule/apps/install/converge.yml new file mode 100644 index 00000000..24ae2f23 --- /dev/null +++ b/extensions/molecule/apps/install/converge.yml @@ -0,0 +1,2 @@ +- name: 'Converge apps playbook' + ansible.builtin.import_playbook: 'linuxfabrik.lfops.apps' diff --git a/extensions/molecule/apps/install/inventory/group_vars/systems_under_test.yml b/extensions/molecule/apps/install/inventory/group_vars/systems_under_test.yml new file mode 100644 index 00000000..c559815d --- /dev/null +++ b/extensions/molecule/apps/install/inventory/group_vars/systems_under_test.yml @@ -0,0 +1,3 @@ +apps__apps__group_var: + - name: 'zsh' + state: 'present' diff --git a/extensions/molecule/apps/install/inventory/hosts.yml b/extensions/molecule/apps/install/inventory/hosts.yml new file mode 100644 index 00000000..02ce0bb3 --- /dev/null +++ b/extensions/molecule/apps/install/inventory/hosts.yml @@ -0,0 +1,15 @@ +lfops_apps: + children: + systems_under_test: + +systems_under_test: + hosts: + debian11-vm: + debian12-vm: + debian13-vm: + rocky8-vm: + rocky9-vm: + rocky10-vm: + ubuntu2004-vm: + ubuntu2204-vm: + ubuntu2404-vm: diff --git a/extensions/molecule/apps/install/molecule.yml b/extensions/molecule/apps/install/molecule.yml new file mode 100644 index 00000000..1e47cbff --- /dev/null +++ b/extensions/molecule/apps/install/molecule.yml @@ -0,0 +1 @@ +# Molecule scenario marker diff --git a/extensions/molecule/apps/install/verify.yml b/extensions/molecule/apps/install/verify.yml new file mode 100644 index 00000000..ff47ba68 --- /dev/null +++ b/extensions/molecule/apps/install/verify.yml @@ -0,0 +1,11 @@ +- name: 'Verify apps are installed' + hosts: 'systems_under_test' + gather_facts: false + tasks: + - name: 'Gather the package facts' + ansible.builtin.package_facts: + manager: 'auto' + + - name: 'Assert' + ansible.builtin.assert: + that: '"zsh" in ansible_facts.packages' diff --git a/extensions/molecule/apps/remove/converge.yml b/extensions/molecule/apps/remove/converge.yml new file mode 100644 index 00000000..24ae2f23 --- /dev/null +++ b/extensions/molecule/apps/remove/converge.yml @@ -0,0 +1,2 @@ +- name: 'Converge apps playbook' + ansible.builtin.import_playbook: 'linuxfabrik.lfops.apps' diff --git a/extensions/molecule/apps/remove/inventory/group_vars/systems_under_test.yml b/extensions/molecule/apps/remove/inventory/group_vars/systems_under_test.yml new file mode 100644 index 00000000..e7bd8516 --- /dev/null +++ b/extensions/molecule/apps/remove/inventory/group_vars/systems_under_test.yml @@ -0,0 +1,3 @@ +apps__apps__group_var: + - name: 'less' + state: 'absent' diff --git a/extensions/molecule/apps/remove/inventory/hosts.yml b/extensions/molecule/apps/remove/inventory/hosts.yml new file mode 100644 index 00000000..02ce0bb3 --- /dev/null +++ b/extensions/molecule/apps/remove/inventory/hosts.yml @@ -0,0 +1,15 @@ +lfops_apps: + children: + systems_under_test: + +systems_under_test: + hosts: + debian11-vm: + debian12-vm: + debian13-vm: + rocky8-vm: + rocky9-vm: + rocky10-vm: + ubuntu2004-vm: + ubuntu2204-vm: + ubuntu2404-vm: diff --git a/extensions/molecule/apps/remove/molecule.yml b/extensions/molecule/apps/remove/molecule.yml new file mode 100644 index 00000000..1e47cbff --- /dev/null +++ b/extensions/molecule/apps/remove/molecule.yml @@ -0,0 +1 @@ +# Molecule scenario marker diff --git a/extensions/molecule/apps/remove/verify.yml b/extensions/molecule/apps/remove/verify.yml new file mode 100644 index 00000000..34616d7b --- /dev/null +++ b/extensions/molecule/apps/remove/verify.yml @@ -0,0 +1,11 @@ +- name: 'Verify apps are not installed' + hosts: 'systems_under_test' + gather_facts: true + tasks: + - name: 'Gather the package facts' + ansible.builtin.package_facts: + manager: 'auto' + + - name: 'Assert' + ansible.builtin.assert: + that: '"less" not in ansible_facts.packages' diff --git a/extensions/molecule/config.yml b/extensions/molecule/config.yml new file mode 100644 index 00000000..2261ef4b --- /dev/null +++ b/extensions/molecule/config.yml @@ -0,0 +1,65 @@ +dependency: + name: 'galaxy' + options: + requirements-file: '${MOLECULE_PROJECT_DIRECTORY}/extensions/molecule/requirements.yml' + +ansible: + cfg: + defaults: + accelerate_timeout: 5 + ansible_managed: 'This file is managed by Ansible - do not edit' + callbacks_enabled: 'profile_tasks' + fact_caching: 'jsonfile' + fact_caching_connection: '${MOLECULE_EPHEMERAL_DIRECTORY}/.ansible_cache' + fact_caching_timeout: 86400 + forks: 30 + gathering: 'smart' + host_key_checking: false + inventory: 'hosts' + nocows: 1 + retry_files_enabled: true + timeout: 60 + log_path: '${MOLECULE_EPHEMERAL_DIRECTORY}/ansible.log' + inventory_ignore_extensions: + - '~' + - '.orig' + - '.bak' + - '.ini' + - '.cfg' + - '.retry' + - '.pyc' + - '.pyo' + - '.csv' + - '.md' + inventory_ignore_patterns: '(host|group)_files' + stdout_callback: 'yaml' + ssh_connections: + pipelining: true + ssh_args: '-o ControlMaster=auto -o ControlPersist=60s' + executor: + backend: 'ansible-playbook' # or 'ansible-navigator' + args: + ansible_navigator: + - '--inventory=${MOLECULE_PROJECT_DIRECTORY}/extensions/molecule/inventory/' + - '--inventory=${MOLECULE_SCENARIO_DIRECTORY}/inventory/' + - '--limit=${LFOPS_TEST_TARGETS}' + ansible_playbook: + - '--inventory=${MOLECULE_PROJECT_DIRECTORY}/extensions/molecule/inventory/' + - '--inventory=${MOLECULE_SCENARIO_DIRECTORY}/inventory/' + - '--limit=${LFOPS_TEST_TARGETS}' + +provisioner: + playbooks: + create: '${MOLECULE_PROJECT_DIRECTORY}/extensions/molecule/playbooks/create-vm.yml' + destroy: '${MOLECULE_PROJECT_DIRECTORY}/extensions/molecule/playbooks/destroy-vm.yml' + prepare: '${MOLECULE_PROJECT_DIRECTORY}/extensions/molecule/playbooks/prepare-vm.yml' + +scenario: + test_sequence: + - 'create' + - 'prepare' + - 'converge' + - 'verify' + - 'idempotence' + - 'verify' + - 'destroy' diff --git a/extensions/molecule/default/molecule.yml b/extensions/molecule/default/molecule.yml new file mode 100644 index 00000000..1e47cbff --- /dev/null +++ b/extensions/molecule/default/molecule.yml @@ -0,0 +1 @@ +# Molecule scenario marker diff --git a/extensions/molecule/inventory/host_vars/debian11-container.yml b/extensions/molecule/inventory/host_vars/debian11-container.yml new file mode 100644 index 00000000..5815e39b --- /dev/null +++ b/extensions/molecule/inventory/host_vars/debian11-container.yml @@ -0,0 +1,6 @@ +ansible_connection: 'containers.podman.podman' +ansible_host: 'debian11-container' + +container_command: 'sleep 1d' +container_image: 'docker.io/library/debian:11' +container_privileged: false diff --git a/extensions/molecule/inventory/host_vars/debian11-vm.yml b/extensions/molecule/inventory/host_vars/debian11-vm.yml new file mode 100644 index 00000000..6e9479e5 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/debian11-vm.yml @@ -0,0 +1,17 @@ +ansible_connection: 'ssh' +ansible_user: 'root' + +vm_image_url: 'https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-genericcloud-amd64.qcow2' + +kvm_vm__autostart: false +kvm_vm__base_image: 'debian-11-genericcloud-amd64.qcow2' +kvm_vm__boot_disk_size: '20G' +kvm_vm__host: 'localhost' +kvm_vm__memory: 4096 +kvm_vm__network_connections: + - name: 'enp1s0' + network_name: 'default' + dhcp4: true +kvm_vm__osinfo: 'debian11' +kvm_vm__packages: [] +kvm_vm__vcpus: 2 diff --git a/extensions/molecule/inventory/host_vars/debian12-container.yml b/extensions/molecule/inventory/host_vars/debian12-container.yml new file mode 100644 index 00000000..1cee9a25 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/debian12-container.yml @@ -0,0 +1,6 @@ +ansible_connection: 'containers.podman.podman' +ansible_host: 'debian12-container' + +container_command: 'sleep 1d' +container_image: 'docker.io/library/debian:12' +container_privileged: false diff --git a/extensions/molecule/inventory/host_vars/debian12-vm.yml b/extensions/molecule/inventory/host_vars/debian12-vm.yml new file mode 100644 index 00000000..68ae9163 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/debian12-vm.yml @@ -0,0 +1,17 @@ +ansible_connection: 'ssh' +ansible_user: 'root' + +vm_image_url: 'https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2' + +kvm_vm__autostart: false +kvm_vm__base_image: 'debian-12-genericcloud-amd64.qcow2' +kvm_vm__boot_disk_size: '20G' +kvm_vm__host: 'localhost' +kvm_vm__memory: 4096 +kvm_vm__network_connections: + - name: 'enp1s0' + network_name: 'default' + dhcp4: true +kvm_vm__osinfo: 'debian12' +kvm_vm__packages: [] +kvm_vm__vcpus: 2 diff --git a/extensions/molecule/inventory/host_vars/debian13-container.yml b/extensions/molecule/inventory/host_vars/debian13-container.yml new file mode 100644 index 00000000..48ef1193 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/debian13-container.yml @@ -0,0 +1,6 @@ +ansible_connection: 'containers.podman.podman' +ansible_host: 'debian13-container' + +container_command: 'sleep 1d' +container_image: 'docker.io/library/debian:13' +container_privileged: false diff --git a/extensions/molecule/inventory/host_vars/debian13-vm.yml b/extensions/molecule/inventory/host_vars/debian13-vm.yml new file mode 100644 index 00000000..6cf4fbf8 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/debian13-vm.yml @@ -0,0 +1,17 @@ +ansible_connection: 'ssh' +ansible_user: 'root' + +vm_image_url: 'https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2' + +kvm_vm__autostart: false +kvm_vm__base_image: 'debian-13-genericcloud-amd64.qcow2' +kvm_vm__boot_disk_size: '20G' +kvm_vm__host: 'localhost' +kvm_vm__memory: 4096 +kvm_vm__network_connections: + - name: 'enp1s0' + network_name: 'default' + dhcp4: true +kvm_vm__osinfo: 'debian13' +kvm_vm__packages: [] +kvm_vm__vcpus: 2 diff --git a/extensions/molecule/inventory/host_vars/rocky10-container.yml b/extensions/molecule/inventory/host_vars/rocky10-container.yml new file mode 100644 index 00000000..d9235047 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/rocky10-container.yml @@ -0,0 +1,7 @@ +ansible_connection: 'containers.podman.podman' +ansible_host: 'rocky10-container' + +container_command: '/usr/sbin/init' +container_image: 'docker.io/rockylinux/rockylinux:10-ubi-init' +container_privileged: false +container_systemd: true diff --git a/extensions/molecule/inventory/host_vars/rocky10-vm.yml b/extensions/molecule/inventory/host_vars/rocky10-vm.yml new file mode 100644 index 00000000..b2d9d39c --- /dev/null +++ b/extensions/molecule/inventory/host_vars/rocky10-vm.yml @@ -0,0 +1,17 @@ +ansible_connection: 'ssh' +ansible_user: 'root' + +vm_image_url: 'https://dl.rockylinux.org/pub/rocky/10/images/x86_64/Rocky-10-GenericCloud-Base.latest.x86_64.qcow2' + +kvm_vm__autostart: false +kvm_vm__base_image: 'Rocky-10-GenericCloud-Base.latest.x86_64.qcow2' +kvm_vm__boot_disk_size: '20G' +kvm_vm__host: 'localhost' +kvm_vm__memory: 4096 +kvm_vm__network_connections: + - name: 'enp1s0' + network_name: 'default' + dhcp4: true +kvm_vm__osinfo: 'rocky10' +kvm_vm__packages: [] +kvm_vm__vcpus: 2 diff --git a/extensions/molecule/inventory/host_vars/rocky8-container.yml b/extensions/molecule/inventory/host_vars/rocky8-container.yml new file mode 100644 index 00000000..c43d3d54 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/rocky8-container.yml @@ -0,0 +1,7 @@ +ansible_connection: 'containers.podman.podman' +ansible_host: 'rocky8-container' + +container_command: '/usr/sbin/init' +container_image: 'docker.io/rockylinux/rockylinux:8-ubi-init' +container_privileged: false +container_systemd: true diff --git a/extensions/molecule/inventory/host_vars/rocky8-vm.yml b/extensions/molecule/inventory/host_vars/rocky8-vm.yml new file mode 100644 index 00000000..0520d860 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/rocky8-vm.yml @@ -0,0 +1,17 @@ +ansible_connection: 'ssh' +ansible_user: 'root' + +vm_image_url: 'https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud-Base.latest.x86_64.qcow2' + +kvm_vm__autostart: false +kvm_vm__base_image: 'Rocky-8-GenericCloud-Base.latest.x86_64.qcow2' +kvm_vm__boot_disk_size: '20G' +kvm_vm__host: 'localhost' +kvm_vm__memory: 4096 +kvm_vm__network_connections: + - name: 'enp1s0' + network_name: 'default' + dhcp4: true +kvm_vm__osinfo: 'rocky8' +kvm_vm__packages: [] +kvm_vm__vcpus: 2 diff --git a/extensions/molecule/inventory/host_vars/rocky9-container.yml b/extensions/molecule/inventory/host_vars/rocky9-container.yml new file mode 100644 index 00000000..af07853f --- /dev/null +++ b/extensions/molecule/inventory/host_vars/rocky9-container.yml @@ -0,0 +1,7 @@ +ansible_connection: 'containers.podman.podman' +ansible_host: 'rocky9-container' + +container_command: '/usr/sbin/init' +container_image: 'docker.io/rockylinux/rockylinux:9-ubi-init' +container_privileged: false +container_systemd: true diff --git a/extensions/molecule/inventory/host_vars/rocky9-vm.yml b/extensions/molecule/inventory/host_vars/rocky9-vm.yml new file mode 100644 index 00000000..77072841 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/rocky9-vm.yml @@ -0,0 +1,17 @@ +ansible_connection: 'ssh' +ansible_user: 'root' + +vm_image_url: 'https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2' + +kvm_vm__autostart: false +kvm_vm__base_image: 'Rocky-9-GenericCloud-Base.latest.x86_64.qcow2' +kvm_vm__boot_disk_size: '20G' +kvm_vm__host: 'localhost' +kvm_vm__memory: 4096 +kvm_vm__network_connections: + - name: 'enp1s0' + network_name: 'default' + dhcp4: true +kvm_vm__osinfo: 'rocky9' +kvm_vm__packages: [] +kvm_vm__vcpus: 2 diff --git a/extensions/molecule/inventory/host_vars/ubuntu2004-container.yml b/extensions/molecule/inventory/host_vars/ubuntu2004-container.yml new file mode 100644 index 00000000..a1195cfb --- /dev/null +++ b/extensions/molecule/inventory/host_vars/ubuntu2004-container.yml @@ -0,0 +1,6 @@ +ansible_connection: 'containers.podman.podman' +ansible_host: 'ubuntu2004-container' + +container_command: 'sleep 1d' +container_image: 'docker.io/library/ubuntu:20.04' +container_privileged: false diff --git a/extensions/molecule/inventory/host_vars/ubuntu2004-vm.yml b/extensions/molecule/inventory/host_vars/ubuntu2004-vm.yml new file mode 100644 index 00000000..8202ec9d --- /dev/null +++ b/extensions/molecule/inventory/host_vars/ubuntu2004-vm.yml @@ -0,0 +1,17 @@ +ansible_connection: 'ssh' +ansible_user: 'root' + +vm_image_url: 'https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img' + +kvm_vm__autostart: false +kvm_vm__base_image: 'focal-server-cloudimg-amd64.img' +kvm_vm__boot_disk_size: '20G' +kvm_vm__host: 'localhost' +kvm_vm__memory: 4096 +kvm_vm__network_connections: + - name: 'enp1s0' + network_name: 'default' + dhcp4: true +kvm_vm__osinfo: 'ubuntu20.04' +kvm_vm__packages: [] +kvm_vm__vcpus: 2 diff --git a/extensions/molecule/inventory/host_vars/ubuntu2204-container.yml b/extensions/molecule/inventory/host_vars/ubuntu2204-container.yml new file mode 100644 index 00000000..289026c0 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/ubuntu2204-container.yml @@ -0,0 +1,6 @@ +ansible_connection: 'containers.podman.podman' +ansible_host: 'ubuntu2204-container' + +container_command: 'sleep 1d' +container_image: 'docker.io/library/ubuntu:22.04' +container_privileged: false diff --git a/extensions/molecule/inventory/host_vars/ubuntu2204-vm.yml b/extensions/molecule/inventory/host_vars/ubuntu2204-vm.yml new file mode 100644 index 00000000..944045db --- /dev/null +++ b/extensions/molecule/inventory/host_vars/ubuntu2204-vm.yml @@ -0,0 +1,17 @@ +ansible_connection: 'ssh' +ansible_user: 'root' + +vm_image_url: 'https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img' + +kvm_vm__autostart: false +kvm_vm__base_image: 'jammy-server-cloudimg-amd64.img' +kvm_vm__boot_disk_size: '20G' +kvm_vm__host: 'localhost' +kvm_vm__memory: 4096 +kvm_vm__network_connections: + - name: 'enp1s0' + network_name: 'default' + dhcp4: true +kvm_vm__osinfo: 'ubuntu22.04' +kvm_vm__packages: [] +kvm_vm__vcpus: 2 diff --git a/extensions/molecule/inventory/host_vars/ubuntu2404-container.yml b/extensions/molecule/inventory/host_vars/ubuntu2404-container.yml new file mode 100644 index 00000000..c2ba1ba2 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/ubuntu2404-container.yml @@ -0,0 +1,6 @@ +ansible_connection: 'containers.podman.podman' +ansible_host: 'ubuntu2404-container' + +container_command: 'sleep 1d' +container_image: 'docker.io/library/ubuntu:24.04' +container_privileged: false diff --git a/extensions/molecule/inventory/host_vars/ubuntu2404-vm.yml b/extensions/molecule/inventory/host_vars/ubuntu2404-vm.yml new file mode 100644 index 00000000..c21fc406 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/ubuntu2404-vm.yml @@ -0,0 +1,17 @@ +ansible_connection: 'ssh' +ansible_user: 'root' + +vm_image_url: 'https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img' + +kvm_vm__autostart: false +kvm_vm__base_image: 'noble-server-cloudimg-amd64.img' +kvm_vm__boot_disk_size: '20G' +kvm_vm__host: 'localhost' +kvm_vm__memory: 4096 +kvm_vm__network_connections: + - name: 'enp1s0' + network_name: 'default' + dhcp4: true +kvm_vm__osinfo: 'ubuntu24.04' +kvm_vm__packages: [] +kvm_vm__vcpus: 2 diff --git a/extensions/molecule/inventory/host_vars/ubuntu2604-container.yml b/extensions/molecule/inventory/host_vars/ubuntu2604-container.yml new file mode 100644 index 00000000..379f6d14 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/ubuntu2604-container.yml @@ -0,0 +1,6 @@ +ansible_connection: 'containers.podman.podman' +ansible_host: 'ubuntu2604-container' + +container_command: 'sleep 1d' +container_image: 'docker.io/library/ubuntu:26.04' +container_privileged: false diff --git a/extensions/molecule/inventory/host_vars/ubuntu2604-vm.yml b/extensions/molecule/inventory/host_vars/ubuntu2604-vm.yml new file mode 100644 index 00000000..ff888065 --- /dev/null +++ b/extensions/molecule/inventory/host_vars/ubuntu2604-vm.yml @@ -0,0 +1,17 @@ +ansible_connection: 'ssh' +ansible_user: 'root' + +vm_image_url: 'https://cloud-images.ubuntu.com/resolute/current/resolute-server-cloudimg-amd64.img' + +kvm_vm__autostart: false +kvm_vm__base_image: 'resolute-server-cloudimg-amd64.img' +kvm_vm__boot_disk_size: '20G' +kvm_vm__host: 'localhost' +kvm_vm__memory: 4096 +kvm_vm__network_connections: + - name: 'enp1s0' + network_name: 'default' + dhcp4: true +kvm_vm__osinfo: 'ubuntu26.04' +kvm_vm__packages: [] +kvm_vm__vcpus: 2 diff --git a/extensions/molecule/inventory/hosts.yml b/extensions/molecule/inventory/hosts.yml new file mode 100644 index 00000000..ef30ee4a --- /dev/null +++ b/extensions/molecule/inventory/hosts.yml @@ -0,0 +1 @@ +# Required for this directory to be an Ansible inventory source diff --git a/extensions/molecule/kernel_settings/sysctl/converge.yml b/extensions/molecule/kernel_settings/sysctl/converge.yml new file mode 100644 index 00000000..b0cbd950 --- /dev/null +++ b/extensions/molecule/kernel_settings/sysctl/converge.yml @@ -0,0 +1,2 @@ +- name: 'Converge kernel_settings playbook' + ansible.builtin.import_playbook: 'linuxfabrik.lfops.kernel_settings' diff --git a/extensions/molecule/kernel_settings/sysctl/inventory/group_vars/systems_under_test.yml b/extensions/molecule/kernel_settings/sysctl/inventory/group_vars/systems_under_test.yml new file mode 100644 index 00000000..b7927d41 --- /dev/null +++ b/extensions/molecule/kernel_settings/sysctl/inventory/group_vars/systems_under_test.yml @@ -0,0 +1,3 @@ +kernel_settings__sysctl__group_var: + - name: 'vm.overcommit_memory' + value: 1 diff --git a/extensions/molecule/kernel_settings/sysctl/inventory/hosts.yml b/extensions/molecule/kernel_settings/sysctl/inventory/hosts.yml new file mode 100644 index 00000000..9134979a --- /dev/null +++ b/extensions/molecule/kernel_settings/sysctl/inventory/hosts.yml @@ -0,0 +1,9 @@ +lfops_kernel_settings: + children: + systems_under_test: + +systems_under_test: + hosts: + rocky8-vm: + rocky9-vm: + rocky10-vm: diff --git a/extensions/molecule/kernel_settings/sysctl/molecule.yml b/extensions/molecule/kernel_settings/sysctl/molecule.yml new file mode 100644 index 00000000..1e47cbff --- /dev/null +++ b/extensions/molecule/kernel_settings/sysctl/molecule.yml @@ -0,0 +1 @@ +# Molecule scenario marker diff --git a/extensions/molecule/kernel_settings/sysctl/verify.yml b/extensions/molecule/kernel_settings/sysctl/verify.yml new file mode 100644 index 00000000..e4081225 --- /dev/null +++ b/extensions/molecule/kernel_settings/sysctl/verify.yml @@ -0,0 +1,12 @@ +- name: 'Verify' + hosts: 'systems_under_test' + gather_facts: false + tasks: + - name: 'Read sysctl vm.overcommit_memory from procfs' + ansible.builtin.slurp: + src: '/proc/sys/vm/overcommit_memory' + register: 'sysctl_vm_overcommit_memory_result' + + - name: 'Assert' + ansible.builtin.assert: + that: 'sysctl_vm_overcommit_memory_result.content | b64decode | int == 1' diff --git a/extensions/molecule/monitoring_plugins/converge.yml b/extensions/molecule/monitoring_plugins/converge.yml new file mode 100644 index 00000000..fc3b8a9d --- /dev/null +++ b/extensions/molecule/monitoring_plugins/converge.yml @@ -0,0 +1,2 @@ +- name: 'Converge monitoring_plugins playbook' + ansible.builtin.import_playbook: 'linuxfabrik.lfops.monitoring_plugins' diff --git a/extensions/molecule/monitoring_plugins/inventory/group_vars/systems_under_test.yml b/extensions/molecule/monitoring_plugins/inventory/group_vars/systems_under_test.yml new file mode 100644 index 00000000..d59c92ea --- /dev/null +++ b/extensions/molecule/monitoring_plugins/inventory/group_vars/systems_under_test.yml @@ -0,0 +1 @@ +monitoring_plugins__version: '5.0.0' diff --git a/extensions/molecule/monitoring_plugins/inventory/hosts.yml b/extensions/molecule/monitoring_plugins/inventory/hosts.yml new file mode 100644 index 00000000..4fa06474 --- /dev/null +++ b/extensions/molecule/monitoring_plugins/inventory/hosts.yml @@ -0,0 +1,20 @@ +lfops_monitoring_plugins: + children: + systems_under_test: + +lfops_repo_monitoring_plugins: + children: + systems_under_test: + +systems_under_test: + hosts: + debian11-vm: + debian12-vm: + debian13-vm: + rocky8-vm: + rocky9-vm: + rocky10-vm: + ubuntu2004-vm: + ubuntu2204-vm: + ubuntu2404-vm: + diff --git a/extensions/molecule/monitoring_plugins/molecule.yml b/extensions/molecule/monitoring_plugins/molecule.yml new file mode 100644 index 00000000..1e47cbff --- /dev/null +++ b/extensions/molecule/monitoring_plugins/molecule.yml @@ -0,0 +1 @@ +# Molecule scenario marker diff --git a/extensions/molecule/monitoring_plugins/verify.yml b/extensions/molecule/monitoring_plugins/verify.yml new file mode 100644 index 00000000..2b409f77 --- /dev/null +++ b/extensions/molecule/monitoring_plugins/verify.yml @@ -0,0 +1,12 @@ +- name: 'Verify monitoring plugins are installed' + hosts: 'systems_under_test' + gather_facts: false + tasks: + - name: 'stat /usr/lib64/nagios/plugins/about-me' + ansible.builtin.stat: + path: '/usr/lib64/nagios/plugins/about-me' + register: 'about_me_plugin_stat_result' + + - name: 'Assert that about-me monitoring plugin is installed' + ansible.builtin.assert: + that: 'about_me_plugin_stat_result.stat.exists' diff --git a/extensions/molecule/playbooks/create-container.yml b/extensions/molecule/playbooks/create-container.yml new file mode 100644 index 00000000..2436e31d --- /dev/null +++ b/extensions/molecule/playbooks/create-container.yml @@ -0,0 +1,28 @@ +- name: 'Create container instances' + hosts: 'systems_under_test' + gather_facts: false + tasks: + - name: 'Create containers from inventory' + containers.podman.podman_container: + capabilities: '{{ container_capabilities | default(omit) }}' + command: '{{ container_command | default("sleep 1d") }}' + hostname: '{{ inventory_hostname }}' + image: '{{ container_image }}' + log_driver: '{{ container_log_driver | default("json-file") }}' + name: '{{ inventory_hostname }}' + privileged: '{{ container_privileged | default(false) }}' + state: 'started' + systemd: '{{ container_systemd | default(false) }}' + volumes: '{{ container_volumes | default(omit) }}' + register: 'create_result' + delegate_to: 'localhost' + + # Not using ansible.builtin.wait_for_connection here as it depends on a working Python + # installation inside the container; however, some container images are shipped with the + # bare minimum and thus do not contain a Python interpreter. + - name: 'Verify containers are running' + ansible.builtin.include_tasks: + file: 'tasks/create-container-fail.yml' + when: > + create_result.container.State.ExitCode != 0 or + not create_result.container.State.Running diff --git a/extensions/molecule/playbooks/create-vm.yml b/extensions/molecule/playbooks/create-vm.yml new file mode 100644 index 00000000..ecacb221 --- /dev/null +++ b/extensions/molecule/playbooks/create-vm.yml @@ -0,0 +1,108 @@ +- name: 'Prepare for VM creation' + hosts: 'systems_under_test' + gather_facts: false + vars: + molecule_ephemeral_dir: '{{ lookup("env", "MOLECULE_EPHEMERAL_DIRECTORY") }}' + tasks: + + - name: 'Generate ephemeral SSH keypair' + community.crypto.openssh_keypair: + path: '{{ molecule_ephemeral_dir }}/molecule_key' + type: 'ed25519' + comment: 'molecule' + delegate_to: 'localhost' + run_once: true + + - name: 'Read SSH public key' + ansible.builtin.slurp: + src: '{{ molecule_ephemeral_dir }}/molecule_key.pub' + delegate_to: 'localhost' + register: 'molecule_pubkey_result' + run_once: true + + - name: 'Set SSH public key fact' + ansible.builtin.set_fact: + molecule_ssh_pubkey: '{{ molecule_pubkey_result["content"] | b64decode | trim }}' + delegate_to: 'localhost' + delegate_facts: true + run_once: true + + - name: 'Get info on storage pools' + community.libvirt.virt_pool: + command: 'info' + delegate_to: 'localhost' + register: 'molecule_pool_info' + run_once: true + + - name: 'Download cloud images into storage pool' + ansible.builtin.get_url: + url: '{{ vm_image_url }}' + dest: '{{ molecule_pool_info["pools"]["default"]["path"] }}/{{ kvm_vm__base_image }}' + mode: '0644' + become: true + delegate_to: 'localhost' + when: 'vm_image_url is defined' + + - name: 'Ensure ephemeral inventory directory exists' + ansible.builtin.file: + path: '{{ molecule_ephemeral_dir }}/inventory' + state: 'directory' + mode: '0755' + delegate_to: 'localhost' + run_once: true + + +- name: 'Create VM instances using kvm_vm role' + become: true + hosts: 'systems_under_test' + gather_facts: false + roles: + - role: 'linuxfabrik.lfops.kvm_vm' + vars: + kvm_vm__ssh_authorized_keys: + - '{{ hostvars["localhost"]["molecule_ssh_pubkey"] }}' + kvm_vm__state: 'running' + + +- name: 'Discover VM IPs and write dynamic inventory' + hosts: 'systems_under_test' + gather_facts: false + vars: + molecule_ephemeral_dir: '{{ lookup("env", "MOLECULE_EPHEMERAL_DIRECTORY") }}' + tasks: + + - name: 'Wait for VMs to obtain IP addresses' + ansible.builtin.command: + cmd: 'virsh domifaddr {{ inventory_hostname }} --source arp' + register: 'domifaddr_result' + until: 'domifaddr_result.stdout_lines | select("match", ".*ipv4.*") | list | length > 0' + retries: 30 + delay: 5 + changed_when: false + delegate_to: 'localhost' + + - name: 'Parse VM IP addresses' + ansible.builtin.set_fact: + molecule_vm_ip: >- + {{ + domifaddr_result.stdout_lines + | select('match', '.*ipv4.*') + | first + | regex_replace('.*\s+(\d+\.\d+\.\d+\.\d+)\/.*', '\1') + }} + + - name: 'Write dynamic VM inventory' + ansible.builtin.copy: + content: | + molecule: + hosts: + {% for host in ansible_play_hosts %} + {{ host }}: + ansible_host: '{{ hostvars[host]["molecule_vm_ip"] }}' + ansible_ssh_private_key_file: '{{ molecule_ephemeral_dir }}/molecule_key' + ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' + {% endfor %} + dest: '{{ molecule_ephemeral_dir }}/inventory/molecule_vm.yml' + mode: '0644' + delegate_to: 'localhost' + run_once: true diff --git a/extensions/molecule/playbooks/destroy-container.yml b/extensions/molecule/playbooks/destroy-container.yml new file mode 100644 index 00000000..6d7a1df9 --- /dev/null +++ b/extensions/molecule/playbooks/destroy-container.yml @@ -0,0 +1,11 @@ +- name: 'Destroy container instances' + hosts: 'systems_under_test' + gather_facts: false + tasks: + + - name: 'Kill container if running' + containers.podman.podman_container: + name: '{{ inventory_hostname }}' + state: 'absent' + timeout: 2 + delegate_to: 'localhost' diff --git a/extensions/molecule/playbooks/destroy-vm.yml b/extensions/molecule/playbooks/destroy-vm.yml new file mode 100644 index 00000000..a33533eb --- /dev/null +++ b/extensions/molecule/playbooks/destroy-vm.yml @@ -0,0 +1,29 @@ +- name: 'Destroy VM instances using kvm_vm role' + become: true + hosts: 'systems_under_test' + gather_facts: false + roles: + - role: 'linuxfabrik.lfops.kvm_vm' + vars: + kvm_vm__state: 'absent' + + +- name: 'Clean up Molecule artifacts' + hosts: 'localhost' + gather_facts: false + vars: + molecule_ephemeral_dir: '{{ lookup("env", "MOLECULE_EPHEMERAL_DIRECTORY") }}' + tasks: + + - name: 'Remove ephemeral SSH keypair' + ansible.builtin.file: + path: '{{ molecule_ephemeral_dir }}/{{ item }}' + state: 'absent' + loop: + - 'molecule_key' + - 'molecule_key.pub' + + - name: 'Remove dynamic inventory' + ansible.builtin.file: + path: '{{ molecule_ephemeral_dir }}/inventory' + state: 'absent' diff --git a/extensions/molecule/playbooks/prepare-container.yml b/extensions/molecule/playbooks/prepare-container.yml new file mode 100644 index 00000000..76137864 --- /dev/null +++ b/extensions/molecule/playbooks/prepare-container.yml @@ -0,0 +1,19 @@ +- name: 'Prepare containers for Ansible' + hosts: 'systems_under_test' + gather_facts: false + tasks: + - name: 'Install Python using raw module (in case it is missing from the container image)' + ansible.builtin.raw: | + sh -c ''' + if command -v dnf > /dev/null 2>&1; then + dnf install -y python3 + elif command -v yum > /dev/null 2>&1; then + yum install -y python3 + elif command -v apt-get > /dev/null 2>&1; then + apt-get update && apt-get install -y python3 + fi + ''' + changed_when: true + + - name: 'Gather facts after Python is available' + ansible.builtin.setup: diff --git a/extensions/molecule/playbooks/prepare-vm.yml b/extensions/molecule/playbooks/prepare-vm.yml new file mode 100644 index 00000000..9ecccf52 --- /dev/null +++ b/extensions/molecule/playbooks/prepare-vm.yml @@ -0,0 +1,10 @@ +- name: 'Prepare VMs for Ansible' + hosts: 'systems_under_test' + gather_facts: false + tasks: + - name: 'Wait for SSH to be available' + ansible.builtin.wait_for_connection: + timeout: 120 + + - name: 'Gather facts' + ansible.builtin.setup: diff --git a/extensions/molecule/playbooks/tasks/create-container-fail.yml b/extensions/molecule/playbooks/tasks/create-container-fail.yml new file mode 100644 index 00000000..00fc5bc7 --- /dev/null +++ b/extensions/molecule/playbooks/tasks/create-container-fail.yml @@ -0,0 +1,14 @@ +- name: 'Retrieve container log' + ansible.builtin.command: + cmd: 'podman logs {{ create_result.container.Name }}' + changed_when: false + register: 'logfile_cmd' + delegate_to: 'localhost' + +- name: 'Display container log and fail' + ansible.builtin.fail: + msg: | + Container {{ create_result.container.Name }} failed to start properly. + Exit Code: {{ create_result.container.State.ExitCode }} + Running: {{ create_result.container.State.Running }} + Log output: {{ logfile_cmd.stdout | default('No logs available') }} diff --git a/extensions/molecule/requirements.yml b/extensions/molecule/requirements.yml new file mode 100644 index 00000000..b2ef5cb7 --- /dev/null +++ b/extensions/molecule/requirements.yml @@ -0,0 +1,4 @@ +collections: + - name: 'containers.podman' + version: '>=1.10.0' + - name: 'community.libvirt' diff --git a/extensions/molecule/setup_basic/converge.yml b/extensions/molecule/setup_basic/converge.yml new file mode 100644 index 00000000..e9e8f2cb --- /dev/null +++ b/extensions/molecule/setup_basic/converge.yml @@ -0,0 +1,2 @@ +- name: 'Converge' + ansible.builtin.import_playbook: 'linuxfabrik.lfops.setup_basic' diff --git a/extensions/molecule/setup_basic/inventory/group_vars/systems_under_test.yml b/extensions/molecule/setup_basic/inventory/group_vars/systems_under_test.yml new file mode 100644 index 00000000..c6f5ca51 --- /dev/null +++ b/extensions/molecule/setup_basic/inventory/group_vars/systems_under_test.yml @@ -0,0 +1,11 @@ +mailto_root__from: 'root@localhost' +mailto_root__to: + - 'root@localhost' + +monitoring_plugins__version: '5.0.0' + +postfix__relayhost: 'mail.example.com' + +setup_basic__skip_duplicity: true +setup_basic__skip_repo_icinga: true +setup_basic__skip_icinga2_agent: true diff --git a/extensions/molecule/setup_basic/inventory/host_vars/rocky10-vm.yml b/extensions/molecule/setup_basic/inventory/host_vars/rocky10-vm.yml new file mode 100644 index 00000000..8491746e --- /dev/null +++ b/extensions/molecule/setup_basic/inventory/host_vars/rocky10-vm.yml @@ -0,0 +1 @@ +setup_basic__skip_glances: true diff --git a/extensions/molecule/setup_basic/inventory/hosts.yml b/extensions/molecule/setup_basic/inventory/hosts.yml new file mode 100644 index 00000000..fcd38a4c --- /dev/null +++ b/extensions/molecule/setup_basic/inventory/hosts.yml @@ -0,0 +1,15 @@ +lfops_setup_basic: + children: + systems_under_test: + +systems_under_test: + hosts: + debian11-vm: + debian12-vm: + debian13-vm: + rocky8-vm: + rocky9-vm: + rocky10-vm: + ubuntu2004-vm: + ubuntu2204-vm: + ubuntu2404-vm: diff --git a/extensions/molecule/setup_basic/molecule.yml b/extensions/molecule/setup_basic/molecule.yml new file mode 100644 index 00000000..1e47cbff --- /dev/null +++ b/extensions/molecule/setup_basic/molecule.yml @@ -0,0 +1 @@ +# Molecule scenario marker diff --git a/extensions/molecule/setup_basic/verify.yml b/extensions/molecule/setup_basic/verify.yml new file mode 100644 index 00000000..3a86d0ea --- /dev/null +++ b/extensions/molecule/setup_basic/verify.yml @@ -0,0 +1,12 @@ +- name: 'Verify monitoring plugins are installed' + hosts: 'systems_under_test' + gather_facts: false + tasks: + - name: 'stat /usr/lib64/nagios/plugins/about-me' + ansible.builtin.stat: + path: '/usr/lib64/nagios/plugins/about-me' + register: 'about_me_plugin_stat_result' + + - name: 'Assert that the about-me monitoring plugin is installed' + ansible.builtin.assert: + that: 'about_me_plugin_stat_result.stat.exists'