From deb528ad95e1903ebb380e0bb77b580923f666c4 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sun, 19 Jan 2025 00:38:52 +0000 Subject: [PATCH 01/28] Add role for pve_exporter Signed-off-by: juju4 --- roles/pve_exporter/README.md | 47 ++++++++ roles/pve_exporter/TROUBLESHOOTING.md | 1 + roles/pve_exporter/defaults/main.yml | 40 ++++++ roles/pve_exporter/handlers/main.yml | 10 ++ roles/pve_exporter/meta/argument_specs.yml | 93 ++++++++++++++ roles/pve_exporter/meta/main.yml | 25 ++++ .../molecule/alternative/molecule.yml | 23 ++++ .../alternative/tests/test_alternative.py | 43 +++++++ .../molecule/default/molecule.yml | 6 + .../molecule/default/tests/test_default.py | 76 ++++++++++++ .../pve_exporter/molecule/latest/molecule.yml | 7 ++ .../molecule/latest/tests/test_latest.py | 51 ++++++++ roles/pve_exporter/tasks/configure.yml | 30 +++++ roles/pve_exporter/tasks/main.yml | 83 +++++++++++++ roles/pve_exporter/tasks/preflight.yml | 65 ++++++++++ .../templates/pve_exporter.service.j2 | 114 ++++++++++++++++++ .../templates/pve_exporter.yml.j2 | 13 ++ roles/pve_exporter/test-requirements.txt | 1 + roles/pve_exporter/vars/main.yml | 3 + 19 files changed, 731 insertions(+) create mode 100644 roles/pve_exporter/README.md create mode 100644 roles/pve_exporter/TROUBLESHOOTING.md create mode 100644 roles/pve_exporter/defaults/main.yml create mode 100644 roles/pve_exporter/handlers/main.yml create mode 100644 roles/pve_exporter/meta/argument_specs.yml create mode 100644 roles/pve_exporter/meta/main.yml create mode 100644 roles/pve_exporter/molecule/alternative/molecule.yml create mode 100644 roles/pve_exporter/molecule/alternative/tests/test_alternative.py create mode 100644 roles/pve_exporter/molecule/default/molecule.yml create mode 100644 roles/pve_exporter/molecule/default/tests/test_default.py create mode 100644 roles/pve_exporter/molecule/latest/molecule.yml create mode 100644 roles/pve_exporter/molecule/latest/tests/test_latest.py create mode 100644 roles/pve_exporter/tasks/configure.yml create mode 100644 roles/pve_exporter/tasks/main.yml create mode 100644 roles/pve_exporter/tasks/preflight.yml create mode 100644 roles/pve_exporter/templates/pve_exporter.service.j2 create mode 100644 roles/pve_exporter/templates/pve_exporter.yml.j2 create mode 100644 roles/pve_exporter/test-requirements.txt create mode 100644 roles/pve_exporter/vars/main.yml diff --git a/roles/pve_exporter/README.md b/roles/pve_exporter/README.md new file mode 100644 index 000000000..c1fac072b --- /dev/null +++ b/roles/pve_exporter/README.md @@ -0,0 +1,47 @@ +# Ansible Role: pve exporter + +## Description + +Deploy prometheus [pve exporter](https://github.com/prometheus-pve/prometheus-pve-exporter), written in python, using ansible. + +Possible Grafana dashboard: https://grafana.com/grafana/dashboards/10347-proxmox-via-prometheus/ + +## Requirements + +- Ansible >= 2.9 (It might work on previous versions, but we cannot guarantee it) + +## Role Variables + +All variables which can be overridden are stored in [defaults/main.yml](defaults/main.yml) file as well as in [meta/argument_specs.yml](meta/argument_specs.yml). +Please refer to the [collection docs](https://prometheus-community.github.io/ansible/branch/main/pve_exporter_role.html) for description and default values of the variables. + +## Example + +### Playbook + +Use it in a playbook as follows: +```yaml +- hosts: all + roles: + - prometheus.prometheus.pve_exporter +``` + +## Local Testing + +The preferred way of locally testing the role is to use Docker and [molecule](https://github.com/ansible-community/molecule) (v3.x). You will have to install Docker on your system. See "Get started" for a Docker package suitable for your system. Running your tests is as simple as executing `molecule test`. + +## Continuous Integration + +Combining molecule and circle CI allows us to test how new PRs will behave when used with multiple ansible versions and multiple operating systems. This also allows use to create test scenarios for different role configurations. As a result we have quite a large test matrix which can take more time than local testing, so please be patient. + +## Contributing + +See [contributor guideline](CONTRIBUTING.md). + +## Troubleshooting + +See [troubleshooting](TROUBLESHOOTING.md). + +## License + +This project is licensed under MIT License. See [LICENSE](/LICENSE) for more details. diff --git a/roles/pve_exporter/TROUBLESHOOTING.md b/roles/pve_exporter/TROUBLESHOOTING.md new file mode 100644 index 000000000..4f341277d --- /dev/null +++ b/roles/pve_exporter/TROUBLESHOOTING.md @@ -0,0 +1 @@ +# Troubleshooting diff --git a/roles/pve_exporter/defaults/main.yml b/roles/pve_exporter/defaults/main.yml new file mode 100644 index 000000000..1be6cc2c4 --- /dev/null +++ b/roles/pve_exporter/defaults/main.yml @@ -0,0 +1,40 @@ +--- +pve_exporter_version: 3.5.0 +# not go, but python pip. placeholders for _common +pve_exporter_textfile_dir: "" +pve_exporter_tls_server_config: {} +pve_exporter_http_server_config: {} +pve_exporter_basic_auth_users: {} +# just for "prometheus.prometheus._common : Naive assertion of proper listen address" else not used +pve_exporter_web_listen_address: "127.0.0.1:9221" + +pve_exporter_web_disable_exporter_metrics: false +pve_exporter_web_telemetry_path: "/metrics" + +# cluster collectors +pve_exporter_enabled_collectors: [] +# pve_exporter_enabled_collectors: +# - status +# - version +# - node +# - cluster +# - resources +pve_exporter_disabled_collectors: [] + +pve_exporter_debug_enable: false + +pve_exporter_home: "/var/lib/pve_exporter" +pve_exporter_virtualenv: "/var/lib/pve_exporter/venv" + +pve_exporter_auth_user: prometheus@pve +pve_exporter_auth_pass: +pve_exporter_auth_verify_ssl: true +pve_exporter_auth_token_name: your-token-id +pve_exporter_auth_token_value: + +pve_exporter_config_template: pve_exporter.yml.j2 + +pve_exporter_system_group: "pve-exp" +pve_exporter_system_user: "{{ pve_exporter_system_group }}" + +pve_exporter_config_dir: "/etc/pve_exporter" diff --git a/roles/pve_exporter/handlers/main.yml b/roles/pve_exporter/handlers/main.yml new file mode 100644 index 000000000..102878b30 --- /dev/null +++ b/roles/pve_exporter/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Restart pve_exporter + listen: "restart pve_exporter" + become: true + ansible.builtin.systemd: + daemon_reload: true + name: pve_exporter + state: restarted + when: + - not ansible_check_mode diff --git a/roles/pve_exporter/meta/argument_specs.yml b/roles/pve_exporter/meta/argument_specs.yml new file mode 100644 index 000000000..336df2e57 --- /dev/null +++ b/roles/pve_exporter/meta/argument_specs.yml @@ -0,0 +1,93 @@ +--- +# yamllint disable rule:line-length +argument_specs: + main: + short_description: "Prometheus Proxmox VE Exporter" + description: + - "Deploy prometheus L(pve exporter,https://github.com/prometheus-pve/prometheus-pve-exporter) using ansible" + author: + - "Prometheus Community" + options: + pve_exporter_home: + description: "Home of pve_exporter user and base file locations" + default: "/var/lib/pve_exporter" + pve_exporter_virtualenv: + description: "Python virtualenv filepath for pve_exporter" + default: "/var/lib/pve_exporter/venv" + pve_exporter_auth_user: + description: "Authentication PVE user name if using user/password auth" + default: "prometheus@pve" + pve_exporter_auth_pass: + description: "Authentication PVE user password if using user/password auth" + pve_exporter_auth_verify_ssl: + description: "Validate proxmox certificate or not" + type: bool + default: true + pve_exporter_auth_token_name: + description: "Authentication PVE user token name if using token auth" + default: your-token-id + pve_exporter_auth_token_value: + description: "Authentication PVE user token value from `pveum user token add ...`" + pve_exporter_config_template: + description: "Configuration template for pve_exporter" + default: "pve_exporter.yml.j2" + pve_exporter_version: + description: "Proxmox VE Exporter package version. Also accepts latest as parameter." + default: "1.0.0" + pve_exporter_web_disable_exporter_metrics: + description: "Exclude metrics about the exporter itself (promhttp_*, process_*, go_*)." + type: bool + default: false + pve_exporter_web_listen_address: + description: "Address on which pve exporter will listen" + default: "0.0.0.0:15353" + pve_exporter_web_telemetry_path: + description: "Path under which to expose metrics" + default: "/metrics" + pve_exporter_enabled_collectors: + description: + - "List of dicts defining additionally enabled collectors and their configuration." + - "It adds collectors to L(those enabled by default,https://github.com/prometheus-pve/prometheus-pve-exporter#enabled-by-default)." + type: "list" + default: + - systemd + - textfile: + directory: "{{ pve_exporter_textfile_dir }}" + pve_exporter_disabled_collectors: + description: + - "List of disabled collectors." + - "By default pve_exporter disables collectors listed L(here,https://github.com/prometheus-pve/prometheus-pve-exporter#disabled-by-default)." + type: "list" + elements: "str" + pve_exporter_textfile_dir: + description: + - "Directory used by the L(Textfile Collector,https://github.com/prometheus-pve/prometheus-pve-exporter#textfile-collector)." + - "To get permissions to write metrics in this directory, users must be in C(pve-exp) system group." + - "B(Note:) More information in TROUBLESHOOTING.md guide." + default: "/var/lib/pve_exporter" + pve_exporter_tls_server_config: + description: + - "Configuration for TLS authentication." + - "Keys and values are the same as in L(pve_exporter docs,https://prometheus.io/docs/prometheus/latest/configuration/https/)." + type: "dict" + pve_exporter_http_server_config: + description: + - "Config for HTTP/2 support." + - "Keys and values are the same as in L(pve_exporter docs,https://prometheus.io/docs/prometheus/latest/configuration/https/)." + type: "dict" + pve_exporter_basic_auth_users: + description: "Dictionary of users and password for basic authentication. Passwords are automatically hashed with bcrypt." + type: "dict" + pve_exporter_system_group: + description: + - "I(Advanced)" + - "System group for pve exporter" + default: "pve-exp" + pve_exporter_system_user: + description: + - "I(Advanced)" + - "Proxmox VE Exporter user" + default: "pve-exp" + pve_exporter_config_dir: + description: "Path to directory with pve_exporter configuration" + default: "/etc/pve_exporter" diff --git a/roles/pve_exporter/meta/main.yml b/roles/pve_exporter/meta/main.yml new file mode 100644 index 000000000..cc004c4e5 --- /dev/null +++ b/roles/pve_exporter/meta/main.yml @@ -0,0 +1,25 @@ +--- +galaxy_info: + author: "Prometheus Community" + description: "Prometheus Proxmox VE Exporter" + license: "Apache" + min_ansible_version: "2.9" + platforms: + - name: "Ubuntu" + versions: + - "focal" + - "jammy" + - "noble" + - name: "Debian" + versions: + - "bullseye" + - name: "EL" + versions: + - "8" + - "9" + galaxy_tags: + - "monitoring" + - "prometheus" + - "exporter" + - "metrics" + - "system" diff --git a/roles/pve_exporter/molecule/alternative/molecule.yml b/roles/pve_exporter/molecule/alternative/molecule.yml new file mode 100644 index 000000000..8cc22dc20 --- /dev/null +++ b/roles/pve_exporter/molecule/alternative/molecule.yml @@ -0,0 +1,23 @@ +--- +provisioner: + playbooks: + prepare: "${MOLECULE_PROJECT_DIRECTORY}/../../.config/molecule/alternative/prepare.yml" + inventory: + group_vars: + all: + pve_exporter_web_listen_address: + - '127.0.0.1:8080' + - '127.0.1.1:8080' + pve_exporter_textfile_dir: "" + pve_exporter_enabled_collectors: + - entropy + pve_exporter_disabled_collectors: + - diskstats + pve_exporter_tls_server_config: + cert_file: /etc/pve_exporter/tls.cert + key_file: /etc/pve_exporter/tls.key + pve_exporter_http_server_config: + http2: true + pve_exporter_basic_auth_users: + randomuser: examplepassword + pve_exporter_version: 3.5.0 diff --git a/roles/pve_exporter/molecule/alternative/tests/test_alternative.py b/roles/pve_exporter/molecule/alternative/tests/test_alternative.py new file mode 100644 index 000000000..f3e79e70a --- /dev/null +++ b/roles/pve_exporter/molecule/alternative/tests/test_alternative.py @@ -0,0 +1,43 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from testinfra_helpers import get_target_hosts +import pytest + +testinfra_hosts = get_target_hosts() + + +def test_directories(host): + dirs = [ + "/var/lib/pve_exporter" + ] + for dir in dirs: + d = host.file(dir) + assert not d.exists + + +def test_service(host): + s = host.service("pve_exporter") + try: + assert s.is_running + except AssertionError: + # Capture service logs + journal_output = host.run('journalctl -u pve_exporter --since "1 hour ago"') + print("\n==== journalctl -u pve_exporter Output ====\n") + print(journal_output) + print("\n============================================\n") + raise # Re-raise the original assertion error + + +def test_protecthome_property(host): + s = host.service("pve_exporter") + p = s.systemd_properties + assert p.get("ProtectHome") == "yes" + + +@pytest.mark.parametrize("sockets", [ + "tcp://127.0.0.1:8080", + "tcp://127.0.1.1:8080", +]) +def test_socket(host, sockets): + assert host.socket(sockets).is_listening diff --git a/roles/pve_exporter/molecule/default/molecule.yml b/roles/pve_exporter/molecule/default/molecule.yml new file mode 100644 index 000000000..fea738e2b --- /dev/null +++ b/roles/pve_exporter/molecule/default/molecule.yml @@ -0,0 +1,6 @@ +--- +provisioner: + inventory: + group_vars: + all: + pve_exporter_web_listen_address: "127.0.0.1:9221" diff --git a/roles/pve_exporter/molecule/default/tests/test_default.py b/roles/pve_exporter/molecule/default/tests/test_default.py new file mode 100644 index 000000000..3f70321b2 --- /dev/null +++ b/roles/pve_exporter/molecule/default/tests/test_default.py @@ -0,0 +1,76 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from testinfra_helpers import get_target_hosts + +testinfra_hosts = get_target_hosts() + + +def test_directories(host): + dirs = [ + "/var/lib/pve_exporter" + ] + for dir in dirs: + d = host.file(dir) + assert d.is_directory + assert d.exists + + +def test_files(host): + files = [ + "/etc/systemd/system/pve_exporter.service", + "/usr/local/bin/pve_exporter" + ] + for file in files: + f = host.file(file) + assert f.exists + assert f.is_file + + +def test_permissions_didnt_change(host): + dirs = [ + "/etc", + "/root", + "/usr", + "/var" + ] + for file in dirs: + f = host.file(file) + assert f.exists + assert f.is_directory + assert f.user == "root" + assert f.group == "root" + + +def test_user(host): + assert host.group("pve-exp").exists + assert "pve-exp" in host.user("pve-exp").groups + assert host.user("pve-exp").shell == "/usr/sbin/nologin" + + +def test_service(host): + s = host.service("pve_exporter") + try: + assert s.is_running + except AssertionError: + # Capture service logs + journal_output = host.run('journalctl -u pve_exporter --since "1 hour ago"') + print("\n==== journalctl -u pve_exporter Output ====\n") + print(journal_output) + print("\n============================================\n") + raise # Re-raise the original assertion error + + +def test_protecthome_property(host): + s = host.service("pve_exporter") + p = s.systemd_properties + assert p.get("ProtectHome") == "yes" + + +def test_socket(host): + sockets = [ + "tcp://127.0.0.1:9221" + ] + for socket in sockets: + s = host.socket(socket) + assert s.is_listening diff --git a/roles/pve_exporter/molecule/latest/molecule.yml b/roles/pve_exporter/molecule/latest/molecule.yml new file mode 100644 index 000000000..afb8c1d86 --- /dev/null +++ b/roles/pve_exporter/molecule/latest/molecule.yml @@ -0,0 +1,7 @@ +--- +provisioner: + inventory: + group_vars: + all: + pve_exporter_version: latest + pve_exporter_textfile_dir: /home/pve_exporter diff --git a/roles/pve_exporter/molecule/latest/tests/test_latest.py b/roles/pve_exporter/molecule/latest/tests/test_latest.py new file mode 100644 index 000000000..426ee277a --- /dev/null +++ b/roles/pve_exporter/molecule/latest/tests/test_latest.py @@ -0,0 +1,51 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from testinfra_helpers import get_target_hosts +import pytest + +testinfra_hosts = get_target_hosts() + + +@pytest.mark.parametrize("files", [ + "/etc/systemd/system/pve_exporter.service", + "/usr/local/bin/pve_exporter" +]) +def test_files(host, files): + f = host.file(files) + assert f.exists + assert f.is_file + + +def test_directories(host): + dirs = [ + "/home/pve_exporter" + ] + for dir in dirs: + d = host.file(dir) + assert d.is_directory + assert d.exists + + +def test_service(host): + s = host.service("pve_exporter") + try: + assert s.is_running + except AssertionError: + # Capture service logs + journal_output = host.run('journalctl -u pve_exporter --since "1 hour ago"') + print("\n==== journalctl -u pve_exporter Output ====\n") + print(journal_output) + print("\n============================================\n") + raise # Re-raise the original assertion error + + +def test_protecthome_property(host): + s = host.service("pve_exporter") + p = s.systemd_properties + assert p.get("ProtectHome") == "read-only" + + +def test_socket(host): + s = host.socket("tcp://0.0.0.0:9221") + assert s.is_listening diff --git a/roles/pve_exporter/tasks/configure.yml b/roles/pve_exporter/tasks/configure.yml new file mode 100644 index 000000000..a73ce3d94 --- /dev/null +++ b/roles/pve_exporter/tasks/configure.yml @@ -0,0 +1,30 @@ +--- +- name: Configure + ansible.builtin.include_role: + name: prometheus.prometheus._common + tasks_from: configure.yml + vars: + _common_system_user: "{{ pve_exporter_system_user }}" + _common_system_group: "{{ pve_exporter_system_group }}" + _common_config_dir: "{{ pve_exporter_config_dir }}" + _common_tls_server_config: "{{ pve_exporter_tls_server_config }}" + _common_http_server_config: "{{ pve_exporter_http_server_config }}" + _common_basic_auth_users: "{{ pve_exporter_basic_auth_users }}" + tags: + - pve_exporter + - configure + - pve_exporter_configure + +- name: Create textfile collector dir + ansible.builtin.file: + path: "{{ pve_exporter_textfile_dir }}" + state: directory + owner: "{{ pve_exporter_system_user }}" + group: "{{ pve_exporter_system_group }}" + mode: u+rwX,g+rwX,o=rX + become: true + when: pve_exporter_textfile_dir | length > 0 + tags: + - pve_exporter + - configure + - pve_exporter_configure diff --git a/roles/pve_exporter/tasks/main.yml b/roles/pve_exporter/tasks/main.yml new file mode 100644 index 000000000..85162b857 --- /dev/null +++ b/roles/pve_exporter/tasks/main.yml @@ -0,0 +1,83 @@ +--- +- name: Preflight + ansible.builtin.include_tasks: + file: preflight.yml + tags: + - pve_exporter_install + - pve_exporter_configure + - pve_exporter_run + +- name: Install + tags: + - pve_exporter_install + block: + - name: Install packages dependencies + ansible.builtin.package: + name: + - python3 + - python3-pip + - python3-virtualenv + state: present + - name: "Create system group {{ pve_exporter_system_group }}" + ansible.builtin.group: + name: "{{ pve_exporter_system_group }}" + state: present + - name: "Create system user {{ pve_exporter_system_user }}" + ansible.builtin.user: + name: "{{ pve_exporter_system_user }}" + system: true + shell: "/usr/sbin/nologin" + group: "{{ pve_exporter_system_group }}" + home: "{{ pve_exporter_home }}" + create_home: true + become: true + - name: Install pve_exporter from pip + ansible.builtin.pip: + name: prometheus-pve-exporter + version: "{{ pve_exporter_version }}" + state: present + virtualenv: "{{ pve_exporter_virtualenv }}" + become: true + become_user: "{{ pve_exporter_system_user }}" + - name: Ensure config dir exists + ansible.builtin.file: + path: "{{ pve_exporter_config_dir }}" + state: directory + mode: '0775' # like _common + owner: root + - name: Configure pve_exporter + ansible.builtin.template: + src: "{{ pve_exporter_config_template }}" + dest: "{{ pve_exporter_config_dir }}/pve_exporter.yml" + mode: '0644' + owner: root + notify: + - Restart pve_exporter + +- name: SELinux + ansible.builtin.include_role: + name: prometheus.prometheus._common + tasks_from: selinux.yml + vars: + _common_selinux_port: "{{ pve_exporter_web_listen_address | urlsplit('port') }}" + when: ansible_selinux.status == "enabled" + tags: + - pve_exporter_configure + +- name: Configure + ansible.builtin.include_tasks: + file: configure.yml + tags: + - pve_exporter_configure + +- name: Ensure Proxmox VE Exporter is enabled on boot + become: true + ansible.builtin.systemd: + daemon_reload: true + name: pve_exporter + enabled: true + state: started + when: + - not ansible_check_mode + tags: + - pve_exporter_run diff --git a/roles/pve_exporter/tasks/preflight.yml b/roles/pve_exporter/tasks/preflight.yml new file mode 100644 index 000000000..5dfdc0f1a --- /dev/null +++ b/roles/pve_exporter/tasks/preflight.yml @@ -0,0 +1,65 @@ +--- +- name: Common preflight + ansible.builtin.include_role: + name: prometheus.prometheus._common + tasks_from: preflight.yml + vars: + _common_web_listen_address: "{{ pve_exporter_web_listen_address }}" + +- name: Assert that used version supports listen address type + ansible.builtin.assert: + that: + - >- + pve_exporter_web_listen_address is string + or + ( + pve_exporter_version is version('1.5.0', '>=') and + pve_exporter_web_listen_address | type_debug == "list" + ) + +- name: Assert collectors are not both disabled and enabled at the same time + ansible.builtin.assert: + that: + - "item not in pve_exporter_enabled_collectors" + with_items: "{{ pve_exporter_disabled_collectors }}" + +- name: Assert that TLS config is correct + when: pve_exporter_tls_server_config | length > 0 + block: + - name: Assert that TLS key and cert path are set + ansible.builtin.assert: + that: + - "pve_exporter_tls_server_config.cert_file is defined" + - "pve_exporter_tls_server_config.key_file is defined" + + - name: Check existence of TLS cert file + ansible.builtin.stat: + path: "{{ pve_exporter_tls_server_config.cert_file }}" + register: __pve_exporter_cert_file + + - name: Check existence of TLS key file + ansible.builtin.stat: + path: "{{ pve_exporter_tls_server_config.key_file }}" + register: __pve_exporter_key_file + + - name: Assert that TLS key and cert are present + ansible.builtin.assert: + that: + - "__pve_exporter_cert_file.stat.exists" + - "__pve_exporter_key_file.stat.exists" + +- name: Discover latest version + ansible.builtin.set_fact: + pve_exporter_version: "{{ (lookup('url', 'https://api.github.com/repos/{{ _pve_exporter_repo }}/releases/latest', headers=_github_api_headers, + split_lines=False) | from_json).get('tag_name') | replace('v', '') }}" + run_once: true + until: pve_exporter_version is version('0.0.0', '>=') + retries: 10 + when: + - pve_exporter_version == "latest" + tags: + - pve_exporter + - install + - pve_exporter_install + - download + - pve_exporter_download diff --git a/roles/pve_exporter/templates/pve_exporter.service.j2 b/roles/pve_exporter/templates/pve_exporter.service.j2 new file mode 100644 index 000000000..e66153ce5 --- /dev/null +++ b/roles/pve_exporter/templates/pve_exporter.service.j2 @@ -0,0 +1,114 @@ +{{ ansible_managed | comment }} + +# Exposure level `systemd-analyze security`: 2.7 OK + +[Unit] +Description=Prometheus Proxmox VE Exporter +After=network-online.target + +[Service] +Type=simple +User={{ pve_exporter_system_user }} +Group={{ pve_exporter_system_group }} +ExecStart={{ pve_exporter_virtualenv }}/bin/pve_exporter \ +{% for collector in pve_exporter_enabled_collectors -%} +{% if not collector is mapping %} + '--collector.{{ collector }}' \ +{% else -%} +{% set name, options = (collector.items()|list)[0] -%} + '--collector.{{ name }}' \ +{% for k,v in options|dictsort %} + '--collector.{{ name }}.{{ k }}={{ v }}' \ +{% endfor -%} +{% endif -%} +{% endfor -%} +{% for collector in pve_exporter_disabled_collectors %} + '--no-collector.{{ collector }}' \ +{% endfor %} + --web.listen-address {{ pve_exporter_web_listen_address }} \ + --config.file {{ pve_exporter_config_dir }}/pve_exporter.yml \ + {% if pve_exporter_debug_enable | bool %}-d{% endif %} + + +SyslogIdentifier=pve_exporter +Restart=always +RestartSec=1 +StartLimitInterval=0 + +{% set ns = namespace(protect_home = 'yes') %} +{% for m in ansible_mounts if m.mount.startswith('/home') %} +{% set ns.protect_home = 'read-only' %} +{% endfor %} +{% if pve_exporter_textfile_dir.startswith('/home') %} +{% set ns.protect_home = 'read-only' %} +{% endif %} +ProtectHome={{ ns.protect_home }} +NoNewPrivileges=yes +PrivateTmp=true +UMask=077 + +{% if (ansible_facts.packages.systemd | first).version is version('232', '>=') %} +ProtectSystem=strict +ProtectProc=noaccess +ProtectControlGroups=true +ProtectKernelModules=true +ProtectKernelTunables=yes +ProtectHostname=yes +ProtectClock=yes +LockPersonality=true +RestrictRealtime=true +RestrictNamespaces=yes +RestrictSUIDSGID=yes +MemoryDenyWriteExecute=yes +RemoveIPC=yes +{% else %} +ProtectSystem=full +{% endif %} + +IPAccounting=yes +IPAddressAllow=localhost link-local multicast 10.0.0.0/8 192.168.0.0/16 +# IPAddressDeny= + +CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_DAC_READ_SEARCH +CapabilityBoundingSet=~CAP_BLOCK_SUSPEND +CapabilityBoundingSet=~CAP_BPF +CapabilityBoundingSet=~CAP_DAC_* CAP_FOWNER CAP_IPC_OWNER +CapabilityBoundingSet=~CAP_FSETID CAP_SETFCAP +CapabilityBoundingSet=~CAP_IPC_LOCK +CapabilityBoundingSet=~CAP_KILL +CapabilityBoundingSet=~CAP_LEASE +CapabilityBoundingSet=~CAP_LINUX_IMMUTABLE +CapabilityBoundingSet=~CAP_MKNOD +CapabilityBoundingSet=~CAP_NET_ADMIN +CapabilityBoundingSet=~CAP_NET_BIND_SERVICE CAP_NET_BROADCAST +CapabilityBoundingSet=~CAP_NET_RAW +CapabilityBoundingSet=~CAP_SETUID CAP_SETGID CAP_SETPCAP +CapabilityBoundingSet=~CAP_SYS_CHROOT +CapabilityBoundingSet=~CAP_SYS_PACCT +CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG +CapabilityBoundingSet=~CAP_SYS_ADMIN +CapabilityBoundingSet=~CAP_SYS_NICE CAP_SYS_RESOURCE +CapabilityBoundingSet=~CAP_SYS_BOOT +CapabilityBoundingSet=~CAP_SYS_RAWIO +CapabilityBoundingSet=~CAP_SYS_PTRACE + +{% if not (ansible_virtualization_type is defined and + (ansible_virtualization_type == "lxc" or ansible_virtualization_type == "docker") + ) +%} +SystemCallFilter=@system-service @privileged @resources +{% endif %} +SystemCallFilter=~@clock @cpu-emulation @debug @mount @obsolete @privileged @raw-io @reboot @resources @swap @module +SystemCallArchitectures=native +# When system call is disallowed, return error code instead of killing process +SystemCallErrorNumber=EPERM + +{% if pve_exporter_cgroups_restriction_enable is defined and pve_exporter_cgroups_restriction_enable|bool %} +CPUWeight={{ pve_exporter_cgroups_cpushares | default('80') }} +CPUQuota={{ pve_exporter_cgroups_cpuquota | default('40%') }} +MemoryMax={{ pve_exporter_cgroups_memorylimit | default('2G') }} +IOWeight={{ pve_exporter_cgroups_ioweight | default('80') }} +{% endif %} + +[Install] +WantedBy=multi-user.target diff --git a/roles/pve_exporter/templates/pve_exporter.yml.j2 b/roles/pve_exporter/templates/pve_exporter.yml.j2 new file mode 100644 index 000000000..26fb7ea8a --- /dev/null +++ b/roles/pve_exporter/templates/pve_exporter.yml.j2 @@ -0,0 +1,13 @@ +--- +{{ ansible_managed | comment }} + +default: + user: {{ pve_exporter_auth_user }} +{% if pve_exporter_auth_pass != None %} + password: "{{ pve_exporter_auth_pass }}" +{% elif pve_exporter_auth_token_name != None and pve_exporter_auth_token_value != None %} + token_name: {{ pve_exporter_auth_token_name }} + token_value: {{ pve_exporter_auth_token_value }} +{% endif %} + # Optional: set to false to skip SSL/TLS verification + verify_ssl: {{ pve_exporter_auth_verify_ssl | bool | lower }} diff --git a/roles/pve_exporter/test-requirements.txt b/roles/pve_exporter/test-requirements.txt new file mode 100644 index 000000000..7f0b6e759 --- /dev/null +++ b/roles/pve_exporter/test-requirements.txt @@ -0,0 +1 @@ +bcrypt diff --git a/roles/pve_exporter/vars/main.yml b/roles/pve_exporter/vars/main.yml new file mode 100644 index 000000000..e67e17c18 --- /dev/null +++ b/roles/pve_exporter/vars/main.yml @@ -0,0 +1,3 @@ +--- +_pve_exporter_repo: "prometheus-pve/prometheus-pve-exporter" +_github_api_headers: "{{ {'GITHUB_TOKEN': lookup('ansible.builtin.env', 'GITHUB_TOKEN')} if (lookup('ansible.builtin.env', 'GITHUB_TOKEN')) else {} }}" From 8bacbc0d9aa060062ab3861fd917f0014e52081c Mon Sep 17 00:00:00 2001 From: juju4 Date: Sun, 19 Jan 2025 00:47:27 +0000 Subject: [PATCH 02/28] ci: fix arguments-spec-lint Signed-off-by: juju4 --- roles/pve_exporter/defaults/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/roles/pve_exporter/defaults/main.yml b/roles/pve_exporter/defaults/main.yml index 1be6cc2c4..ee22f8cec 100644 --- a/roles/pve_exporter/defaults/main.yml +++ b/roles/pve_exporter/defaults/main.yml @@ -21,8 +21,6 @@ pve_exporter_enabled_collectors: [] # - resources pve_exporter_disabled_collectors: [] -pve_exporter_debug_enable: false - pve_exporter_home: "/var/lib/pve_exporter" pve_exporter_virtualenv: "/var/lib/pve_exporter/venv" From 32575451389d71693753ddb30a334f224f1e165a Mon Sep 17 00:00:00 2001 From: juju4 Date: Sun, 19 Jan 2025 00:48:23 +0000 Subject: [PATCH 03/28] ci: fix check-unused-variables Signed-off-by: juju4 --- roles/pve_exporter/defaults/main.yml | 3 --- roles/pve_exporter/meta/argument_specs.yml | 7 ------- 2 files changed, 10 deletions(-) diff --git a/roles/pve_exporter/defaults/main.yml b/roles/pve_exporter/defaults/main.yml index ee22f8cec..06c695a91 100644 --- a/roles/pve_exporter/defaults/main.yml +++ b/roles/pve_exporter/defaults/main.yml @@ -8,9 +8,6 @@ pve_exporter_basic_auth_users: {} # just for "prometheus.prometheus._common : Naive assertion of proper listen address" else not used pve_exporter_web_listen_address: "127.0.0.1:9221" -pve_exporter_web_disable_exporter_metrics: false -pve_exporter_web_telemetry_path: "/metrics" - # cluster collectors pve_exporter_enabled_collectors: [] # pve_exporter_enabled_collectors: diff --git a/roles/pve_exporter/meta/argument_specs.yml b/roles/pve_exporter/meta/argument_specs.yml index 336df2e57..bf92be781 100644 --- a/roles/pve_exporter/meta/argument_specs.yml +++ b/roles/pve_exporter/meta/argument_specs.yml @@ -34,16 +34,9 @@ argument_specs: pve_exporter_version: description: "Proxmox VE Exporter package version. Also accepts latest as parameter." default: "1.0.0" - pve_exporter_web_disable_exporter_metrics: - description: "Exclude metrics about the exporter itself (promhttp_*, process_*, go_*)." - type: bool - default: false pve_exporter_web_listen_address: description: "Address on which pve exporter will listen" default: "0.0.0.0:15353" - pve_exporter_web_telemetry_path: - description: "Path under which to expose metrics" - default: "/metrics" pve_exporter_enabled_collectors: description: - "List of dicts defining additionally enabled collectors and their configuration." From eb9a8f40dbf9c77d4ab89c768e77ed3aa8417340 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sun, 19 Jan 2025 20:23:30 +0000 Subject: [PATCH 04/28] fix: remove invalid debug option Signed-off-by: juju4 --- roles/pve_exporter/templates/pve_exporter.service.j2 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/roles/pve_exporter/templates/pve_exporter.service.j2 b/roles/pve_exporter/templates/pve_exporter.service.j2 index e66153ce5..b7c950b17 100644 --- a/roles/pve_exporter/templates/pve_exporter.service.j2 +++ b/roles/pve_exporter/templates/pve_exporter.service.j2 @@ -26,8 +26,7 @@ ExecStart={{ pve_exporter_virtualenv }}/bin/pve_exporter \ '--no-collector.{{ collector }}' \ {% endfor %} --web.listen-address {{ pve_exporter_web_listen_address }} \ - --config.file {{ pve_exporter_config_dir }}/pve_exporter.yml \ - {% if pve_exporter_debug_enable | bool %}-d{% endif %} + --config.file {{ pve_exporter_config_dir }}/pve_exporter.yml SyslogIdentifier=pve_exporter From 2b84d50be31d9a5a1ef7f03a0e6d0d69db6b0d97 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sun, 19 Jan 2025 20:23:47 +0000 Subject: [PATCH 05/28] ci: add test/integration/targets Signed-off-by: juju4 --- .../targets/molecule-pve_exporter-alternative/runme.sh | 4 ++++ .../targets/molecule-pve_exporter-default/runme.sh | 4 ++++ .../integration/targets/molecule-pve_exporter-latest/runme.sh | 4 ++++ 3 files changed, 12 insertions(+) create mode 100755 tests/integration/targets/molecule-pve_exporter-alternative/runme.sh create mode 100755 tests/integration/targets/molecule-pve_exporter-default/runme.sh create mode 100755 tests/integration/targets/molecule-pve_exporter-latest/runme.sh diff --git a/tests/integration/targets/molecule-pve_exporter-alternative/runme.sh b/tests/integration/targets/molecule-pve_exporter-alternative/runme.sh new file mode 100755 index 000000000..d094c3e1b --- /dev/null +++ b/tests/integration/targets/molecule-pve_exporter-alternative/runme.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +collection_root=$(pwd | grep -oP ".+\/ansible_collections\/\w+?\/\w+") +source "$collection_root/tests/integration/molecule.sh" diff --git a/tests/integration/targets/molecule-pve_exporter-default/runme.sh b/tests/integration/targets/molecule-pve_exporter-default/runme.sh new file mode 100755 index 000000000..d094c3e1b --- /dev/null +++ b/tests/integration/targets/molecule-pve_exporter-default/runme.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +collection_root=$(pwd | grep -oP ".+\/ansible_collections\/\w+?\/\w+") +source "$collection_root/tests/integration/molecule.sh" diff --git a/tests/integration/targets/molecule-pve_exporter-latest/runme.sh b/tests/integration/targets/molecule-pve_exporter-latest/runme.sh new file mode 100755 index 000000000..d094c3e1b --- /dev/null +++ b/tests/integration/targets/molecule-pve_exporter-latest/runme.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +collection_root=$(pwd | grep -oP ".+\/ansible_collections\/\w+?\/\w+") +source "$collection_root/tests/integration/molecule.sh" From 186e5a3f1b35269d9db5225e7168e7b88cc0e654 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 25 Jan 2025 21:20:15 +0000 Subject: [PATCH 06/28] feat: option to set up exporter credentials in proxmox if local --- roles/pve_exporter/defaults/main.yml | 2 + roles/pve_exporter/meta/argument_specs.yml | 12 ++++ roles/pve_exporter/tasks/credentials.yml | 72 ++++++++++++++++++++++ roles/pve_exporter/tasks/main.yml | 4 ++ 4 files changed, 90 insertions(+) create mode 100644 roles/pve_exporter/tasks/credentials.yml diff --git a/roles/pve_exporter/defaults/main.yml b/roles/pve_exporter/defaults/main.yml index 06c695a91..4b58bae1b 100644 --- a/roles/pve_exporter/defaults/main.yml +++ b/roles/pve_exporter/defaults/main.yml @@ -33,3 +33,5 @@ pve_exporter_system_group: "pve-exp" pve_exporter_system_user: "{{ pve_exporter_system_group }}" pve_exporter_config_dir: "/etc/pve_exporter" +pve_exporter_set_credential: false +pve_exporter_no_log: true diff --git a/roles/pve_exporter/meta/argument_specs.yml b/roles/pve_exporter/meta/argument_specs.yml index bf92be781..c4482080e 100644 --- a/roles/pve_exporter/meta/argument_specs.yml +++ b/roles/pve_exporter/meta/argument_specs.yml @@ -84,3 +84,15 @@ argument_specs: pve_exporter_config_dir: description: "Path to directory with pve_exporter configuration" default: "/etc/pve_exporter" + pve_exporter_set_credential: + description: + - "I(Advanced)" + - "Create pve_exporter user in proxmox" + type: bool + default: false + pve_exporter_no_log: + description: + - "I(Advanced)" + - "Display/Log ansible tasks containing secrets" + type: bool + default: true diff --git a/roles/pve_exporter/tasks/credentials.yml b/roles/pve_exporter/tasks/credentials.yml new file mode 100644 index 000000000..1892ffbf4 --- /dev/null +++ b/roles/pve_exporter/tasks/credentials.yml @@ -0,0 +1,72 @@ +--- + +- name: Get PVE users + ansible.builtin.command: + cmd: pveum user list + changed_when: false + register: pve_user_list + +- name: Ensure user prometheus@pve + ansible.builtin.command: # noqa no-changed-when + cmd: "pveum user add prometheus@pve --comment 'prometheus user' --password \"{{ lookup('community.general.random_string', length=64, special=false) }}\"" + # changed_when: false + when: + - "'prometheus@pve' not in pve_user_list.stdout" + no_log: "{{ pve_exporter_no_log }}" + +- name: Get prometheus@pve user tokens + ansible.builtin.command: + cmd: pveum user token list prometheus@pve + changed_when: false + register: pve_usertoken_list + +- name: Check if /etc/pve_exporter/pve_exporter.yml exists + ansible.builtin.stat: + path: /etc/pve_exporter/pve_exporter.yml + register: pve_exporter_config + +- name: Delete existing user prometheus@pve token if no pve_exporter.yml + ansible.builtin.command: # noqa no-changed-when + cmd: "pveum user token delete prometheus@pve prometheus-pve-exporter-token" + when: + - not pve_exporter_config.stat.exists + +- name: Create user token + when: > + ("'prometheus-pve-exporter-token' not in pve_usertoken_list.stdout") and + not pve_exporter_config.stat.exists + block: + - name: Ensure user prometheus@pve has a token + ansible.builtin.command: # noqa no-changed-when + cmd: "pveum user token add prometheus@pve prometheus-pve-exporter-token" + register: pve_exporter_token + # changed_when: false + no_log: "{{ pve_exporter_no_log }}" + - name: Ensure user and token have right acl + ansible.builtin.command: # noqa no-changed-when + cmd: "{{ item }}" + loop: + - "pveum acl modify / -user 'prometheus@pve' -role PVEAuditor" + - "pveum acl modify / -token 'prometheus@pve!prometheus-pve-exporter-token' -role PVEAuditor" + # changed_when: false + - name: Set facts for pve_exporter role + ansible.builtin.set_fact: # noqa no-changed-when + pve_exporter_auth_token_name: "prometheus-pve-exporter-token" + pve_exporter_auth_token_value: "{{ pve_exporter_token.stdout | regex_findall('value .* ([0-9a-f-]+) ', multiline=True, ignorecase=True) | first }}" + no_log: "{{ pve_exporter_no_log }}" + +- name: Get existing user token + when: + - "'prometheus-pve-exporter-token' in pve_usertoken_list.stdout" + - pve_exporter_config.stat.exists + block: + - name: Get existing user token + ansible.builtin.command: # noqa no-changed-when + cmd: "grep token_value /etc/pve_exporter/pve_exporter.yml" + changed_when: false + register: pve_exporter_existing_token + - name: Set facts for pve_exporter role - existing token + ansible.builtin.set_fact: # noqa no-changed-when + pve_exporter_auth_token_name: "prometheus-pve-exporter-token" + pve_exporter_auth_token_value: "{{ pve_exporter_existing_token.stdout | regex_findall('token_value: ([0-9a-f-]+)', ignorecase=True) | first }}" + no_log: "{{ pve_exporter_no_log }}" diff --git a/roles/pve_exporter/tasks/main.yml b/roles/pve_exporter/tasks/main.yml index 85162b857..4bcef059a 100644 --- a/roles/pve_exporter/tasks/main.yml +++ b/roles/pve_exporter/tasks/main.yml @@ -7,6 +7,10 @@ - pve_exporter_configure - pve_exporter_run +- name: Credentials + ansible.builtin.import_tasks: credentials.yml + when: pve_exporter_set_credential | bool + - name: Install tags: - pve_exporter_install From 3d614da90822886b8fba3d566177d75b93b53c5f Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 25 Jan 2025 21:48:43 +0000 Subject: [PATCH 07/28] chore: fix ansible-lint --- roles/pve_exporter/tasks/credentials.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/roles/pve_exporter/tasks/credentials.yml b/roles/pve_exporter/tasks/credentials.yml index 1892ffbf4..1fb49e39f 100644 --- a/roles/pve_exporter/tasks/credentials.yml +++ b/roles/pve_exporter/tasks/credentials.yml @@ -48,7 +48,6 @@ loop: - "pveum acl modify / -user 'prometheus@pve' -role PVEAuditor" - "pveum acl modify / -token 'prometheus@pve!prometheus-pve-exporter-token' -role PVEAuditor" - # changed_when: false - name: Set facts for pve_exporter role ansible.builtin.set_fact: # noqa no-changed-when pve_exporter_auth_token_name: "prometheus-pve-exporter-token" From 0f36cadbfa1b81bfd4374a3bf726c80d479750ac Mon Sep 17 00:00:00 2001 From: juju4 Date: Sun, 16 Feb 2025 19:45:50 +0000 Subject: [PATCH 08/28] fix: missing condition --- roles/pve_exporter/tasks/credentials.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/pve_exporter/tasks/credentials.yml b/roles/pve_exporter/tasks/credentials.yml index 1fb49e39f..933a9f2fd 100644 --- a/roles/pve_exporter/tasks/credentials.yml +++ b/roles/pve_exporter/tasks/credentials.yml @@ -30,6 +30,7 @@ cmd: "pveum user token delete prometheus@pve prometheus-pve-exporter-token" when: - not pve_exporter_config.stat.exists + - "'prometheus-pve-exporter-token' in pve_usertoken_list.stdout" - name: Create user token when: > From a20755106c62898987714ba69b36c3200f4e0b06 Mon Sep 17 00:00:00 2001 From: Julien Date: Sat, 22 Feb 2025 22:01:27 +0000 Subject: [PATCH 09/28] style: use ansible_facts python and venv module Co-authored-by: gardar Signed-off-by: Julien --- roles/pve_exporter/tasks/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/pve_exporter/tasks/main.yml b/roles/pve_exporter/tasks/main.yml index 4bcef059a..9171ad41b 100644 --- a/roles/pve_exporter/tasks/main.yml +++ b/roles/pve_exporter/tasks/main.yml @@ -41,6 +41,7 @@ version: "{{ pve_exporter_version }}" state: present virtualenv: "{{ pve_exporter_virtualenv }}" + virtualenv_command: "{{ ansible_facts['discovered_interpreter_python'] }} -m venv" become: true become_user: "{{ pve_exporter_system_user }}" - name: Ensure config dir exists From 8a769c033918a5eac29ae035b2b3bbc72ed56084 Mon Sep 17 00:00:00 2001 From: Julien Date: Sat, 22 Feb 2025 22:02:21 +0000 Subject: [PATCH 10/28] style: ansible_facts python and venv module install Co-authored-by: gardar Signed-off-by: Julien --- roles/pve_exporter/tasks/main.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/roles/pve_exporter/tasks/main.yml b/roles/pve_exporter/tasks/main.yml index 9171ad41b..aa2614d25 100644 --- a/roles/pve_exporter/tasks/main.yml +++ b/roles/pve_exporter/tasks/main.yml @@ -15,13 +15,22 @@ tags: - pve_exporter_install block: + - name: Gather python facts + ansible.builtin.setup: + gather_subset: + - "!all" + - "python" + when: + "('python' not in ansible_facts) or + ('discovered_interpreter_python' not in ansible_facts) + - name: Install packages dependencies ansible.builtin.package: name: - - python3 - - python3-pip - - python3-virtualenv + - "python{{ ansible_facts['ansible_python']['version']['major'] }}-setuptools" + - "python{{ ansible_facts['ansible_python']['version']['major'] }}-venv" state: present + become: true - name: "Create system group {{ pve_exporter_system_group }}" ansible.builtin.group: name: "{{ pve_exporter_system_group }}" From 8dc6667aade25a39457194b0614aaad294731bd0 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 22 Feb 2025 22:05:58 +0000 Subject: [PATCH 11/28] style: split install tasks --- roles/pve_exporter/tasks/install.yml | 55 ++++++++++++++++++++++++++++ roles/pve_exporter/tasks/main.yml | 54 +-------------------------- 2 files changed, 56 insertions(+), 53 deletions(-) create mode 100644 roles/pve_exporter/tasks/install.yml diff --git a/roles/pve_exporter/tasks/install.yml b/roles/pve_exporter/tasks/install.yml new file mode 100644 index 000000000..5b756e02f --- /dev/null +++ b/roles/pve_exporter/tasks/install.yml @@ -0,0 +1,55 @@ +--- + +- name: Gather python facts + ansible.builtin.setup: + gather_subset: + - "!all" + - "python" + when: + "('python' not in ansible_facts) or + ('discovered_interpreter_python' not in ansible_facts) + +- name: Install packages dependencies + ansible.builtin.package: + name: + - "python{{ ansible_facts['ansible_python']['version']['major'] }}-setuptools" + - "python{{ ansible_facts['ansible_python']['version']['major'] }}-venv" + state: present + become: true +- name: "Create system group {{ pve_exporter_system_group }}" + ansible.builtin.group: + name: "{{ pve_exporter_system_group }}" + state: present + become: true +- name: "Create system user {{ pve_exporter_system_user }}" + ansible.builtin.user: + name: "{{ pve_exporter_system_user }}" + system: true + shell: "/usr/sbin/nologin" + group: "{{ pve_exporter_system_group }}" + home: "{{ pve_exporter_home }}" + create_home: true + become: true +- name: Install pve_exporter from pip + ansible.builtin.pip: + name: prometheus-pve-exporter + version: "{{ pve_exporter_version }}" + state: present + virtualenv: "{{ pve_exporter_virtualenv }}" + virtualenv_command: "{{ ansible_facts['discovered_interpreter_python'] }} -m venv" + become: true + become_user: "{{ pve_exporter_system_user }}" +- name: Ensure config dir exists + ansible.builtin.file: + path: "{{ pve_exporter_config_dir }}" + state: directory + mode: '0775' # like _common + owner: root +- name: Configure pve_exporter + ansible.builtin.template: + src: "{{ pve_exporter_config_template }}" + dest: "{{ pve_exporter_config_dir }}/pve_exporter.yml" + mode: '0644' + owner: root + notify: + - Restart pve_exporter diff --git a/roles/pve_exporter/tasks/main.yml b/roles/pve_exporter/tasks/main.yml index aa2614d25..238b3ee87 100644 --- a/roles/pve_exporter/tasks/main.yml +++ b/roles/pve_exporter/tasks/main.yml @@ -12,61 +12,9 @@ when: pve_exporter_set_credential | bool - name: Install + ansible.builtin.import_tasks: install.yml tags: - pve_exporter_install - block: - - name: Gather python facts - ansible.builtin.setup: - gather_subset: - - "!all" - - "python" - when: - "('python' not in ansible_facts) or - ('discovered_interpreter_python' not in ansible_facts) - - - name: Install packages dependencies - ansible.builtin.package: - name: - - "python{{ ansible_facts['ansible_python']['version']['major'] }}-setuptools" - - "python{{ ansible_facts['ansible_python']['version']['major'] }}-venv" - state: present - become: true - - name: "Create system group {{ pve_exporter_system_group }}" - ansible.builtin.group: - name: "{{ pve_exporter_system_group }}" - state: present - - name: "Create system user {{ pve_exporter_system_user }}" - ansible.builtin.user: - name: "{{ pve_exporter_system_user }}" - system: true - shell: "/usr/sbin/nologin" - group: "{{ pve_exporter_system_group }}" - home: "{{ pve_exporter_home }}" - create_home: true - become: true - - name: Install pve_exporter from pip - ansible.builtin.pip: - name: prometheus-pve-exporter - version: "{{ pve_exporter_version }}" - state: present - virtualenv: "{{ pve_exporter_virtualenv }}" - virtualenv_command: "{{ ansible_facts['discovered_interpreter_python'] }} -m venv" - become: true - become_user: "{{ pve_exporter_system_user }}" - - name: Ensure config dir exists - ansible.builtin.file: - path: "{{ pve_exporter_config_dir }}" - state: directory - mode: '0775' # like _common - owner: root - - name: Configure pve_exporter - ansible.builtin.template: - src: "{{ pve_exporter_config_template }}" - dest: "{{ pve_exporter_config_dir }}/pve_exporter.yml" - mode: '0644' - owner: root - notify: - - Restart pve_exporter - name: SELinux ansible.builtin.include_role: From 42a1b68e48f2dbac6aa876effeb76eb23ed7ec21 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 22 Feb 2025 22:15:42 +0000 Subject: [PATCH 12/28] fix: missing closing quotes Signed-off-by: juju4 --- roles/pve_exporter/tasks/install.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/pve_exporter/tasks/install.yml b/roles/pve_exporter/tasks/install.yml index 5b756e02f..920a4ccfb 100644 --- a/roles/pve_exporter/tasks/install.yml +++ b/roles/pve_exporter/tasks/install.yml @@ -7,7 +7,7 @@ - "python" when: "('python' not in ansible_facts) or - ('discovered_interpreter_python' not in ansible_facts) + ('discovered_interpreter_python' not in ansible_facts)" - name: Install packages dependencies ansible.builtin.package: From 13cdda5abd0e9c2a15a450ccf0a1f82b55e3a11a Mon Sep 17 00:00:00 2001 From: juju4 Date: Sun, 2 Mar 2025 21:36:44 +0000 Subject: [PATCH 13/28] fix: s/ansible_python/python/ --- roles/pve_exporter/tasks/install.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/pve_exporter/tasks/install.yml b/roles/pve_exporter/tasks/install.yml index 920a4ccfb..5095f455a 100644 --- a/roles/pve_exporter/tasks/install.yml +++ b/roles/pve_exporter/tasks/install.yml @@ -12,8 +12,8 @@ - name: Install packages dependencies ansible.builtin.package: name: - - "python{{ ansible_facts['ansible_python']['version']['major'] }}-setuptools" - - "python{{ ansible_facts['ansible_python']['version']['major'] }}-venv" + - "python{{ ansible_facts['python']['version']['major'] }}-setuptools" + - "python{{ ansible_facts['python']['version']['major'] }}-venv" state: present become: true - name: "Create system group {{ pve_exporter_system_group }}" From ece87939ac72c6b72d6f7f69875aa1a4cf891332 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 19 Apr 2025 22:57:27 +0000 Subject: [PATCH 14/28] feat: Add role for pve_exporter Signed-off-by: juju4 --- roles/pve_exporter/README.md | 47 ++++++++ roles/pve_exporter/TROUBLESHOOTING.md | 1 + roles/pve_exporter/defaults/main.yml | 37 ++++++ roles/pve_exporter/handlers/main.yml | 10 ++ roles/pve_exporter/meta/argument_specs.yml | 98 +++++++++++++++ roles/pve_exporter/meta/main.yml | 25 ++++ .../molecule/alternative/molecule.yml | 23 ++++ .../alternative/tests/test_alternative.py | 43 +++++++ .../molecule/default/molecule.yml | 6 + .../molecule/default/tests/test_default.py | 76 ++++++++++++ .../pve_exporter/molecule/latest/molecule.yml | 7 ++ .../molecule/latest/tests/test_latest.py | 51 ++++++++ roles/pve_exporter/tasks/configure.yml | 30 +++++ roles/pve_exporter/tasks/credentials.yml | 72 +++++++++++ roles/pve_exporter/tasks/install.yml | 55 +++++++++ roles/pve_exporter/tasks/main.yml | 45 +++++++ roles/pve_exporter/tasks/preflight.yml | 65 ++++++++++ .../templates/pve_exporter.service.j2 | 113 ++++++++++++++++++ .../templates/pve_exporter.yml.j2 | 13 ++ roles/pve_exporter/test-requirements.txt | 1 + roles/pve_exporter/vars/main.yml | 3 + .../runme.sh | 4 + .../molecule-pve_exporter-default/runme.sh | 4 + .../molecule-pve_exporter-latest/runme.sh | 4 + 24 files changed, 833 insertions(+) create mode 100644 roles/pve_exporter/README.md create mode 100644 roles/pve_exporter/TROUBLESHOOTING.md create mode 100644 roles/pve_exporter/defaults/main.yml create mode 100644 roles/pve_exporter/handlers/main.yml create mode 100644 roles/pve_exporter/meta/argument_specs.yml create mode 100644 roles/pve_exporter/meta/main.yml create mode 100644 roles/pve_exporter/molecule/alternative/molecule.yml create mode 100644 roles/pve_exporter/molecule/alternative/tests/test_alternative.py create mode 100644 roles/pve_exporter/molecule/default/molecule.yml create mode 100644 roles/pve_exporter/molecule/default/tests/test_default.py create mode 100644 roles/pve_exporter/molecule/latest/molecule.yml create mode 100644 roles/pve_exporter/molecule/latest/tests/test_latest.py create mode 100644 roles/pve_exporter/tasks/configure.yml create mode 100644 roles/pve_exporter/tasks/credentials.yml create mode 100644 roles/pve_exporter/tasks/install.yml create mode 100644 roles/pve_exporter/tasks/main.yml create mode 100644 roles/pve_exporter/tasks/preflight.yml create mode 100644 roles/pve_exporter/templates/pve_exporter.service.j2 create mode 100644 roles/pve_exporter/templates/pve_exporter.yml.j2 create mode 100644 roles/pve_exporter/test-requirements.txt create mode 100644 roles/pve_exporter/vars/main.yml create mode 100755 tests/integration/targets/molecule-pve_exporter-alternative/runme.sh create mode 100755 tests/integration/targets/molecule-pve_exporter-default/runme.sh create mode 100755 tests/integration/targets/molecule-pve_exporter-latest/runme.sh diff --git a/roles/pve_exporter/README.md b/roles/pve_exporter/README.md new file mode 100644 index 000000000..c1fac072b --- /dev/null +++ b/roles/pve_exporter/README.md @@ -0,0 +1,47 @@ +# Ansible Role: pve exporter + +## Description + +Deploy prometheus [pve exporter](https://github.com/prometheus-pve/prometheus-pve-exporter), written in python, using ansible. + +Possible Grafana dashboard: https://grafana.com/grafana/dashboards/10347-proxmox-via-prometheus/ + +## Requirements + +- Ansible >= 2.9 (It might work on previous versions, but we cannot guarantee it) + +## Role Variables + +All variables which can be overridden are stored in [defaults/main.yml](defaults/main.yml) file as well as in [meta/argument_specs.yml](meta/argument_specs.yml). +Please refer to the [collection docs](https://prometheus-community.github.io/ansible/branch/main/pve_exporter_role.html) for description and default values of the variables. + +## Example + +### Playbook + +Use it in a playbook as follows: +```yaml +- hosts: all + roles: + - prometheus.prometheus.pve_exporter +``` + +## Local Testing + +The preferred way of locally testing the role is to use Docker and [molecule](https://github.com/ansible-community/molecule) (v3.x). You will have to install Docker on your system. See "Get started" for a Docker package suitable for your system. Running your tests is as simple as executing `molecule test`. + +## Continuous Integration + +Combining molecule and circle CI allows us to test how new PRs will behave when used with multiple ansible versions and multiple operating systems. This also allows use to create test scenarios for different role configurations. As a result we have quite a large test matrix which can take more time than local testing, so please be patient. + +## Contributing + +See [contributor guideline](CONTRIBUTING.md). + +## Troubleshooting + +See [troubleshooting](TROUBLESHOOTING.md). + +## License + +This project is licensed under MIT License. See [LICENSE](/LICENSE) for more details. diff --git a/roles/pve_exporter/TROUBLESHOOTING.md b/roles/pve_exporter/TROUBLESHOOTING.md new file mode 100644 index 000000000..4f341277d --- /dev/null +++ b/roles/pve_exporter/TROUBLESHOOTING.md @@ -0,0 +1 @@ +# Troubleshooting diff --git a/roles/pve_exporter/defaults/main.yml b/roles/pve_exporter/defaults/main.yml new file mode 100644 index 000000000..4b58bae1b --- /dev/null +++ b/roles/pve_exporter/defaults/main.yml @@ -0,0 +1,37 @@ +--- +pve_exporter_version: 3.5.0 +# not go, but python pip. placeholders for _common +pve_exporter_textfile_dir: "" +pve_exporter_tls_server_config: {} +pve_exporter_http_server_config: {} +pve_exporter_basic_auth_users: {} +# just for "prometheus.prometheus._common : Naive assertion of proper listen address" else not used +pve_exporter_web_listen_address: "127.0.0.1:9221" + +# cluster collectors +pve_exporter_enabled_collectors: [] +# pve_exporter_enabled_collectors: +# - status +# - version +# - node +# - cluster +# - resources +pve_exporter_disabled_collectors: [] + +pve_exporter_home: "/var/lib/pve_exporter" +pve_exporter_virtualenv: "/var/lib/pve_exporter/venv" + +pve_exporter_auth_user: prometheus@pve +pve_exporter_auth_pass: +pve_exporter_auth_verify_ssl: true +pve_exporter_auth_token_name: your-token-id +pve_exporter_auth_token_value: + +pve_exporter_config_template: pve_exporter.yml.j2 + +pve_exporter_system_group: "pve-exp" +pve_exporter_system_user: "{{ pve_exporter_system_group }}" + +pve_exporter_config_dir: "/etc/pve_exporter" +pve_exporter_set_credential: false +pve_exporter_no_log: true diff --git a/roles/pve_exporter/handlers/main.yml b/roles/pve_exporter/handlers/main.yml new file mode 100644 index 000000000..102878b30 --- /dev/null +++ b/roles/pve_exporter/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Restart pve_exporter + listen: "restart pve_exporter" + become: true + ansible.builtin.systemd: + daemon_reload: true + name: pve_exporter + state: restarted + when: + - not ansible_check_mode diff --git a/roles/pve_exporter/meta/argument_specs.yml b/roles/pve_exporter/meta/argument_specs.yml new file mode 100644 index 000000000..c4482080e --- /dev/null +++ b/roles/pve_exporter/meta/argument_specs.yml @@ -0,0 +1,98 @@ +--- +# yamllint disable rule:line-length +argument_specs: + main: + short_description: "Prometheus Proxmox VE Exporter" + description: + - "Deploy prometheus L(pve exporter,https://github.com/prometheus-pve/prometheus-pve-exporter) using ansible" + author: + - "Prometheus Community" + options: + pve_exporter_home: + description: "Home of pve_exporter user and base file locations" + default: "/var/lib/pve_exporter" + pve_exporter_virtualenv: + description: "Python virtualenv filepath for pve_exporter" + default: "/var/lib/pve_exporter/venv" + pve_exporter_auth_user: + description: "Authentication PVE user name if using user/password auth" + default: "prometheus@pve" + pve_exporter_auth_pass: + description: "Authentication PVE user password if using user/password auth" + pve_exporter_auth_verify_ssl: + description: "Validate proxmox certificate or not" + type: bool + default: true + pve_exporter_auth_token_name: + description: "Authentication PVE user token name if using token auth" + default: your-token-id + pve_exporter_auth_token_value: + description: "Authentication PVE user token value from `pveum user token add ...`" + pve_exporter_config_template: + description: "Configuration template for pve_exporter" + default: "pve_exporter.yml.j2" + pve_exporter_version: + description: "Proxmox VE Exporter package version. Also accepts latest as parameter." + default: "1.0.0" + pve_exporter_web_listen_address: + description: "Address on which pve exporter will listen" + default: "0.0.0.0:15353" + pve_exporter_enabled_collectors: + description: + - "List of dicts defining additionally enabled collectors and their configuration." + - "It adds collectors to L(those enabled by default,https://github.com/prometheus-pve/prometheus-pve-exporter#enabled-by-default)." + type: "list" + default: + - systemd + - textfile: + directory: "{{ pve_exporter_textfile_dir }}" + pve_exporter_disabled_collectors: + description: + - "List of disabled collectors." + - "By default pve_exporter disables collectors listed L(here,https://github.com/prometheus-pve/prometheus-pve-exporter#disabled-by-default)." + type: "list" + elements: "str" + pve_exporter_textfile_dir: + description: + - "Directory used by the L(Textfile Collector,https://github.com/prometheus-pve/prometheus-pve-exporter#textfile-collector)." + - "To get permissions to write metrics in this directory, users must be in C(pve-exp) system group." + - "B(Note:) More information in TROUBLESHOOTING.md guide." + default: "/var/lib/pve_exporter" + pve_exporter_tls_server_config: + description: + - "Configuration for TLS authentication." + - "Keys and values are the same as in L(pve_exporter docs,https://prometheus.io/docs/prometheus/latest/configuration/https/)." + type: "dict" + pve_exporter_http_server_config: + description: + - "Config for HTTP/2 support." + - "Keys and values are the same as in L(pve_exporter docs,https://prometheus.io/docs/prometheus/latest/configuration/https/)." + type: "dict" + pve_exporter_basic_auth_users: + description: "Dictionary of users and password for basic authentication. Passwords are automatically hashed with bcrypt." + type: "dict" + pve_exporter_system_group: + description: + - "I(Advanced)" + - "System group for pve exporter" + default: "pve-exp" + pve_exporter_system_user: + description: + - "I(Advanced)" + - "Proxmox VE Exporter user" + default: "pve-exp" + pve_exporter_config_dir: + description: "Path to directory with pve_exporter configuration" + default: "/etc/pve_exporter" + pve_exporter_set_credential: + description: + - "I(Advanced)" + - "Create pve_exporter user in proxmox" + type: bool + default: false + pve_exporter_no_log: + description: + - "I(Advanced)" + - "Display/Log ansible tasks containing secrets" + type: bool + default: true diff --git a/roles/pve_exporter/meta/main.yml b/roles/pve_exporter/meta/main.yml new file mode 100644 index 000000000..cc004c4e5 --- /dev/null +++ b/roles/pve_exporter/meta/main.yml @@ -0,0 +1,25 @@ +--- +galaxy_info: + author: "Prometheus Community" + description: "Prometheus Proxmox VE Exporter" + license: "Apache" + min_ansible_version: "2.9" + platforms: + - name: "Ubuntu" + versions: + - "focal" + - "jammy" + - "noble" + - name: "Debian" + versions: + - "bullseye" + - name: "EL" + versions: + - "8" + - "9" + galaxy_tags: + - "monitoring" + - "prometheus" + - "exporter" + - "metrics" + - "system" diff --git a/roles/pve_exporter/molecule/alternative/molecule.yml b/roles/pve_exporter/molecule/alternative/molecule.yml new file mode 100644 index 000000000..8cc22dc20 --- /dev/null +++ b/roles/pve_exporter/molecule/alternative/molecule.yml @@ -0,0 +1,23 @@ +--- +provisioner: + playbooks: + prepare: "${MOLECULE_PROJECT_DIRECTORY}/../../.config/molecule/alternative/prepare.yml" + inventory: + group_vars: + all: + pve_exporter_web_listen_address: + - '127.0.0.1:8080' + - '127.0.1.1:8080' + pve_exporter_textfile_dir: "" + pve_exporter_enabled_collectors: + - entropy + pve_exporter_disabled_collectors: + - diskstats + pve_exporter_tls_server_config: + cert_file: /etc/pve_exporter/tls.cert + key_file: /etc/pve_exporter/tls.key + pve_exporter_http_server_config: + http2: true + pve_exporter_basic_auth_users: + randomuser: examplepassword + pve_exporter_version: 3.5.0 diff --git a/roles/pve_exporter/molecule/alternative/tests/test_alternative.py b/roles/pve_exporter/molecule/alternative/tests/test_alternative.py new file mode 100644 index 000000000..f3e79e70a --- /dev/null +++ b/roles/pve_exporter/molecule/alternative/tests/test_alternative.py @@ -0,0 +1,43 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from testinfra_helpers import get_target_hosts +import pytest + +testinfra_hosts = get_target_hosts() + + +def test_directories(host): + dirs = [ + "/var/lib/pve_exporter" + ] + for dir in dirs: + d = host.file(dir) + assert not d.exists + + +def test_service(host): + s = host.service("pve_exporter") + try: + assert s.is_running + except AssertionError: + # Capture service logs + journal_output = host.run('journalctl -u pve_exporter --since "1 hour ago"') + print("\n==== journalctl -u pve_exporter Output ====\n") + print(journal_output) + print("\n============================================\n") + raise # Re-raise the original assertion error + + +def test_protecthome_property(host): + s = host.service("pve_exporter") + p = s.systemd_properties + assert p.get("ProtectHome") == "yes" + + +@pytest.mark.parametrize("sockets", [ + "tcp://127.0.0.1:8080", + "tcp://127.0.1.1:8080", +]) +def test_socket(host, sockets): + assert host.socket(sockets).is_listening diff --git a/roles/pve_exporter/molecule/default/molecule.yml b/roles/pve_exporter/molecule/default/molecule.yml new file mode 100644 index 000000000..fea738e2b --- /dev/null +++ b/roles/pve_exporter/molecule/default/molecule.yml @@ -0,0 +1,6 @@ +--- +provisioner: + inventory: + group_vars: + all: + pve_exporter_web_listen_address: "127.0.0.1:9221" diff --git a/roles/pve_exporter/molecule/default/tests/test_default.py b/roles/pve_exporter/molecule/default/tests/test_default.py new file mode 100644 index 000000000..3f70321b2 --- /dev/null +++ b/roles/pve_exporter/molecule/default/tests/test_default.py @@ -0,0 +1,76 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from testinfra_helpers import get_target_hosts + +testinfra_hosts = get_target_hosts() + + +def test_directories(host): + dirs = [ + "/var/lib/pve_exporter" + ] + for dir in dirs: + d = host.file(dir) + assert d.is_directory + assert d.exists + + +def test_files(host): + files = [ + "/etc/systemd/system/pve_exporter.service", + "/usr/local/bin/pve_exporter" + ] + for file in files: + f = host.file(file) + assert f.exists + assert f.is_file + + +def test_permissions_didnt_change(host): + dirs = [ + "/etc", + "/root", + "/usr", + "/var" + ] + for file in dirs: + f = host.file(file) + assert f.exists + assert f.is_directory + assert f.user == "root" + assert f.group == "root" + + +def test_user(host): + assert host.group("pve-exp").exists + assert "pve-exp" in host.user("pve-exp").groups + assert host.user("pve-exp").shell == "/usr/sbin/nologin" + + +def test_service(host): + s = host.service("pve_exporter") + try: + assert s.is_running + except AssertionError: + # Capture service logs + journal_output = host.run('journalctl -u pve_exporter --since "1 hour ago"') + print("\n==== journalctl -u pve_exporter Output ====\n") + print(journal_output) + print("\n============================================\n") + raise # Re-raise the original assertion error + + +def test_protecthome_property(host): + s = host.service("pve_exporter") + p = s.systemd_properties + assert p.get("ProtectHome") == "yes" + + +def test_socket(host): + sockets = [ + "tcp://127.0.0.1:9221" + ] + for socket in sockets: + s = host.socket(socket) + assert s.is_listening diff --git a/roles/pve_exporter/molecule/latest/molecule.yml b/roles/pve_exporter/molecule/latest/molecule.yml new file mode 100644 index 000000000..afb8c1d86 --- /dev/null +++ b/roles/pve_exporter/molecule/latest/molecule.yml @@ -0,0 +1,7 @@ +--- +provisioner: + inventory: + group_vars: + all: + pve_exporter_version: latest + pve_exporter_textfile_dir: /home/pve_exporter diff --git a/roles/pve_exporter/molecule/latest/tests/test_latest.py b/roles/pve_exporter/molecule/latest/tests/test_latest.py new file mode 100644 index 000000000..426ee277a --- /dev/null +++ b/roles/pve_exporter/molecule/latest/tests/test_latest.py @@ -0,0 +1,51 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from testinfra_helpers import get_target_hosts +import pytest + +testinfra_hosts = get_target_hosts() + + +@pytest.mark.parametrize("files", [ + "/etc/systemd/system/pve_exporter.service", + "/usr/local/bin/pve_exporter" +]) +def test_files(host, files): + f = host.file(files) + assert f.exists + assert f.is_file + + +def test_directories(host): + dirs = [ + "/home/pve_exporter" + ] + for dir in dirs: + d = host.file(dir) + assert d.is_directory + assert d.exists + + +def test_service(host): + s = host.service("pve_exporter") + try: + assert s.is_running + except AssertionError: + # Capture service logs + journal_output = host.run('journalctl -u pve_exporter --since "1 hour ago"') + print("\n==== journalctl -u pve_exporter Output ====\n") + print(journal_output) + print("\n============================================\n") + raise # Re-raise the original assertion error + + +def test_protecthome_property(host): + s = host.service("pve_exporter") + p = s.systemd_properties + assert p.get("ProtectHome") == "read-only" + + +def test_socket(host): + s = host.socket("tcp://0.0.0.0:9221") + assert s.is_listening diff --git a/roles/pve_exporter/tasks/configure.yml b/roles/pve_exporter/tasks/configure.yml new file mode 100644 index 000000000..a73ce3d94 --- /dev/null +++ b/roles/pve_exporter/tasks/configure.yml @@ -0,0 +1,30 @@ +--- +- name: Configure + ansible.builtin.include_role: + name: prometheus.prometheus._common + tasks_from: configure.yml + vars: + _common_system_user: "{{ pve_exporter_system_user }}" + _common_system_group: "{{ pve_exporter_system_group }}" + _common_config_dir: "{{ pve_exporter_config_dir }}" + _common_tls_server_config: "{{ pve_exporter_tls_server_config }}" + _common_http_server_config: "{{ pve_exporter_http_server_config }}" + _common_basic_auth_users: "{{ pve_exporter_basic_auth_users }}" + tags: + - pve_exporter + - configure + - pve_exporter_configure + +- name: Create textfile collector dir + ansible.builtin.file: + path: "{{ pve_exporter_textfile_dir }}" + state: directory + owner: "{{ pve_exporter_system_user }}" + group: "{{ pve_exporter_system_group }}" + mode: u+rwX,g+rwX,o=rX + become: true + when: pve_exporter_textfile_dir | length > 0 + tags: + - pve_exporter + - configure + - pve_exporter_configure diff --git a/roles/pve_exporter/tasks/credentials.yml b/roles/pve_exporter/tasks/credentials.yml new file mode 100644 index 000000000..933a9f2fd --- /dev/null +++ b/roles/pve_exporter/tasks/credentials.yml @@ -0,0 +1,72 @@ +--- + +- name: Get PVE users + ansible.builtin.command: + cmd: pveum user list + changed_when: false + register: pve_user_list + +- name: Ensure user prometheus@pve + ansible.builtin.command: # noqa no-changed-when + cmd: "pveum user add prometheus@pve --comment 'prometheus user' --password \"{{ lookup('community.general.random_string', length=64, special=false) }}\"" + # changed_when: false + when: + - "'prometheus@pve' not in pve_user_list.stdout" + no_log: "{{ pve_exporter_no_log }}" + +- name: Get prometheus@pve user tokens + ansible.builtin.command: + cmd: pveum user token list prometheus@pve + changed_when: false + register: pve_usertoken_list + +- name: Check if /etc/pve_exporter/pve_exporter.yml exists + ansible.builtin.stat: + path: /etc/pve_exporter/pve_exporter.yml + register: pve_exporter_config + +- name: Delete existing user prometheus@pve token if no pve_exporter.yml + ansible.builtin.command: # noqa no-changed-when + cmd: "pveum user token delete prometheus@pve prometheus-pve-exporter-token" + when: + - not pve_exporter_config.stat.exists + - "'prometheus-pve-exporter-token' in pve_usertoken_list.stdout" + +- name: Create user token + when: > + ("'prometheus-pve-exporter-token' not in pve_usertoken_list.stdout") and + not pve_exporter_config.stat.exists + block: + - name: Ensure user prometheus@pve has a token + ansible.builtin.command: # noqa no-changed-when + cmd: "pveum user token add prometheus@pve prometheus-pve-exporter-token" + register: pve_exporter_token + # changed_when: false + no_log: "{{ pve_exporter_no_log }}" + - name: Ensure user and token have right acl + ansible.builtin.command: # noqa no-changed-when + cmd: "{{ item }}" + loop: + - "pveum acl modify / -user 'prometheus@pve' -role PVEAuditor" + - "pveum acl modify / -token 'prometheus@pve!prometheus-pve-exporter-token' -role PVEAuditor" + - name: Set facts for pve_exporter role + ansible.builtin.set_fact: # noqa no-changed-when + pve_exporter_auth_token_name: "prometheus-pve-exporter-token" + pve_exporter_auth_token_value: "{{ pve_exporter_token.stdout | regex_findall('value .* ([0-9a-f-]+) ', multiline=True, ignorecase=True) | first }}" + no_log: "{{ pve_exporter_no_log }}" + +- name: Get existing user token + when: + - "'prometheus-pve-exporter-token' in pve_usertoken_list.stdout" + - pve_exporter_config.stat.exists + block: + - name: Get existing user token + ansible.builtin.command: # noqa no-changed-when + cmd: "grep token_value /etc/pve_exporter/pve_exporter.yml" + changed_when: false + register: pve_exporter_existing_token + - name: Set facts for pve_exporter role - existing token + ansible.builtin.set_fact: # noqa no-changed-when + pve_exporter_auth_token_name: "prometheus-pve-exporter-token" + pve_exporter_auth_token_value: "{{ pve_exporter_existing_token.stdout | regex_findall('token_value: ([0-9a-f-]+)', ignorecase=True) | first }}" + no_log: "{{ pve_exporter_no_log }}" diff --git a/roles/pve_exporter/tasks/install.yml b/roles/pve_exporter/tasks/install.yml new file mode 100644 index 000000000..5095f455a --- /dev/null +++ b/roles/pve_exporter/tasks/install.yml @@ -0,0 +1,55 @@ +--- + +- name: Gather python facts + ansible.builtin.setup: + gather_subset: + - "!all" + - "python" + when: + "('python' not in ansible_facts) or + ('discovered_interpreter_python' not in ansible_facts)" + +- name: Install packages dependencies + ansible.builtin.package: + name: + - "python{{ ansible_facts['python']['version']['major'] }}-setuptools" + - "python{{ ansible_facts['python']['version']['major'] }}-venv" + state: present + become: true +- name: "Create system group {{ pve_exporter_system_group }}" + ansible.builtin.group: + name: "{{ pve_exporter_system_group }}" + state: present + become: true +- name: "Create system user {{ pve_exporter_system_user }}" + ansible.builtin.user: + name: "{{ pve_exporter_system_user }}" + system: true + shell: "/usr/sbin/nologin" + group: "{{ pve_exporter_system_group }}" + home: "{{ pve_exporter_home }}" + create_home: true + become: true +- name: Install pve_exporter from pip + ansible.builtin.pip: + name: prometheus-pve-exporter + version: "{{ pve_exporter_version }}" + state: present + virtualenv: "{{ pve_exporter_virtualenv }}" + virtualenv_command: "{{ ansible_facts['discovered_interpreter_python'] }} -m venv" + become: true + become_user: "{{ pve_exporter_system_user }}" +- name: Ensure config dir exists + ansible.builtin.file: + path: "{{ pve_exporter_config_dir }}" + state: directory + mode: '0775' # like _common + owner: root +- name: Configure pve_exporter + ansible.builtin.template: + src: "{{ pve_exporter_config_template }}" + dest: "{{ pve_exporter_config_dir }}/pve_exporter.yml" + mode: '0644' + owner: root + notify: + - Restart pve_exporter diff --git a/roles/pve_exporter/tasks/main.yml b/roles/pve_exporter/tasks/main.yml new file mode 100644 index 000000000..238b3ee87 --- /dev/null +++ b/roles/pve_exporter/tasks/main.yml @@ -0,0 +1,45 @@ +--- +- name: Preflight + ansible.builtin.include_tasks: + file: preflight.yml + tags: + - pve_exporter_install + - pve_exporter_configure + - pve_exporter_run + +- name: Credentials + ansible.builtin.import_tasks: credentials.yml + when: pve_exporter_set_credential | bool + +- name: Install + ansible.builtin.import_tasks: install.yml + tags: + - pve_exporter_install + +- name: SELinux + ansible.builtin.include_role: + name: prometheus.prometheus._common + tasks_from: selinux.yml + vars: + _common_selinux_port: "{{ pve_exporter_web_listen_address | urlsplit('port') }}" + when: ansible_selinux.status == "enabled" + tags: + - pve_exporter_configure + +- name: Configure + ansible.builtin.include_tasks: + file: configure.yml + tags: + - pve_exporter_configure + +- name: Ensure Proxmox VE Exporter is enabled on boot + become: true + ansible.builtin.systemd: + daemon_reload: true + name: pve_exporter + enabled: true + state: started + when: + - not ansible_check_mode + tags: + - pve_exporter_run diff --git a/roles/pve_exporter/tasks/preflight.yml b/roles/pve_exporter/tasks/preflight.yml new file mode 100644 index 000000000..5dfdc0f1a --- /dev/null +++ b/roles/pve_exporter/tasks/preflight.yml @@ -0,0 +1,65 @@ +--- +- name: Common preflight + ansible.builtin.include_role: + name: prometheus.prometheus._common + tasks_from: preflight.yml + vars: + _common_web_listen_address: "{{ pve_exporter_web_listen_address }}" + +- name: Assert that used version supports listen address type + ansible.builtin.assert: + that: + - >- + pve_exporter_web_listen_address is string + or + ( + pve_exporter_version is version('1.5.0', '>=') and + pve_exporter_web_listen_address | type_debug == "list" + ) + +- name: Assert collectors are not both disabled and enabled at the same time + ansible.builtin.assert: + that: + - "item not in pve_exporter_enabled_collectors" + with_items: "{{ pve_exporter_disabled_collectors }}" + +- name: Assert that TLS config is correct + when: pve_exporter_tls_server_config | length > 0 + block: + - name: Assert that TLS key and cert path are set + ansible.builtin.assert: + that: + - "pve_exporter_tls_server_config.cert_file is defined" + - "pve_exporter_tls_server_config.key_file is defined" + + - name: Check existence of TLS cert file + ansible.builtin.stat: + path: "{{ pve_exporter_tls_server_config.cert_file }}" + register: __pve_exporter_cert_file + + - name: Check existence of TLS key file + ansible.builtin.stat: + path: "{{ pve_exporter_tls_server_config.key_file }}" + register: __pve_exporter_key_file + + - name: Assert that TLS key and cert are present + ansible.builtin.assert: + that: + - "__pve_exporter_cert_file.stat.exists" + - "__pve_exporter_key_file.stat.exists" + +- name: Discover latest version + ansible.builtin.set_fact: + pve_exporter_version: "{{ (lookup('url', 'https://api.github.com/repos/{{ _pve_exporter_repo }}/releases/latest', headers=_github_api_headers, + split_lines=False) | from_json).get('tag_name') | replace('v', '') }}" + run_once: true + until: pve_exporter_version is version('0.0.0', '>=') + retries: 10 + when: + - pve_exporter_version == "latest" + tags: + - pve_exporter + - install + - pve_exporter_install + - download + - pve_exporter_download diff --git a/roles/pve_exporter/templates/pve_exporter.service.j2 b/roles/pve_exporter/templates/pve_exporter.service.j2 new file mode 100644 index 000000000..b7c950b17 --- /dev/null +++ b/roles/pve_exporter/templates/pve_exporter.service.j2 @@ -0,0 +1,113 @@ +{{ ansible_managed | comment }} + +# Exposure level `systemd-analyze security`: 2.7 OK + +[Unit] +Description=Prometheus Proxmox VE Exporter +After=network-online.target + +[Service] +Type=simple +User={{ pve_exporter_system_user }} +Group={{ pve_exporter_system_group }} +ExecStart={{ pve_exporter_virtualenv }}/bin/pve_exporter \ +{% for collector in pve_exporter_enabled_collectors -%} +{% if not collector is mapping %} + '--collector.{{ collector }}' \ +{% else -%} +{% set name, options = (collector.items()|list)[0] -%} + '--collector.{{ name }}' \ +{% for k,v in options|dictsort %} + '--collector.{{ name }}.{{ k }}={{ v }}' \ +{% endfor -%} +{% endif -%} +{% endfor -%} +{% for collector in pve_exporter_disabled_collectors %} + '--no-collector.{{ collector }}' \ +{% endfor %} + --web.listen-address {{ pve_exporter_web_listen_address }} \ + --config.file {{ pve_exporter_config_dir }}/pve_exporter.yml + + +SyslogIdentifier=pve_exporter +Restart=always +RestartSec=1 +StartLimitInterval=0 + +{% set ns = namespace(protect_home = 'yes') %} +{% for m in ansible_mounts if m.mount.startswith('/home') %} +{% set ns.protect_home = 'read-only' %} +{% endfor %} +{% if pve_exporter_textfile_dir.startswith('/home') %} +{% set ns.protect_home = 'read-only' %} +{% endif %} +ProtectHome={{ ns.protect_home }} +NoNewPrivileges=yes +PrivateTmp=true +UMask=077 + +{% if (ansible_facts.packages.systemd | first).version is version('232', '>=') %} +ProtectSystem=strict +ProtectProc=noaccess +ProtectControlGroups=true +ProtectKernelModules=true +ProtectKernelTunables=yes +ProtectHostname=yes +ProtectClock=yes +LockPersonality=true +RestrictRealtime=true +RestrictNamespaces=yes +RestrictSUIDSGID=yes +MemoryDenyWriteExecute=yes +RemoveIPC=yes +{% else %} +ProtectSystem=full +{% endif %} + +IPAccounting=yes +IPAddressAllow=localhost link-local multicast 10.0.0.0/8 192.168.0.0/16 +# IPAddressDeny= + +CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_DAC_READ_SEARCH +CapabilityBoundingSet=~CAP_BLOCK_SUSPEND +CapabilityBoundingSet=~CAP_BPF +CapabilityBoundingSet=~CAP_DAC_* CAP_FOWNER CAP_IPC_OWNER +CapabilityBoundingSet=~CAP_FSETID CAP_SETFCAP +CapabilityBoundingSet=~CAP_IPC_LOCK +CapabilityBoundingSet=~CAP_KILL +CapabilityBoundingSet=~CAP_LEASE +CapabilityBoundingSet=~CAP_LINUX_IMMUTABLE +CapabilityBoundingSet=~CAP_MKNOD +CapabilityBoundingSet=~CAP_NET_ADMIN +CapabilityBoundingSet=~CAP_NET_BIND_SERVICE CAP_NET_BROADCAST +CapabilityBoundingSet=~CAP_NET_RAW +CapabilityBoundingSet=~CAP_SETUID CAP_SETGID CAP_SETPCAP +CapabilityBoundingSet=~CAP_SYS_CHROOT +CapabilityBoundingSet=~CAP_SYS_PACCT +CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG +CapabilityBoundingSet=~CAP_SYS_ADMIN +CapabilityBoundingSet=~CAP_SYS_NICE CAP_SYS_RESOURCE +CapabilityBoundingSet=~CAP_SYS_BOOT +CapabilityBoundingSet=~CAP_SYS_RAWIO +CapabilityBoundingSet=~CAP_SYS_PTRACE + +{% if not (ansible_virtualization_type is defined and + (ansible_virtualization_type == "lxc" or ansible_virtualization_type == "docker") + ) +%} +SystemCallFilter=@system-service @privileged @resources +{% endif %} +SystemCallFilter=~@clock @cpu-emulation @debug @mount @obsolete @privileged @raw-io @reboot @resources @swap @module +SystemCallArchitectures=native +# When system call is disallowed, return error code instead of killing process +SystemCallErrorNumber=EPERM + +{% if pve_exporter_cgroups_restriction_enable is defined and pve_exporter_cgroups_restriction_enable|bool %} +CPUWeight={{ pve_exporter_cgroups_cpushares | default('80') }} +CPUQuota={{ pve_exporter_cgroups_cpuquota | default('40%') }} +MemoryMax={{ pve_exporter_cgroups_memorylimit | default('2G') }} +IOWeight={{ pve_exporter_cgroups_ioweight | default('80') }} +{% endif %} + +[Install] +WantedBy=multi-user.target diff --git a/roles/pve_exporter/templates/pve_exporter.yml.j2 b/roles/pve_exporter/templates/pve_exporter.yml.j2 new file mode 100644 index 000000000..26fb7ea8a --- /dev/null +++ b/roles/pve_exporter/templates/pve_exporter.yml.j2 @@ -0,0 +1,13 @@ +--- +{{ ansible_managed | comment }} + +default: + user: {{ pve_exporter_auth_user }} +{% if pve_exporter_auth_pass != None %} + password: "{{ pve_exporter_auth_pass }}" +{% elif pve_exporter_auth_token_name != None and pve_exporter_auth_token_value != None %} + token_name: {{ pve_exporter_auth_token_name }} + token_value: {{ pve_exporter_auth_token_value }} +{% endif %} + # Optional: set to false to skip SSL/TLS verification + verify_ssl: {{ pve_exporter_auth_verify_ssl | bool | lower }} diff --git a/roles/pve_exporter/test-requirements.txt b/roles/pve_exporter/test-requirements.txt new file mode 100644 index 000000000..7f0b6e759 --- /dev/null +++ b/roles/pve_exporter/test-requirements.txt @@ -0,0 +1 @@ +bcrypt diff --git a/roles/pve_exporter/vars/main.yml b/roles/pve_exporter/vars/main.yml new file mode 100644 index 000000000..e67e17c18 --- /dev/null +++ b/roles/pve_exporter/vars/main.yml @@ -0,0 +1,3 @@ +--- +_pve_exporter_repo: "prometheus-pve/prometheus-pve-exporter" +_github_api_headers: "{{ {'GITHUB_TOKEN': lookup('ansible.builtin.env', 'GITHUB_TOKEN')} if (lookup('ansible.builtin.env', 'GITHUB_TOKEN')) else {} }}" diff --git a/tests/integration/targets/molecule-pve_exporter-alternative/runme.sh b/tests/integration/targets/molecule-pve_exporter-alternative/runme.sh new file mode 100755 index 000000000..d094c3e1b --- /dev/null +++ b/tests/integration/targets/molecule-pve_exporter-alternative/runme.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +collection_root=$(pwd | grep -oP ".+\/ansible_collections\/\w+?\/\w+") +source "$collection_root/tests/integration/molecule.sh" diff --git a/tests/integration/targets/molecule-pve_exporter-default/runme.sh b/tests/integration/targets/molecule-pve_exporter-default/runme.sh new file mode 100755 index 000000000..d094c3e1b --- /dev/null +++ b/tests/integration/targets/molecule-pve_exporter-default/runme.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +collection_root=$(pwd | grep -oP ".+\/ansible_collections\/\w+?\/\w+") +source "$collection_root/tests/integration/molecule.sh" diff --git a/tests/integration/targets/molecule-pve_exporter-latest/runme.sh b/tests/integration/targets/molecule-pve_exporter-latest/runme.sh new file mode 100755 index 000000000..d094c3e1b --- /dev/null +++ b/tests/integration/targets/molecule-pve_exporter-latest/runme.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +collection_root=$(pwd | grep -oP ".+\/ansible_collections\/\w+?\/\w+") +source "$collection_root/tests/integration/molecule.sh" From baa0b0af0d80aa939a2f58c2923a7be3db4406c5 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 14 Jun 2025 20:02:49 +0000 Subject: [PATCH 15/28] fix(redhat): add epel role for almalinux integration tests --- roles/pve_exporter/meta/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/roles/pve_exporter/meta/main.yml b/roles/pve_exporter/meta/main.yml index cc004c4e5..b99b6b62d 100644 --- a/roles/pve_exporter/meta/main.yml +++ b/roles/pve_exporter/meta/main.yml @@ -23,3 +23,5 @@ galaxy_info: - "exporter" - "metrics" - "system" +dependencies: + - role: juju4.redhat_epel From c239497b7398f3d762ea15ab7aab5eb3fafe7367 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 14 Jun 2025 20:06:56 +0000 Subject: [PATCH 16/28] fix(redhat): add epel role for almalinux integration tests - requirements.yml --- requirements.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/requirements.yml b/requirements.yml index 7bb16e48c..835d0bc0d 100644 --- a/requirements.yml +++ b/requirements.yml @@ -4,3 +4,8 @@ collections: type: git - name: https://github.com/ansible-collections/community.crypto.git type: git + +roles: + - src: https://github.com/juju4/ansible-redhat-epel/ + version: main + name: juju4.redhat_epel From 0d3e32a9a0f4658de82ebbc1587a6141f0991e06 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 14 Jun 2025 20:20:13 +0000 Subject: [PATCH 17/28] Revert "fix(redhat): add epel role for almalinux integration tests - requirements.yml" This reverts commit c239497b7398f3d762ea15ab7aab5eb3fafe7367. --- requirements.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/requirements.yml b/requirements.yml index 835d0bc0d..7bb16e48c 100644 --- a/requirements.yml +++ b/requirements.yml @@ -4,8 +4,3 @@ collections: type: git - name: https://github.com/ansible-collections/community.crypto.git type: git - -roles: - - src: https://github.com/juju4/ansible-redhat-epel/ - version: main - name: juju4.redhat_epel From 5e6be660f60d114eb9b49b686ce35aaeb1036229 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 14 Jun 2025 20:20:23 +0000 Subject: [PATCH 18/28] Revert "fix(redhat): add epel role for almalinux integration tests" This reverts commit baa0b0af0d80aa939a2f58c2923a7be3db4406c5. --- roles/pve_exporter/meta/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/roles/pve_exporter/meta/main.yml b/roles/pve_exporter/meta/main.yml index b99b6b62d..cc004c4e5 100644 --- a/roles/pve_exporter/meta/main.yml +++ b/roles/pve_exporter/meta/main.yml @@ -23,5 +23,3 @@ galaxy_info: - "exporter" - "metrics" - "system" -dependencies: - - role: juju4.redhat_epel From 6b957bf51367b8160b6ee43b91b9461716c670e9 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 14 Jun 2025 20:25:38 +0000 Subject: [PATCH 19/28] fix(redhat): add epel for almalinux inline --- roles/pve_exporter/tasks/main.yml | 5 ++ roles/pve_exporter/tasks/redhat-epel.yml | 66 ++++++++++++++++++++++++ roles/pve_exporter/vars/RedHat-10.yml | 8 +++ roles/pve_exporter/vars/RedHat-8.yml | 6 +++ roles/pve_exporter/vars/RedHat-9.yml | 6 +++ 5 files changed, 91 insertions(+) create mode 100644 roles/pve_exporter/tasks/redhat-epel.yml create mode 100644 roles/pve_exporter/vars/RedHat-10.yml create mode 100644 roles/pve_exporter/vars/RedHat-8.yml create mode 100644 roles/pve_exporter/vars/RedHat-9.yml diff --git a/roles/pve_exporter/tasks/main.yml b/roles/pve_exporter/tasks/main.yml index 238b3ee87..08921d334 100644 --- a/roles/pve_exporter/tasks/main.yml +++ b/roles/pve_exporter/tasks/main.yml @@ -1,4 +1,9 @@ --- + +- name: RedHat | Import redhat-epel + ansible.builtin.import_tasks: redhat-epel.yml + when: ansible_os_family == "RedHat" + - name: Preflight ansible.builtin.include_tasks: file: preflight.yml diff --git a/roles/pve_exporter/tasks/redhat-epel.yml b/roles/pve_exporter/tasks/redhat-epel.yml new file mode 100644 index 000000000..e03d4bcc2 --- /dev/null +++ b/roles/pve_exporter/tasks/redhat-epel.yml @@ -0,0 +1,66 @@ +--- + +- name: Include version-specific variables for RedHat + ansible.builtin.include_vars: "RedHat-{{ ansible_distribution_version.split('.')[0] }}.yml" + +- name: RedHat | Install dependencies packages + ansible.builtin.package: + name: "{{ redhat_epel_pkgs | default([]) }}" + state: present + +- name: Check if epel key already retrieved + ansible.builtin.stat: + path: "/var/tmp/{{ epel_rpm_key | basename }}" + register: epelkey +- name: Download epel rpm key + ansible.builtin.get_url: + url: "{{ epel_rpm_key }}" + dest: "/var/tmp/{{ epel_rpm_key | basename }}" + mode: '0600' + checksum: "sha256:{{ epel_rpm_key_hash }}" + validate_certs: false + register: dl_result + until: dl_result is success + when: not epelkey.stat.exists and epel_rpm_key +- name: Installing epel rpm key + # rpm_key: key={{ epel_rpm_key }} + ansible.builtin.rpm_key: + key: "/var/tmp/{{ epel_rpm_key | basename }}" + state: present + when: epel_rpm_key | string + register: pkg_result + until: pkg_result is success + become: true + +- name: Gather package facts + ansible.builtin.package_facts: + manager: auto + +- name: RedHat | Install Fedora epel + ansible.builtin.package: + name: "{{ redhat_epel_rpm }}" + state: present + when: "'epel-release' not in ansible_facts.packages" + register: pkg_result + until: pkg_result is success + become: true + +- name: RedHat | Ensure dnf config-manager is present + ansible.builtin.package: + name: 'dnf-command(config-manager)' + state: present + register: pkg_result + until: pkg_result is success + become: true + when: + - ansible_distribution_major_version | int >= 9 + +- name: RedHat9 | Enable CRB repo + community.general.ini_file: + path: "/etc/yum.repos.d/{{ ansible_distribution | lower }}.repo" + section: crb + option: enabled + value: '1' + mode: '0644' + when: + - ansible_distribution_major_version | int == 9 diff --git a/roles/pve_exporter/vars/RedHat-10.yml b/roles/pve_exporter/vars/RedHat-10.yml new file mode 100644 index 000000000..c5d4a7598 --- /dev/null +++ b/roles/pve_exporter/vars/RedHat-10.yml @@ -0,0 +1,8 @@ +--- + +epel_rpm_key: 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-10' +epel_rpm_key_hash: de390fc168eae5ab2852e9e93d34a0b9ddf05cf9ce90ee28d97de26a4b1f6b93 + +redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm" +redhat_epel_pkgs: + - gnupg2 diff --git a/roles/pve_exporter/vars/RedHat-8.yml b/roles/pve_exporter/vars/RedHat-8.yml new file mode 100644 index 000000000..c2fbd440b --- /dev/null +++ b/roles/pve_exporter/vars/RedHat-8.yml @@ -0,0 +1,6 @@ +--- + +epel_rpm_key: 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8' +epel_rpm_key_hash: cd1db21a863185127f2e3b264c97fb1c6c44c316385707999041ea475c110d1c + +redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm" diff --git a/roles/pve_exporter/vars/RedHat-9.yml b/roles/pve_exporter/vars/RedHat-9.yml new file mode 100644 index 000000000..74d54724b --- /dev/null +++ b/roles/pve_exporter/vars/RedHat-9.yml @@ -0,0 +1,6 @@ +--- + +epel_rpm_key: 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-9' +epel_rpm_key_hash: fcf0eab4f05a1c0de6363ac4b707600a27a9d774e9b491059e59e6921b255a84 + +redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm" From 54dfdf1c767902a679ef27afffa65cf2bd2279ff Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 14 Jun 2025 20:39:05 +0000 Subject: [PATCH 20/28] fix: ansible variables under ansible_facts --- roles/pve_exporter/tasks/main.yml | 2 +- roles/pve_exporter/tasks/redhat-epel.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/roles/pve_exporter/tasks/main.yml b/roles/pve_exporter/tasks/main.yml index 08921d334..61cc2e72f 100644 --- a/roles/pve_exporter/tasks/main.yml +++ b/roles/pve_exporter/tasks/main.yml @@ -2,7 +2,7 @@ - name: RedHat | Import redhat-epel ansible.builtin.import_tasks: redhat-epel.yml - when: ansible_os_family == "RedHat" + when: ansible_facts['os_family'] == "RedHat" - name: Preflight ansible.builtin.include_tasks: diff --git a/roles/pve_exporter/tasks/redhat-epel.yml b/roles/pve_exporter/tasks/redhat-epel.yml index e03d4bcc2..c5ade2972 100644 --- a/roles/pve_exporter/tasks/redhat-epel.yml +++ b/roles/pve_exporter/tasks/redhat-epel.yml @@ -1,7 +1,7 @@ --- - name: Include version-specific variables for RedHat - ansible.builtin.include_vars: "RedHat-{{ ansible_distribution_version.split('.')[0] }}.yml" + ansible.builtin.include_vars: "RedHat-{{ ansible_facts['distribution_major_version'] }}.yml" - name: RedHat | Install dependencies packages ansible.builtin.package: @@ -53,14 +53,14 @@ until: pkg_result is success become: true when: - - ansible_distribution_major_version | int >= 9 + - ansible_facts['distribution_major_version'] | int >= 9 - name: RedHat9 | Enable CRB repo community.general.ini_file: - path: "/etc/yum.repos.d/{{ ansible_distribution | lower }}.repo" + path: "/etc/yum.repos.d/{{ ansible_facts['distribution'] | lower }}.repo" section: crb option: enabled value: '1' mode: '0644' when: - - ansible_distribution_major_version | int == 9 + - ansible_facts['distribution_major_version'] | int == 9 From ea7545b2fb0f2abc8b2604e0b520948caacf42db Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 14 Jun 2025 20:44:21 +0000 Subject: [PATCH 21/28] fix: ansible variables under ansible_facts (2) --- roles/pve_exporter/vars/RedHat-10.yml | 2 +- roles/pve_exporter/vars/RedHat-8.yml | 2 +- roles/pve_exporter/vars/RedHat-9.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/roles/pve_exporter/vars/RedHat-10.yml b/roles/pve_exporter/vars/RedHat-10.yml index c5d4a7598..3cd557547 100644 --- a/roles/pve_exporter/vars/RedHat-10.yml +++ b/roles/pve_exporter/vars/RedHat-10.yml @@ -3,6 +3,6 @@ epel_rpm_key: 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-10' epel_rpm_key_hash: de390fc168eae5ab2852e9e93d34a0b9ddf05cf9ce90ee28d97de26a4b1f6b93 -redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm" +redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_facts['distribution_major_version'] }}.noarch.rpm" redhat_epel_pkgs: - gnupg2 diff --git a/roles/pve_exporter/vars/RedHat-8.yml b/roles/pve_exporter/vars/RedHat-8.yml index c2fbd440b..7f447fdb8 100644 --- a/roles/pve_exporter/vars/RedHat-8.yml +++ b/roles/pve_exporter/vars/RedHat-8.yml @@ -3,4 +3,4 @@ epel_rpm_key: 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8' epel_rpm_key_hash: cd1db21a863185127f2e3b264c97fb1c6c44c316385707999041ea475c110d1c -redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm" +redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_facts['distribution_major_version'] }}.noarch.rpm" diff --git a/roles/pve_exporter/vars/RedHat-9.yml b/roles/pve_exporter/vars/RedHat-9.yml index 74d54724b..3b0ad2468 100644 --- a/roles/pve_exporter/vars/RedHat-9.yml +++ b/roles/pve_exporter/vars/RedHat-9.yml @@ -3,4 +3,4 @@ epel_rpm_key: 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-9' epel_rpm_key_hash: fcf0eab4f05a1c0de6363ac4b707600a27a9d774e9b491059e59e6921b255a84 -redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_distribution_major_version }}.noarch.rpm" +redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_facts['distribution_major_version'] }}.noarch.rpm" From 2e20da2f443cd56868e4f7b6624d4230aefebf91 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 14 Jun 2025 20:51:25 +0000 Subject: [PATCH 22/28] fix(redhat): remove crb on redhat-9 --- roles/pve_exporter/tasks/redhat-epel.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/roles/pve_exporter/tasks/redhat-epel.yml b/roles/pve_exporter/tasks/redhat-epel.yml index c5ade2972..bc314e8ae 100644 --- a/roles/pve_exporter/tasks/redhat-epel.yml +++ b/roles/pve_exporter/tasks/redhat-epel.yml @@ -54,13 +54,3 @@ become: true when: - ansible_facts['distribution_major_version'] | int >= 9 - -- name: RedHat9 | Enable CRB repo - community.general.ini_file: - path: "/etc/yum.repos.d/{{ ansible_facts['distribution'] | lower }}.repo" - section: crb - option: enabled - value: '1' - mode: '0644' - when: - - ansible_facts['distribution_major_version'] | int == 9 From 7726b2c2cad1b5d068cf9e132f9078b705f462cb Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 14 Jun 2025 21:00:07 +0000 Subject: [PATCH 23/28] fix(redhat): switch from venv to virtualenv --- roles/pve_exporter/tasks/install.yml | 6 ++---- roles/pve_exporter/vars/RedHat-10.yml | 4 ++++ roles/pve_exporter/vars/RedHat-8.yml | 4 ++++ roles/pve_exporter/vars/RedHat-9.yml | 4 ++++ roles/pve_exporter/vars/main.yml | 3 +++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/roles/pve_exporter/tasks/install.yml b/roles/pve_exporter/tasks/install.yml index 5095f455a..b56511d58 100644 --- a/roles/pve_exporter/tasks/install.yml +++ b/roles/pve_exporter/tasks/install.yml @@ -11,9 +11,7 @@ - name: Install packages dependencies ansible.builtin.package: - name: - - "python{{ ansible_facts['python']['version']['major'] }}-setuptools" - - "python{{ ansible_facts['python']['version']['major'] }}-venv" + name: "{{ _pve_exporter_pkgs }}" state: present become: true - name: "Create system group {{ pve_exporter_system_group }}" @@ -36,7 +34,7 @@ version: "{{ pve_exporter_version }}" state: present virtualenv: "{{ pve_exporter_virtualenv }}" - virtualenv_command: "{{ ansible_facts['discovered_interpreter_python'] }} -m venv" + virtualenv_command: "{% if ansible_facts['os_family'] != 'RedHat' %}{{ ansible_facts['discovered_interpreter_python'] }} -m venv{% endif %}" become: true become_user: "{{ pve_exporter_system_user }}" - name: Ensure config dir exists diff --git a/roles/pve_exporter/vars/RedHat-10.yml b/roles/pve_exporter/vars/RedHat-10.yml index 3cd557547..a8d934870 100644 --- a/roles/pve_exporter/vars/RedHat-10.yml +++ b/roles/pve_exporter/vars/RedHat-10.yml @@ -6,3 +6,7 @@ epel_rpm_key_hash: de390fc168eae5ab2852e9e93d34a0b9ddf05cf9ce90ee28d97de26a4b1f6 redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_facts['distribution_major_version'] }}.noarch.rpm" redhat_epel_pkgs: - gnupg2 + +_pve_exporter_pkgs: + - "python{{ ansible_facts['python']['version']['major'] }}-setuptools" + - "python{{ ansible_facts['python']['version']['major'] }}-virtualenv" diff --git a/roles/pve_exporter/vars/RedHat-8.yml b/roles/pve_exporter/vars/RedHat-8.yml index 7f447fdb8..b41a2e74d 100644 --- a/roles/pve_exporter/vars/RedHat-8.yml +++ b/roles/pve_exporter/vars/RedHat-8.yml @@ -4,3 +4,7 @@ epel_rpm_key: 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-8' epel_rpm_key_hash: cd1db21a863185127f2e3b264c97fb1c6c44c316385707999041ea475c110d1c redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_facts['distribution_major_version'] }}.noarch.rpm" + +_pve_exporter_pkgs: + - "python{{ ansible_facts['python']['version']['major'] }}-setuptools" + - "python{{ ansible_facts['python']['version']['major'] }}-virtualenv" diff --git a/roles/pve_exporter/vars/RedHat-9.yml b/roles/pve_exporter/vars/RedHat-9.yml index 3b0ad2468..908b72425 100644 --- a/roles/pve_exporter/vars/RedHat-9.yml +++ b/roles/pve_exporter/vars/RedHat-9.yml @@ -4,3 +4,7 @@ epel_rpm_key: 'https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-9' epel_rpm_key_hash: fcf0eab4f05a1c0de6363ac4b707600a27a9d774e9b491059e59e6921b255a84 redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_facts['distribution_major_version'] }}.noarch.rpm" + +_pve_exporter_pkgs: + - "python{{ ansible_facts['python']['version']['major'] }}-setuptools" + - "python{{ ansible_facts['python']['version']['major'] }}-virtualenv" diff --git a/roles/pve_exporter/vars/main.yml b/roles/pve_exporter/vars/main.yml index e67e17c18..655579822 100644 --- a/roles/pve_exporter/vars/main.yml +++ b/roles/pve_exporter/vars/main.yml @@ -1,3 +1,6 @@ --- _pve_exporter_repo: "prometheus-pve/prometheus-pve-exporter" _github_api_headers: "{{ {'GITHUB_TOKEN': lookup('ansible.builtin.env', 'GITHUB_TOKEN')} if (lookup('ansible.builtin.env', 'GITHUB_TOKEN')) else {} }}" +_pve_exporter_pkgs: + - "python{{ ansible_facts['python']['version']['major'] }}-setuptools" + - "python{{ ansible_facts['python']['version']['major'] }}-venv" From e1a09c6a8abbfe39ded1a7366a0a29f66af51423 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 7 Feb 2026 19:39:54 +0000 Subject: [PATCH 24/28] fix: INJECT_FACTS_AS_VARS - porting ansible 13 --- roles/pve_exporter/tasks/main.yml | 2 +- roles/pve_exporter/templates/pve_exporter.service.j2 | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/roles/pve_exporter/tasks/main.yml b/roles/pve_exporter/tasks/main.yml index 61cc2e72f..384a7769f 100644 --- a/roles/pve_exporter/tasks/main.yml +++ b/roles/pve_exporter/tasks/main.yml @@ -27,7 +27,7 @@ tasks_from: selinux.yml vars: _common_selinux_port: "{{ pve_exporter_web_listen_address | urlsplit('port') }}" - when: ansible_selinux.status == "enabled" + when: ansible_facts.selinux.status == "enabled" tags: - pve_exporter_configure diff --git a/roles/pve_exporter/templates/pve_exporter.service.j2 b/roles/pve_exporter/templates/pve_exporter.service.j2 index b7c950b17..d3125fc5b 100644 --- a/roles/pve_exporter/templates/pve_exporter.service.j2 +++ b/roles/pve_exporter/templates/pve_exporter.service.j2 @@ -35,7 +35,7 @@ RestartSec=1 StartLimitInterval=0 {% set ns = namespace(protect_home = 'yes') %} -{% for m in ansible_mounts if m.mount.startswith('/home') %} +{% for m in ansible_facts.mounts if m.mount.startswith('/home') %} {% set ns.protect_home = 'read-only' %} {% endfor %} {% if pve_exporter_textfile_dir.startswith('/home') %} @@ -91,8 +91,8 @@ CapabilityBoundingSet=~CAP_SYS_BOOT CapabilityBoundingSet=~CAP_SYS_RAWIO CapabilityBoundingSet=~CAP_SYS_PTRACE -{% if not (ansible_virtualization_type is defined and - (ansible_virtualization_type == "lxc" or ansible_virtualization_type == "docker") +{% if not (ansible_facts.virtualization_type is defined and + (ansible_facts.virtualization_type == "lxc" or ansible_facts.virtualization_type == "docker") ) %} SystemCallFilter=@system-service @privileged @resources From 21792041a8b1041604721493601c5f4f46f20f80 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 7 Mar 2026 20:24:06 +0000 Subject: [PATCH 25/28] fix(redhat): add CRB repo, broken conditionals --- roles/pve_exporter/tasks/redhat-epel.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/roles/pve_exporter/tasks/redhat-epel.yml b/roles/pve_exporter/tasks/redhat-epel.yml index bc314e8ae..77cae90d8 100644 --- a/roles/pve_exporter/tasks/redhat-epel.yml +++ b/roles/pve_exporter/tasks/redhat-epel.yml @@ -7,6 +7,8 @@ ansible.builtin.package: name: "{{ redhat_epel_pkgs | default([]) }}" state: present + register: pkg_result + until: pkg_result is success - name: Check if epel key already retrieved ansible.builtin.stat: @@ -21,13 +23,16 @@ validate_certs: false register: dl_result until: dl_result is success - when: not epelkey.stat.exists and epel_rpm_key + when: + - not epelkey.stat.exists + - epel_rpm_key | length > 0 - name: Installing epel rpm key # rpm_key: key={{ epel_rpm_key }} ansible.builtin.rpm_key: key: "/var/tmp/{{ epel_rpm_key | basename }}" state: present - when: epel_rpm_key | string + when: + - epel_rpm_key | length > 0 register: pkg_result until: pkg_result is success become: true @@ -54,3 +59,13 @@ become: true when: - ansible_facts['distribution_major_version'] | int >= 9 + +- name: RedHat9+ | Enable CRB repo + community.general.ini_file: + path: "{{ redhatepel_crb_file }}" + section: crb + option: enabled + value: '1' + mode: '0644' + when: + - ansible_facts['distribution_major_version'] | int >= 9 From 650f81577c8c61362a2092bb170e0c67e0c89d72 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 7 Mar 2026 21:10:11 +0000 Subject: [PATCH 26/28] fix: redhatepel_crb_file vars --- roles/pve_exporter/vars/RedHat-10.yml | 2 ++ roles/pve_exporter/vars/RedHat-9.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/roles/pve_exporter/vars/RedHat-10.yml b/roles/pve_exporter/vars/RedHat-10.yml index a8d934870..c9b016337 100644 --- a/roles/pve_exporter/vars/RedHat-10.yml +++ b/roles/pve_exporter/vars/RedHat-10.yml @@ -7,6 +7,8 @@ redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ a redhat_epel_pkgs: - gnupg2 +redhatepel_crb_file: "/etc/yum.repos.d/{{ ansible_facts['distribution'] | lower }}-crb.repo" + _pve_exporter_pkgs: - "python{{ ansible_facts['python']['version']['major'] }}-setuptools" - "python{{ ansible_facts['python']['version']['major'] }}-virtualenv" diff --git a/roles/pve_exporter/vars/RedHat-9.yml b/roles/pve_exporter/vars/RedHat-9.yml index 908b72425..2e5d660f2 100644 --- a/roles/pve_exporter/vars/RedHat-9.yml +++ b/roles/pve_exporter/vars/RedHat-9.yml @@ -5,6 +5,8 @@ epel_rpm_key_hash: fcf0eab4f05a1c0de6363ac4b707600a27a9d774e9b491059e59e6921b255 redhat_epel_rpm: "https://dl.fedoraproject.org/pub/epel/epel-release-latest-{{ ansible_facts['distribution_major_version'] }}.noarch.rpm" +redhatepel_crb_file: "/etc/yum.repos.d/{{ ansible_facts['distribution'] | lower }}.repo" + _pve_exporter_pkgs: - "python{{ ansible_facts['python']['version']['major'] }}-setuptools" - "python{{ ansible_facts['python']['version']['major'] }}-virtualenv" From 7253c29955762e9e530f4e44f21aa66a1b67fe04 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sat, 7 Mar 2026 23:26:14 +0000 Subject: [PATCH 27/28] fix(almalinux-9): redhatepel_crb_file vars --- roles/pve_exporter/tasks/redhat-epel.yml | 5 +++++ roles/pve_exporter/vars/AlmaLinux-9.yml | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 roles/pve_exporter/vars/AlmaLinux-9.yml diff --git a/roles/pve_exporter/tasks/redhat-epel.yml b/roles/pve_exporter/tasks/redhat-epel.yml index 77cae90d8..8afd1422d 100644 --- a/roles/pve_exporter/tasks/redhat-epel.yml +++ b/roles/pve_exporter/tasks/redhat-epel.yml @@ -2,6 +2,11 @@ - name: Include version-specific variables for RedHat ansible.builtin.include_vars: "RedHat-{{ ansible_facts['distribution_major_version'] }}.yml" +- name: Include AlmaLinux-9 specific variables + ansible.builtin.include_vars: "AlmaLinux-9.yml" + when: + - ansible_facts['distribution'] == 'AlmaLinux' + - ansible_facts['distribution_major_version'] | int == 9 - name: RedHat | Install dependencies packages ansible.builtin.package: diff --git a/roles/pve_exporter/vars/AlmaLinux-9.yml b/roles/pve_exporter/vars/AlmaLinux-9.yml new file mode 100644 index 000000000..9bcc5e20f --- /dev/null +++ b/roles/pve_exporter/vars/AlmaLinux-9.yml @@ -0,0 +1,3 @@ +--- + +redhatepel_crb_file: "/etc/yum.repos.d/almalinux-crb.repo" From 07d07f40f0c8192664b1fdc7c192831346318fb7 Mon Sep 17 00:00:00 2001 From: juju4 Date: Sun, 8 Mar 2026 00:10:02 +0000 Subject: [PATCH 28/28] fix: pip install without become - user shell issue? --- roles/pve_exporter/tasks/install.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/roles/pve_exporter/tasks/install.yml b/roles/pve_exporter/tasks/install.yml index b56511d58..e6ae396ec 100644 --- a/roles/pve_exporter/tasks/install.yml +++ b/roles/pve_exporter/tasks/install.yml @@ -35,8 +35,6 @@ state: present virtualenv: "{{ pve_exporter_virtualenv }}" virtualenv_command: "{% if ansible_facts['os_family'] != 'RedHat' %}{{ ansible_facts['discovered_interpreter_python'] }} -m venv{% endif %}" - become: true - become_user: "{{ pve_exporter_system_user }}" - name: Ensure config dir exists ansible.builtin.file: path: "{{ pve_exporter_config_dir }}"