aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorColin Wilk <colin@wilk.cx>2026-08-11 00:54:41 +0200
committerColin Wilk <colin@wilk.cx>2026-08-11 00:54:41 +0200
commitc267536ecfbcf37e68226ade2ed29c3b1f356a98 (patch)
tree0fb9728b0095dd34b873fa6c137b75c60734940d
parentc33948c219032977779a7ef6d82c16e84f37b81a (diff)
downloadansible-role-prometheus-target-c267536ecfbcf37e68226ade2ed29c3b1f356a98.tar.gz
ansible-role-prometheus-target-c267536ecfbcf37e68226ade2ed29c3b1f356a98.zip
Add `state` option for removing exporters
-rw-r--r--README.md12
-rw-r--r--defaults/main.yml3
-rw-r--r--meta/argument_specs.yml14
-rw-r--r--molecule/default/converge.yml48
-rw-r--r--molecule/default/molecule.yml7
-rw-r--r--molecule/default/tests/test_lineinfile_strategy.py7
-rw-r--r--molecule/default/tests/test_yaml_strategy.py6
-rw-r--r--tasks/process_exporter.yml7
-rw-r--r--tasks/strategy_lineinfile.yml14
-rw-r--r--tasks/strategy_yaml.yml37
10 files changed, 142 insertions, 13 deletions
diff --git a/README.md b/README.md
index 32d85c8..5e5ce86 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ prometheus_target_exporter_defaults: {}
# node_exporter:
# path: /opt/prometheus/targets.yml
# host: '{{ inventory_hostname }}:9100'
+ # state: present
# labels: # Labels to match when using yaml strategy
# severity: warning
# job: external
@@ -65,6 +66,17 @@ prometheus_target_exporter_defaults: {}
prometheus_target_exporter: []
```
+Each exporter accepts `state: present` or `state: absent`, defaulting to
+`present`. Set `state: absent` to remove the rendered target from its target
+file. The setting can be defined in `prometheus_target_exporter_defaults` and
+overridden by an entry in `prometheus_target_exporter`.
+
+With the `yaml` strategy, removal searches every group in the configured file,
+regardless of the exporter's labels, and removes groups left without targets.
+With the `lineinfile` strategy, removal deletes the exact line produced by the
+configured prefix, host, and suffix. Removing a target from a missing file is a
+no-op for both strategies.
+
You can also add exporter that you want to have deployed without needing to
specify them in the `prometheus_target_exporter` variable by adding them to the
`prometheus_target_default_exporters` variable.
diff --git a/defaults/main.yml b/defaults/main.yml
index 2b62d46..f253626 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -129,12 +129,14 @@ prometheus_target_exporter_defaults: {}
# labels: # Labels to match when using yaml strategy
# severity: warning
# job: external
+ # state: present # Set to absent to remove this target
# blackbox_exporter:
# path: /opt/targets/blackbox.yml
# host: "https://{{ hostvars[inventory_hostname].ansible_host }}"
# path_prefix: ""
# labels:
# severity: critical
+ # state: present
# This is where you specify the exporters that should be deployed to prometheus.
# You should configure this on a per play basis. If you wish to configure
@@ -150,6 +152,7 @@ prometheus_target_exporter: []
# labels:
# severity: warning
# job: external
+ # state: present # Set to absent to remove this target
# This is a list of exporters that will be appended to the
# prometheus_target_exporter variable. duplicate exporters in
diff --git a/meta/argument_specs.yml b/meta/argument_specs.yml
index b35c993..f0477a9 100644
--- a/meta/argument_specs.yml
+++ b/meta/argument_specs.yml
@@ -121,6 +121,13 @@ argument_specs:
type: dict
required: false
description: Labels to match the target group in the YAML file (yaml strategy only)
+ state:
+ type: str
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
prometheus_target_default_exporters:
type: list
@@ -144,6 +151,13 @@ argument_specs:
type: dict
required: false
description: Labels to match the target group in the YAML file (yaml strategy only)
+ state:
+ type: str
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
prometheus_target_skip_default_exporters:
type: bool
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
index 59d63bc..8e0b5c9 100644
--- a/molecule/default/converge.yml
+++ b/molecule/default/converge.yml
@@ -743,3 +743,51 @@
prometheus_target_exporter:
- path: /opt/handler_disabled.yml
host: application
+
+################################################################################
+################################################################################
+- name: Remove lineinfile exporters
+ hosts: application_remove
+ vars:
+ prometheus_target_host: prometheus
+ prometheus_target_strategy_lineinfile_prefix: " - "
+ prometheus_target_exporter_defaults:
+ remove_shared:
+ path: /opt/lineinfile_state_removal.yml
+ host: remove:9100
+ state: absent
+
+ roles:
+ - role: kliwniloc.prometheus_target
+ prometheus_target_exporter:
+ - id: remove_shared
+ - path: /opt/lineinfile_state_removal.yml
+ host: remove:9200
+ state: absent
+ - path: /opt/lineinfile_state_missing.yml
+ host: missing:9100
+ state: absent
+
+################################################################################
+################################################################################
+- name: Remove YAML exporters
+ hosts: application_remove
+ vars:
+ prometheus_target_host: prometheus
+ prometheus_target_strategy: yaml
+ prometheus_target_exporter_defaults:
+ remove_shared:
+ path: /opt/yaml_state_removal.yml
+ host: remove:9100
+ state: absent
+
+ roles:
+ - role: kliwniloc.prometheus_target
+ prometheus_target_exporter:
+ - id: remove_shared
+ - path: /opt/yaml_state_removal.yml
+ host: remove:9200
+ state: absent
+ - path: /opt/yaml_state_missing.yml
+ host: missing:9100
+ state: absent
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
index 04d4391..bdc5df7 100644
--- a/molecule/default/molecule.yml
+++ b/molecule/default/molecule.yml
@@ -43,6 +43,13 @@ platforms:
- name: molecule-container-net
groups: [application_group]
+ - name: application_remove
+ image: ${MOLECULE_DISTRO:-debian:12}
+ dockerfile: Dockerfile.j2
+ pre_build_image: false
+ networks:
+ - name: molecule-container-net
+
- name: prometheus
image: ${MOLECULE_DISTRO:-debian:12}
dockerfile: Dockerfile.j2
diff --git a/molecule/default/tests/test_lineinfile_strategy.py b/molecule/default/tests/test_lineinfile_strategy.py
index 770459e..d7f9738 100644
--- a/molecule/default/tests/test_lineinfile_strategy.py
+++ b/molecule/default/tests/test_lineinfile_strategy.py
@@ -122,11 +122,12 @@ def test_disabled_handlers_do_not_run(host):
assert not host.file("/tmp/disabled-shell-handler").exists
-def test_future_lineinfile_removal_fixture_is_untouched(host):
+def test_lineinfile_absent_removes_exporters(host):
assert read_yaml_file(host, "/opt/lineinfile_state_removal.yml") == [
{
"labels": {"job": "shared"},
- "targets": ["keep:9100", "remove:9100"],
+ "targets": ["keep:9100"],
},
- {"labels": {"job": "remove_last"}, "targets": ["remove:9200"]},
+ {"labels": {"job": "remove_last"}, "targets": None},
]
+ assert not host.file("/opt/lineinfile_state_missing.yml").exists
diff --git a/molecule/default/tests/test_yaml_strategy.py b/molecule/default/tests/test_yaml_strategy.py
index c3d0dd9..7f9b6ed 100644
--- a/molecule/default/tests/test_yaml_strategy.py
+++ b/molecule/default/tests/test_yaml_strategy.py
@@ -197,11 +197,11 @@ def test_yaml_skip_default_exporters(host):
assert target.content_string == ""
-def test_future_yaml_removal_fixture_is_untouched(host):
+def test_yaml_absent_removes_exporters_and_empty_groups(host):
assert read_yaml_file(host, "/opt/yaml_state_removal.yml") == [
{
"labels": {"job": "shared"},
- "targets": ["keep:9100", "remove:9100"],
+ "targets": ["keep:9100"],
},
- {"labels": {"job": "remove_last"}, "targets": ["remove:9200"]},
]
+ assert not host.file("/opt/yaml_state_missing.yml").exists
diff --git a/tasks/process_exporter.yml b/tasks/process_exporter.yml
index b4f33ba..0a19480 100644
--- a/tasks/process_exporter.yml
+++ b/tasks/process_exporter.yml
@@ -12,6 +12,13 @@
{{ (item.host if item.host is defined else _defaults.host) | mandatory }}
_target_labels: >-
{{ (_defaults.labels | default({})) | combine(item.labels | default({})) }}
+ _target_state: >-
+ {{ item.state if item.state is defined else (_defaults.state | default('present')) }}
+
+- name: Validate exporter state
+ ansible.builtin.assert:
+ that: _target_state in ["present", "absent"]
+ fail_msg: "Exporter state must be 'present' or 'absent', got '{{ _target_state }}'"
- name: Execute strategy
ansible.builtin.include_tasks:
diff --git a/tasks/strategy_lineinfile.yml b/tasks/strategy_lineinfile.yml
index 7656602..2b2d02a 100644
--- a/tasks/strategy_lineinfile.yml
+++ b/tasks/strategy_lineinfile.yml
@@ -1,13 +1,23 @@
---
+- name: Check target file for removal
+ ansible.builtin.stat:
+ path: "{{ _target_path }}"
+ delegate_to: "{{ prometheus_target_host }}"
+ become: true
+ register: _target_file
+ when: _target_state == "absent"
+
- name: Deploy target via lineinfile
ansible.builtin.lineinfile:
path: "{{ _target_path }}"
line: "{{ prometheus_target_strategy_lineinfile_prefix ~ _target_host ~ prometheus_target_strategy_lineinfile_suffix }}"
- state: present
+ state: "{{ _target_state }}"
delegate_to: "{{ prometheus_target_host }}"
become: true
register: _lineinfile_result
+ when: _target_state == "present" or _target_file.stat.exists
- name: Track changes
ansible.builtin.set_fact:
- changed: "{{ changed | default(false) or _lineinfile_result.changed }}"
+ changed: >-
+ {{ changed | default(false) or (_lineinfile_result.changed | default(false)) }}
diff --git a/tasks/strategy_yaml.yml b/tasks/strategy_yaml.yml
index 24cd6c8..45b18c3 100644
--- a/tasks/strategy_yaml.yml
+++ b/tasks/strategy_yaml.yml
@@ -14,10 +14,22 @@
((exporter.path if exporter.path is defined else defaults.path) | mandatory) -%}
{%- set host = (exporter.host if exporter.host is defined else defaults.host) | mandatory -%}
{%- set labels = (defaults.labels | default({})) | combine(exporter.labels | default({})) -%}
- {%- set _ = ns.updates.append({'path': path, 'host': host, 'labels': labels}) -%}
+ {%- set target_state = exporter.state if exporter.state is defined else
+ (defaults.state | default('present')) -%}
+ {%- set _ = ns.updates.append(
+ {'path': path, 'host': host, 'labels': labels, 'state': target_state}) -%}
{%- endfor -%}
{{ ns.updates }}
+- name: Validate YAML exporter states
+ ansible.builtin.assert:
+ that: _yaml_update.state in ["present", "absent"]
+ fail_msg: "Exporter state must be 'present' or 'absent', got '{{ _yaml_update.state }}'"
+ loop: "{{ _prometheus_target_yaml_updates }}"
+ loop_control:
+ loop_var: _yaml_update
+ label: "{{ _yaml_update.path }}"
+
# Every play host prepares its values above. Applying all updates from one host
# prevents parallel Ansible forks from overwriting each other's YAML changes.
- name: Aggregate YAML exporter updates
@@ -56,14 +68,27 @@
_prometheus_target_yaml_files: >-
{%- set output = namespace(files=[]) -%}
{%- for file in _yaml_target_files.results -%}
+ {%- set file_updates = _prometheus_target_yaml_updates_all |
+ selectattr('path', 'equalto', file._yaml_path) | list -%}
+ {%- set present_updates = file_updates |
+ selectattr('state', 'equalto', 'present') | list -%}
{%- set state = namespace(groups=(file.content | b64decode | from_yaml)
if file.content is defined else []) -%}
{%- set state.groups = state.groups if state.groups is not none else [] -%}
- {%- for update in _prometheus_target_yaml_updates_all if update.path == file._yaml_path -%}
+ {%- for update in file_updates -%}
{%- set next = namespace(found=false, groups=[]) -%}
{%- for group in state.groups -%}
{%- set group_labels = group.labels | default({}) -%}
- {%- if group_labels == update.labels -%}
+ {%- if update.state == 'absent' -%}
+ {%- set filtered = group.targets | reject('equalto', update.host) | list -%}
+ {%- if filtered | length > 0 -%}
+ {%- set replacement = {'targets': filtered} -%}
+ {%- if group_labels | length > 0 -%}
+ {%- set _ = replacement.update({'labels': group_labels}) -%}
+ {%- endif -%}
+ {%- set _ = next.groups.append(replacement) -%}
+ {%- endif -%}
+ {%- elif group_labels == update.labels -%}
{%- if update.host not in group.targets -%}
{%- set _ = group.targets.append(update.host) -%}
{%- endif -%}
@@ -80,7 +105,7 @@
{%- endif -%}
{%- endif -%}
{%- endfor -%}
- {%- if not next.found -%}
+ {%- if update.state == 'present' and not next.found -%}
{%- set new_group = {'targets': [update.host]} -%}
{%- if update.labels | length > 0 -%}
{%- set _ = new_group.update({'labels': update.labels}) -%}
@@ -89,7 +114,9 @@
{%- endif -%}
{%- set state.groups = next.groups -%}
{%- endfor -%}
- {%- set _ = output.files.append({'path': file._yaml_path, 'groups': state.groups}) -%}
+ {%- if file.content is defined or present_updates | length > 0 -%}
+ {%- set _ = output.files.append({'path': file._yaml_path, 'groups': state.groups}) -%}
+ {%- endif -%}
{%- endfor -%}
{{ output.files }}
run_once: true