diff options
| -rw-r--r-- | README.md | 12 | ||||
| -rw-r--r-- | defaults/main.yml | 3 | ||||
| -rw-r--r-- | meta/argument_specs.yml | 14 | ||||
| -rw-r--r-- | molecule/default/converge.yml | 48 | ||||
| -rw-r--r-- | molecule/default/molecule.yml | 7 | ||||
| -rw-r--r-- | molecule/default/tests/test_lineinfile_strategy.py | 7 | ||||
| -rw-r--r-- | molecule/default/tests/test_yaml_strategy.py | 6 | ||||
| -rw-r--r-- | tasks/process_exporter.yml | 7 | ||||
| -rw-r--r-- | tasks/strategy_lineinfile.yml | 14 | ||||
| -rw-r--r-- | tasks/strategy_yaml.yml | 37 |
10 files changed, 142 insertions, 13 deletions
@@ -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 |