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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

* **roles**: Tasks that run on the Ansible controller no longer escalate via sudo when the inventory sets `ansible_become: true`, where they failed without passwordless sudo on the controller or ran as root and left root-owned files in `/tmp`.
* **plugin:bitwarden_item, module:bitwarden_item**: A failed sync of the Bitwarden vault, such as an "HTTP Error 400: Bad Request" or a timeout of `bw serve`, is tried again after 10, 30 and 60 seconds instead of aborting the run right away.
* **role:aide**: Before it creates the database, the role also waits for running `dnf-automatic` jobs on the Red Hat family and for the update jobs of the system_update role on every platform, not only for the apt jobs on Debian and Ubuntu. An update during the initialisation left files in the database that the first check then reported.
* **playbook:setup_basic**: With `setup_basic__skip_duplicity` or `setup_basic__skip_glances`, the playbook no longer builds the Python venv of the skipped role, which could abort the run with a pip error on hosts that do not back up with duplicity.
Expand Down
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ The project-agnostic "Changelog" rules above apply. LFOps overrides only the sor
* When you must use `ansible.builtin.shell`, pin the interpreter with `executable: '/bin/bash'` (in the task's `args:`, or as a sibling of `cmd:`). On Debian `/bin/sh` is `dash`, which rejects bashisms such as `set -o pipefail`, `[[ ... ]]` and `source` (Debian 12's dash errors on `set -o pipefail` outright); pinning bash keeps shell tasks working across the Red Hat family and Debian/Ubuntu. Use `/bin/bash`, not `/usr/bin/bash`, so it resolves with or without usrmerge.
* Do not use `state: 'latest'` for the `ansible.builtin.package` module as this is not idempotent. Always use `state: 'present'`.
* Always use `delegate_to: 'localhost'` instead of `local_action`.
* Always set `become: false` on every task delegated to localhost. When a play sets `become: true` at the play level (not typical for lfops, but useful if others import our roles in their playbooks), it propagates to delegated tasks too and tries to escalate via sudo on the Ansible controller. On a controller without passwordless sudo this fails with `sudo: a password is required`, even though the delegated task only writes to `/tmp` or hits a remote API and does not need root locally. Example:
* Always set both `become: false` and `vars: ansible_become: false` on every task (or block) delegated to localhost. Otherwise the task escalates via sudo on the Ansible controller: `become: true` at the play level propagates to delegated tasks, and `ansible_become: true` in the inventory, the usual way to run LFOps, even overrides the `become: false` keyword, since a connection variable takes precedence over the keyword. The task variable in turn overrides the inventory. On a controller without passwordless sudo the task fails with `sudo: a password is required`; with it, the task runs as root, leaves root-owned files in `/tmp` and loses most of the environment of the `ansible-playbook` call, since sudo resets it, although it only writes to `/tmp` or hits a remote API. Verified with ansible-core 2.16 and 2.18. Example:

```yaml
- name: 'curl --output /tmp/ansible.example.tar.gz https://example.com/releases/example.tar.gz'
Expand All @@ -376,6 +376,8 @@ The project-agnostic "Changelog" rules above apply. LFOps overrides only the sor
mode: 0o644
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
changed_when: false # not an actual config change on the target
check_mode: false # run task even if `--check` is specified
```
Expand All @@ -393,6 +395,8 @@ The project-agnostic "Changelog" rules above apply. LFOps overrides only the sor
depth: 1
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
throttle: 1 # serialize: shared git working dir on the controller, avoid races between hosts
check_mode: false # run task even if `--check` is specified
```
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ Full documentation is available at [linuxfabrik.github.io/lfops](https://linuxfa

If you manage RHEL 8 hosts with the default system Python (3.6), use **ansible-core 2.16**. If all your managed nodes have Python >= 3.8 (e.g. RHEL 9+, Debian 12+, Ubuntu 22.04+), you can use **ansible-core 2.18** for the latest features.

**Set the Python interpreter of the managed nodes.** Since ansible-core 2.17, Ansible no longer prefers the system Python of the distribution, but the newest `python3.X` from its list of known versions that it finds on the host. Some roles install an additional Python, for example duplicity (Python 3.11 on RHEL 9, Python 3.13 on RHEL 10). From the next run on, Ansible then runs its modules under that Python, which lacks the Python libraries of the distribution, and roles such as kernel_settings, monitoring_plugins and python_venv fail. Pin the system Python in the inventory for all hosts except RHEL 8, where ansible-core 2.16 already picks `/usr/libexec/platform-python`:

```yaml
ansible_python_interpreter: '/usr/bin/python3'
```

Tasks that run on the Ansible controller are not affected by this setting, they use the Python that runs Ansible.


## Installation

Expand Down
2 changes: 2 additions & 0 deletions roles/acme_sh/tasks/issue-cert.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
register: 'acme_sh__curl_result'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]

- name: 'check if acme-challenge was reachable'
ansible.builtin.assert:
Expand Down
4 changes: 4 additions & 0 deletions roles/apache_solr/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
checksum: '{{ apache_solr__checksum }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
check_mode: false # run task even if `--check` is specified
changed_when: false # just gathering info, no actual change
when:
Expand All @@ -24,6 +26,8 @@
checksum: '{{ apache_solr__checksum }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
check_mode: false # run task even if `--check` is specified
changed_when: false # just gathering info, no actual change
when:
Expand Down
6 changes: 6 additions & 0 deletions roles/blocky/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
check_mode: false # run task even if `--check` is specified
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
run_once: true

- name: 'Store the latest release version'
Expand All @@ -47,6 +49,8 @@
mode: 0o644
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
changed_when: false # not an actual config change on the server
check_mode: false # run task even if `--check` is specified

Expand All @@ -56,6 +60,8 @@
dest: '/tmp'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
ignore_errors: '{{ ansible_check_mode }}' # ignore errors if `--check` is specified

- name: 'scp /tmp/blocky {{ inventory_hostname }}:/usr/local/sbin/blocky'
Expand Down
9 changes: 6 additions & 3 deletions roles/example/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,10 @@

# download on the controller (`delegate_to: 'localhost'`) and copy to the target afterwards,
# so that targets without internet access can still be provisioned.
# always set `become: false` on tasks delegated to localhost. otherwise `become: true`
# from the playbook level propagates and triggers sudo on the controller, which is
# unneeded for a download into /tmp and usually fails because no sudo password is set.
# always set `become: false` and the task variable `ansible_become: false` on tasks
# delegated to localhost. otherwise `become: true` from the playbook level, or
# `ansible_become: true` from the inventory (which beats the keyword), triggers sudo on the
# controller, which is unneeded for a download into /tmp and fails without a sudo password.
# do not use `run_once: true` here. `run_once` binds the task to the first host of the
# batch and evaluates any `when` against that host, so a host that skips the role (or a
# block-level `when`) on the first host skips the download for everyone. `get_url` writes
Expand All @@ -148,6 +149,8 @@
mode: 0o644
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
changed_when: false # not an actual config change on the target
check_mode: false # run task even if `--check` is specified

Expand Down
16 changes: 16 additions & 0 deletions roles/exoscale_vm/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
state: 'present'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'exoscale_vm__security_group_rules is defined and exoscale_vm__security_group_rules | length > 0'
- 'exoscale_vm__state != "absent"'
Expand All @@ -28,6 +30,8 @@
loop: '{{ exoscale_vm__security_group_rules }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'exoscale_vm__security_group_rules is defined and exoscale_vm__security_group_rules | length > 0'
- 'exoscale_vm__state != "absent"'
Expand Down Expand Up @@ -64,6 +68,8 @@
register: 'exoscale_vm__instance_list_result'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
changed_when: false # just gathering information, no actual change happening here

- name: 'Create the VM at Exoscale'
Expand All @@ -86,6 +92,8 @@
'{{ exoscale_vm__name }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'exoscale_vm__state != "absent"'
- 'exoscale_vm__name not in exoscale_vm__instance_list_result["stdout"] | from_json | map(attribute="name")'
Expand All @@ -100,6 +108,8 @@
'{{ exoscale_vm__name }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'exoscale_vm__state == "absent"'
- 'exoscale_vm__name in exoscale_vm__instance_list_result["stdout"] | from_json | map(attribute="name")'
Expand Down Expand Up @@ -128,6 +138,8 @@
- 'item["cidr"] is defined'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]

- name: "Manage the VM's networks"
ngine_io.cloudstack.cs_instance_nic:
Expand All @@ -142,6 +154,8 @@
when: 'exoscale_vm__state != "absent"'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]

tags:
- 'exoscale_vm'
Expand All @@ -159,6 +173,8 @@
state: 'absent'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'exoscale_vm__security_group_rules is defined and exoscale_vm__security_group_rules | length > 0'
- 'exoscale_vm__state == "absent"'
Expand Down
2 changes: 2 additions & 0 deletions roles/firewall/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@
changed_when: false # not a config change on the server
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
throttle: 1 # serialize: shared git working dir on the controller, avoid races between hosts
check_mode: false # run task even if `--check` is specified

Expand Down
4 changes: 4 additions & 0 deletions roles/glpi_agent/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
check_mode: false # run task even if `--check` is specified
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
run_once: true

- name: 'Store the latest release version'
Expand All @@ -48,6 +50,8 @@
mode: 0o644
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
changed_when: false # not an actual config change on the server
check_mode: false # run task even if `--check` is specified

Expand Down
2 changes: 2 additions & 0 deletions roles/grafana/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@
label: '{{ item["json"]["name"] | d(item) }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'not grafana__skip_token_to_bitwarden'
- 'not (item["skipped"] is defined and item["skipped"])'
Expand Down
4 changes: 4 additions & 0 deletions roles/grafana_grizzly/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
check_mode: false # run task even if `--check` is specified
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
run_once: true

- name: 'Store the latest release version'
Expand All @@ -30,6 +32,8 @@
mode: 0o644
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
changed_when: false # not an actual config change on the server
check_mode: false # run task even if `--check` is specified

Expand Down
16 changes: 16 additions & 0 deletions roles/hetzner_vm/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
loop: '{{ hetzner_vm__networks }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'hetzner_vm__state != "absent"'
- 'item["cidr"] is defined'
Expand All @@ -24,6 +26,8 @@
loop: '{{ hetzner_vm__networks }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'hetzner_vm__state != "absent"'
- 'item["cidr"] is defined'
Expand All @@ -38,6 +42,8 @@
loop: '{{ hetzner_vm__networks | subelements("routes", skip_missing=True) }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'hetzner_vm__state != "absent"'

Expand All @@ -49,6 +55,8 @@
state: 'present'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'hetzner_vm__firewall_rules is defined and hetzner_vm__firewall_rules | length > 0'
- 'hetzner_vm__state != "absent"' # cannot remove the firewall here, as it is still in use
Expand All @@ -71,6 +79,8 @@
state: '{{ hetzner_vm__state }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]

- name: 'Manage the firewall of the VM'
hetzner.hcloud.hcloud_firewall:
Expand All @@ -79,6 +89,8 @@
state: 'absent'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'hetzner_vm__firewall_rules is defined and hetzner_vm__firewall_rules | length > 0'
- 'hetzner_vm__state == "absent"' # cannot remove the firewall here, as it is still in use
Expand All @@ -94,6 +106,8 @@
loop: '{{ hetzner_vm__networks }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
when:
- 'hetzner_vm__state != "absent"'
- 'item["fixed_ip"] is defined'
Expand All @@ -110,6 +124,8 @@
loop: '{{ hetzner_vm__volumes }}'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]

tags:
- 'hetzner_vm'
2 changes: 2 additions & 0 deletions roles/icinga2_agent/tasks/Windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
dest: '/tmp/ansible.Icinga2-{{ icinga2_agent__windows_version }}-x86_64.msi'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
check_mode: false # run task even if `--check` is specified

- name: 'copy msi file from "/tmp/ansible.Icinga2-{{ icinga2_agent__windows_version }}-x86_64.msi" to "{{ icinga2_agent__windows_download_path }}\Icinga2-{{ icinga2_agent__windows_version }}-x86_64.msi"'
Expand Down
2 changes: 2 additions & 0 deletions roles/icinga_kubernetes_web/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
dest: '/tmp/ansible.icingaweb2-kubernetes-web-{{ icinga_kubernetes_web__version }}.tar.gz'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
check_mode: false # run task even if `--check` is specified

- name: 'copy /tmp/ansible.icingaweb2-kubernetes-web-{{ icinga_kubernetes_web__version }}.tar.gz to the server'
Expand Down
2 changes: 2 additions & 0 deletions roles/icingaweb2_module_businessprocess/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
dest: '/tmp/ansible.icingaweb2-module-businessprocess-{{ icingaweb2_module_businessprocess__version }}.tar.gz'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
check_mode: false # run task even if `--check` is specified

- name: 'copy /tmp/ansible.icingaweb2-module-businessprocess-{{ icingaweb2_module_businessprocess__version }}.tar.gz to the server'
Expand Down
2 changes: 2 additions & 0 deletions roles/icingaweb2_module_company/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
dest: '/tmp/ansible.icingaweb2-theme-company-v1.0.0.tar.gz'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
check_mode: false # run task even if `--check` is specified

- name: 'copy /tmp/ansible.icingaweb2-theme-company-v1.0.0.tar.gz to the server'
Expand Down
2 changes: 2 additions & 0 deletions roles/icingaweb2_module_cube/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
dest: '/tmp/ansible.icingaweb2-module-cube-{{ icingaweb2_module_cube__version }}.tar.gz'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
check_mode: false # run task even if `--check` is specified

- name: 'copy /tmp/ansible.icingaweb2-module-cube-{{ icingaweb2_module_cube__version }}.tar.gz to the server'
Expand Down
4 changes: 4 additions & 0 deletions roles/icingaweb2_module_director/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
dest: '/tmp/ansible.icingaweb2-module-director-{{ icingaweb2_module_director__version }}.tar.gz'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
check_mode: false # run task even if `--check` is specified

- name: 'copy /tmp/ansible.icingaweb2-module-director-{{ icingaweb2_module_director__version }}.tar.gz to the server'
Expand Down Expand Up @@ -171,6 +173,8 @@
chdir: '/tmp/ansible.monitoring-plugins-repo/'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
throttle: 1 # serialize: shared dir on the controller, avoid races between hosts
check_mode: false # run task even if `--check` is specified
changed_when: false # no change on the remote host
Expand Down
2 changes: 2 additions & 0 deletions roles/icingaweb2_module_fileshipper/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
dest: '/tmp/ansible.icingaweb2-module-fileshipper-{{ icingaweb2_module_fileshipper__version }}.tar.gz'
delegate_to: 'localhost'
become: false
vars:
ansible_become: false # noqa var-naming[pattern]
check_mode: false # run task even if `--check` is specified

- name: 'copy /tmp/ansible.icingaweb2-module-fileshipper-{{ icingaweb2_module_fileshipper__version }}.tar.gz to the server'
Expand Down
Loading
Loading