--- - name: Checkmk | Validate Windows plugin execution settings ansible.builtin.assert: that: - checkmk_extra_plugin_patterns is sequence - checkmk_extra_plugin_patterns is not string - checkmk_windows_updates_timeout | int >= 0 - checkmk_windows_updates_cache | int >= 0 - checkmk_mk_inventory_timeout | int >= 0 - checkmk_plugins_default_timeout | int >= 0 - checkmk_plugins_default_cache | int >= 0 fail_msg: Invalid Checkmk plugin execution setting type or negative timeout. quiet: true when: ansible_facts.os_family == 'Windows' - name: Checkmk | Validate custom plugin rule fields ansible.builtin.assert: that: - item is mapping - item.pattern is defined - item.pattern is string - item.run is not defined or item.run is boolean - item['async'] is not defined or item['async'] is boolean - item.timeout is not defined or item.timeout | int >= 0 - item.cache_age is not defined or item.cache_age | int >= 0 - item.keys() | difference(['pattern','run','async','timeout','cache_age','retry_count']) | length == 0 fail_msg: Custom plugin rules require pattern and supported execution fields. quiet: true loop: '{{ checkmk_extra_plugin_patterns }}' loop_control: label: custom plugin execution rule no_log: true when: ansible_facts.os_family == 'Windows' - name: Windows | Render AIM-managed Checkmk plugins section ansible.windows.win_template: src: windows_plugins_section.yml.j2 dest: '{{ checkmk_windows_user_cfg }}.aim-plugins.tmp' when: ansible_facts.os_family == 'Windows' diff: false changed_when: false - name: Windows | Replace only Checkmk plugins section ansible.windows.win_shell: | $ErrorActionPreference = 'Stop' $configPath = '{{ checkmk_windows_user_cfg }}' $fragmentPath = '{{ checkmk_windows_user_cfg }}.aim-plugins.tmp' $header = '# Managed by Ansible (checkmk_configure_agent). Only AIM-marked sections are modified; all other content is preserved.' $legacyHeader = '# Managed by Ansible (checkmk_configure_agent).' $managedMarkerPrefix = '# AIM managed section: plugins (checkmk_configure_agent).' if (-not (Test-Path -LiteralPath $fragmentPath)) { throw "AIM Checkmk plugins fragment is missing: $fragmentPath" } $fragment = [System.IO.File]::ReadAllText($fragmentPath) $fragment = $fragment.TrimEnd([char[]]"`r`n") if (Test-Path -LiteralPath $configPath) { $original = [System.IO.File]::ReadAllText($configPath) } else { $original = '' } if ($original.Contains("`r`n")) { $newline = "`r`n" } else { $newline = "`n" } $hadFinalNewline = $original.EndsWith("`r`n") -or $original.EndsWith("`n") -or $original.EndsWith("`r") $lines = @() if ($original.Length -gt 0) { $lines = @([regex]::Split($original, "`r`n|`n|`r")) if ($hadFinalNewline -and $lines.Count -gt 0 -and $lines[$lines.Count - 1] -eq '') { if ($lines.Count -eq 1) { $lines = @() } else { $lines = @($lines[0..($lines.Count - 2)]) } } } # Keep the AIM ownership notice as line 1 without discarding the previous first line. if ($lines.Count -eq 0) { $lines = @($header) } elseif ($lines[0] -eq $legacyHeader -or $lines[0].StartsWith('# Managed by Ansible (checkmk_configure_agent).')) { $lines[0] = $header } else { $lines = @($header) + $lines } $pluginIndex = -1 for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^plugins\s*:\s*(?:#.*)?$') { $pluginIndex = $i break } } $fragmentLines = @([regex]::Split($fragment, "`r`n|`n|`r")) if ($pluginIndex -ge 0) { $replaceStart = $pluginIndex if ($pluginIndex -gt 0 -and $lines[$pluginIndex - 1].StartsWith($managedMarkerPrefix)) { $replaceStart = $pluginIndex - 1 } $nextTopLevelKey = $lines.Count for ($i = $pluginIndex + 1; $i -lt $lines.Count; $i++) { if ($lines[$i] -match '^[A-Za-z_][A-Za-z0-9_.-]*\s*:') { $nextTopLevelKey = $i break } } # Preserve blank lines and top-level comments immediately before the next untouched section. $replaceEnd = $nextTopLevelKey while ($replaceEnd -gt ($pluginIndex + 1)) { $candidate = $lines[$replaceEnd - 1] if ([string]::IsNullOrWhiteSpace($candidate) -or $candidate.StartsWith('#')) { $replaceEnd-- } else { break } } $before = @() if ($replaceStart -gt 0) { $before = @($lines[0..($replaceStart - 1)]) } $after = @() if ($replaceEnd -lt $lines.Count) { $after = @($lines[$replaceEnd..($lines.Count - 1)]) } $lines = @($before + $fragmentLines + $after) } else { if ($lines.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($lines[$lines.Count - 1])) { $lines += '' } $lines += $fragmentLines } $updated = [string]::Join($newline, $lines) if ($hadFinalNewline -or $original.Length -eq 0) { $updated += $newline } if ($updated -ne $original) { $utf8NoBom = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText($configPath, $updated, $utf8NoBom) Write-Output 'AIM_CHANGED=true' } else { Write-Output 'AIM_CHANGED=false' } register: _checkmk_plugins_update changed_when: "'AIM_CHANGED=true' in _checkmk_plugins_update.stdout" when: ansible_facts.os_family == 'Windows' notify: checkmk | windows | configuration-changed diff: false - name: Windows | Normalize ACL on Checkmk user configuration ansible.builtin.include_role: name: checkmk_windows_acl vars: checkmk_windows_acl_paths: - '{{ checkmk_windows_user_cfg }}' when: ansible_facts.os_family == 'Windows' - name: Windows | Remove temporary Checkmk plugins fragment ansible.windows.win_file: path: '{{ checkmk_windows_user_cfg }}.aim-plugins.tmp' state: absent when: ansible_facts.os_family == 'Windows' changed_when: false - name: Checkmk | Configuration summary ansible.builtin.debug: msg: destination: '{{ checkmk_windows_user_cfg }}' managed_section: plugins extra_plugin_rules: '{{ checkmk_extra_plugin_patterns | length }}' other_sections: preserved when: - ansible_facts.os_family == 'Windows' - aim_debug | default(false) | bool