aim-web2.1.0rc9
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# maintenance_patch_os
|
||||
|
||||
Operator defaults are intentionally low-precedence; inventory and explicit run options
|
||||
may override them.
|
||||
|
||||
```yaml
|
||||
---
|
||||
os_patching_reboot: true
|
||||
os_patching_windows_categories:
|
||||
- SecurityUpdates
|
||||
- CriticalUpdates
|
||||
- UpdateRollups
|
||||
- DefinitionUpdates
|
||||
- Updates
|
||||
os_patching_reboot_timeout: 600
|
||||
os_patching_reboot_delay_minutes: 0
|
||||
os_patching_reboot_message: 'AIM maintenance: operating system patching requires a reboot.'
|
||||
os_patching_rescan_after_reboot: false
|
||||
```
|
||||
|
||||
`os_patching_reboot_delay_minutes` is shared across Windows/Linux so the public setting
|
||||
has one meaning. Linux reboot scheduling is minute-granular. Windows converts the value
|
||||
to seconds; a zero-minute reboot still observes the Windows reboot module's minimum delay.
|
||||
The message is displayed by the native reboot module when AIM actually initiates a reboot.
|
||||
|
||||
When automatic reboot is disabled, newly installed updates can legitimately leave
|
||||
`reboot_deferred: true` while the patch run itself succeeds. A later run that detects the
|
||||
already-pending reboot fails before starting new patch work and tells the operator to
|
||||
reboot manually or enable the reboot option.
|
||||
|
||||
## Structured result integration
|
||||
|
||||
Current reporting behavior and field semantics are specified in
|
||||
[scripts/docs/OPERATION_RESULTS.md](../../scripts/docs/OPERATION_RESULTS.md).
|
||||
Runbook-owned filters normalize only reviewed fields; arbitrary package-manager/module
|
||||
results are not exported.
|
||||
|
||||
## Windows patch waves
|
||||
|
||||
Windows uses the native `ansible.windows.win_updates` batch/orchestration path for the
|
||||
currently selected categories. AIM calls it with `reboot: false`, so Windows Update may
|
||||
process all updates in that current wave while AIM retains control over the reviewed reboot
|
||||
message, delay and continuation policy. AIM does not create a per-update install queue.
|
||||
|
||||
The default `os_patching_rescan_after_reboot: false` stops the run after any AIM-performed
|
||||
reboot boundary; a new operator-approved run discovers the next wave. Set it to true only
|
||||
when the operator explicitly wants AIM to start another wave after reboot. A deferred reboot
|
||||
always stops the run.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
# Operator defaults. Existing os_patching_* variable names are intentionally retained.
|
||||
os_patching_reboot: true
|
||||
os_patching_windows_categories:
|
||||
- SecurityUpdates
|
||||
- CriticalUpdates
|
||||
- UpdateRollups
|
||||
- DefinitionUpdates
|
||||
- Updates
|
||||
os_patching_reboot_timeout: 600
|
||||
# Cross-platform delay before an AIM-initiated reboot. Linux reboot scheduling is
|
||||
# minute-granular, so this public setting is intentionally expressed in minutes.
|
||||
os_patching_reboot_delay_minutes: 0
|
||||
os_patching_reboot_message: 'AIM maintenance: operating system patching requires a reboot.'
|
||||
# Windows only. False preserves one operator-approved patch wave per run.
|
||||
os_patching_rescan_after_reboot: false
|
||||
@@ -0,0 +1,144 @@
|
||||
- name: Patching | Initialize Debian report state
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_action_failed: false
|
||||
_aim_patch_pre_reboot_performed: false
|
||||
_aim_patch_post_reboot_performed: false
|
||||
|
||||
- name: Patching | Detect pending Debian reboot before patching
|
||||
become: true
|
||||
ansible.builtin.stat:
|
||||
path: /var/run/reboot-required
|
||||
register: _aim_patch_pre_reboot_probe
|
||||
|
||||
- name: Patching | Record pre-existing Debian reboot state
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_preexisting_reboot_required: '{{ _aim_patch_pre_reboot_probe.stat.exists | default(false) | bool }}'
|
||||
|
||||
- name: Patching | Publish blocked Debian result when reboot is deferred
|
||||
when:
|
||||
- _aim_patch_preexisting_reboot_required | bool
|
||||
- not (os_patching_reboot | bool)
|
||||
block:
|
||||
- name: Patching | Build blocked Debian patch report
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_report: >-
|
||||
{{ 'debian' | aim_report_patch_blocked(ansible_check_mode,
|
||||
os_patching_reboot_delay_minutes | int) }}
|
||||
- name: AIM | Publish blocked operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: patch_summary_v1
|
||||
data: '{{ _aim_patch_report }}'
|
||||
- name: Patching | Require reboot before continuing Debian patching
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
A reboot is already pending from a previous update or installation. Reboot the host first,
|
||||
or rerun with "Reboot when required" enabled. No new package upgrade was started by this run.
|
||||
|
||||
- name: Patching | Clear pre-existing Debian reboot before patching
|
||||
become: true
|
||||
ansible.builtin.reboot:
|
||||
msg: '{{ os_patching_reboot_message }}'
|
||||
pre_reboot_delay: '{{ (os_patching_reboot_delay_minutes | int) * 60 }}'
|
||||
reboot_timeout: '{{ os_patching_reboot_timeout | int }}'
|
||||
register: _aim_patch_pre_reboot
|
||||
when:
|
||||
- _aim_patch_preexisting_reboot_required | bool
|
||||
- os_patching_reboot | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Patching | Record pre-patch Debian reboot
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_pre_reboot_performed: '{{ _aim_patch_pre_reboot.rebooted | default(false) | bool }}'
|
||||
|
||||
- name: Patching | Collect installed package facts before operation
|
||||
ansible.builtin.package_facts:
|
||||
manager: auto
|
||||
|
||||
- name: Patching | Snapshot installed package facts before operation
|
||||
ansible.builtin.set_fact:
|
||||
_aim_packages_before: '{{ ansible_facts.packages | default({}) }}'
|
||||
|
||||
- name: Patching | Apply native Debian updates
|
||||
block:
|
||||
- name: Update Debian-based host
|
||||
become: true
|
||||
ansible.builtin.apt:
|
||||
upgrade: safe
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
autoremove: true
|
||||
- name: Check if Debian-based host requires reboot
|
||||
become: true
|
||||
ansible.builtin.stat:
|
||||
path: /var/run/reboot-required
|
||||
register: os_patching_reboot_required
|
||||
rescue:
|
||||
- name: Patching | Retain failed action for reporting
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_action_failed: true
|
||||
|
||||
- name: Patching | Reboot Debian host after updates when required
|
||||
become: true
|
||||
ansible.builtin.reboot:
|
||||
msg: '{{ os_patching_reboot_message }}'
|
||||
pre_reboot_delay: '{{ (os_patching_reboot_delay_minutes | int) * 60 }}'
|
||||
reboot_timeout: '{{ os_patching_reboot_timeout | int }}'
|
||||
register: _aim_patch_post_reboot
|
||||
when:
|
||||
- os_patching_reboot | bool
|
||||
- not ansible_check_mode
|
||||
- os_patching_reboot_required.stat.exists | default(false) | bool
|
||||
|
||||
- name: Patching | Record post-update Debian reboot
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_post_reboot_performed: '{{ _aim_patch_post_reboot.rebooted | default(false) | bool }}'
|
||||
|
||||
- name: Patching | Collect installed package facts after operation
|
||||
ansible.builtin.package_facts:
|
||||
manager: auto
|
||||
|
||||
- name: Patching | Snapshot installed package facts after operation
|
||||
ansible.builtin.set_fact:
|
||||
_aim_packages_after: '{{ ansible_facts.packages | default({}) }}'
|
||||
|
||||
- name: Patching | Compare package database snapshots
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_report: >-
|
||||
{{ _aim_packages_before |
|
||||
aim_report_patch_linux(
|
||||
_aim_packages_after,
|
||||
'debian',
|
||||
ansible_check_mode,
|
||||
not _aim_patch_action_failed,
|
||||
os_patching_reboot_required.stat.exists | default(none),
|
||||
(_aim_patch_pre_reboot_performed | bool) or (_aim_patch_post_reboot_performed | bool),
|
||||
_aim_patch_preexisting_reboot_required | bool,
|
||||
os_patching_reboot | bool,
|
||||
os_patching_reboot_delay_minutes | int
|
||||
) }}
|
||||
|
||||
- name: Patching | Package change summary
|
||||
ansible.builtin.debug:
|
||||
msg: '{{ _aim_patch_report }}'
|
||||
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: patch_summary_v1
|
||||
data: '{{ _aim_patch_report }}'
|
||||
|
||||
- name: Patching | Preserve native operation failure
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
The native Debian patch operation failed. Available observed package changes and reboot state
|
||||
are in the structured result. If a reboot is reported as required, reboot before retrying.
|
||||
when: _aim_patch_action_failed | bool
|
||||
@@ -0,0 +1,167 @@
|
||||
- name: Patching | Initialize RedHat report state
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_action_failed: false
|
||||
_aim_patch_pre_reboot_performed: false
|
||||
_aim_patch_post_reboot_performed: false
|
||||
_aim_patch_preexisting_reboot_required: false
|
||||
|
||||
- name: Patching | Detect existing needs-restarting command
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- command -v needs-restarting
|
||||
register: _aim_needs_restarting_available
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Patching | Detect pending RedHat reboot before patching
|
||||
become: true
|
||||
ansible.builtin.command:
|
||||
cmd: needs-restarting -r
|
||||
register: _aim_patch_pre_reboot_probe
|
||||
changed_when: false
|
||||
failed_when: _aim_patch_pre_reboot_probe.rc not in [0, 1]
|
||||
when: _aim_needs_restarting_available.rc == 0
|
||||
|
||||
- name: Patching | Record pre-existing RedHat reboot state
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_preexisting_reboot_required: >-
|
||||
{{ (_aim_needs_restarting_available.rc == 0) and
|
||||
(_aim_patch_pre_reboot_probe.rc | default(0) == 1) }}
|
||||
|
||||
- name: Patching | Publish blocked RedHat result when reboot is deferred
|
||||
when:
|
||||
- _aim_patch_preexisting_reboot_required | bool
|
||||
- not (os_patching_reboot | bool)
|
||||
block:
|
||||
- name: Patching | Build blocked RedHat patch report
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_report: >-
|
||||
{{ 'redhat' | aim_report_patch_blocked(ansible_check_mode,
|
||||
os_patching_reboot_delay_minutes | int) }}
|
||||
- name: AIM | Publish blocked operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: patch_summary_v1
|
||||
data: '{{ _aim_patch_report }}'
|
||||
- name: Patching | Require reboot before continuing RedHat patching
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
A reboot is already pending from a previous update or installation. Reboot the host first,
|
||||
or rerun with "Reboot when required" enabled. No new package upgrade was started by this run.
|
||||
|
||||
- name: Patching | Clear pre-existing RedHat reboot before patching
|
||||
become: true
|
||||
ansible.builtin.reboot:
|
||||
msg: '{{ os_patching_reboot_message }}'
|
||||
pre_reboot_delay: '{{ (os_patching_reboot_delay_minutes | int) * 60 }}'
|
||||
reboot_timeout: '{{ os_patching_reboot_timeout | int }}'
|
||||
register: _aim_patch_pre_reboot
|
||||
when:
|
||||
- _aim_patch_preexisting_reboot_required | bool
|
||||
- os_patching_reboot | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Patching | Record pre-patch RedHat reboot
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_pre_reboot_performed: '{{ _aim_patch_pre_reboot.rebooted | default(false) | bool }}'
|
||||
|
||||
- name: Patching | Collect installed package facts before operation
|
||||
ansible.builtin.package_facts:
|
||||
manager: auto
|
||||
|
||||
- name: Patching | Snapshot installed package facts before operation
|
||||
ansible.builtin.set_fact:
|
||||
_aim_packages_before: '{{ ansible_facts.packages | default({}) }}'
|
||||
|
||||
- name: Patching | Apply native RedHat updates
|
||||
block:
|
||||
- name: Update RHEL-based host
|
||||
become: true
|
||||
ansible.builtin.dnf:
|
||||
name: '*'
|
||||
state: latest
|
||||
update_only: true
|
||||
- name: Ensure needs-restarting binary is present (yum-utils)
|
||||
become: true
|
||||
ansible.builtin.dnf:
|
||||
name: yum-utils
|
||||
state: present
|
||||
- name: Check if RHEL-based host requires reboot
|
||||
become: true
|
||||
ansible.builtin.command:
|
||||
cmd: needs-restarting -r
|
||||
register: os_patching_reboot_required
|
||||
changed_when: false
|
||||
failed_when: os_patching_reboot_required.rc not in [0, 1]
|
||||
rescue:
|
||||
- name: Patching | Retain failed action for reporting
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_action_failed: true
|
||||
|
||||
- name: Patching | Reboot RedHat host after updates when required
|
||||
become: true
|
||||
ansible.builtin.reboot:
|
||||
msg: '{{ os_patching_reboot_message }}'
|
||||
pre_reboot_delay: '{{ (os_patching_reboot_delay_minutes | int) * 60 }}'
|
||||
reboot_timeout: '{{ os_patching_reboot_timeout | int }}'
|
||||
register: _aim_patch_post_reboot
|
||||
when:
|
||||
- os_patching_reboot | bool
|
||||
- not ansible_check_mode
|
||||
- os_patching_reboot_required.rc | default(0) == 1
|
||||
|
||||
- name: Patching | Record post-update RedHat reboot
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_post_reboot_performed: '{{ _aim_patch_post_reboot.rebooted | default(false) | bool }}'
|
||||
|
||||
- name: Patching | Collect installed package facts after operation
|
||||
ansible.builtin.package_facts:
|
||||
manager: auto
|
||||
|
||||
- name: Patching | Snapshot installed package facts after operation
|
||||
ansible.builtin.set_fact:
|
||||
_aim_packages_after: '{{ ansible_facts.packages | default({}) }}'
|
||||
|
||||
- name: Patching | Compare package database snapshots
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_report: >-
|
||||
{{ _aim_packages_before |
|
||||
aim_report_patch_linux(
|
||||
_aim_packages_after,
|
||||
'redhat',
|
||||
ansible_check_mode,
|
||||
not _aim_patch_action_failed,
|
||||
(os_patching_reboot_required.rc == 1) if os_patching_reboot_required.rc is defined else none,
|
||||
(_aim_patch_pre_reboot_performed | bool) or (_aim_patch_post_reboot_performed | bool),
|
||||
_aim_patch_preexisting_reboot_required | bool,
|
||||
os_patching_reboot | bool,
|
||||
os_patching_reboot_delay_minutes | int
|
||||
) }}
|
||||
|
||||
- name: Patching | Package change summary
|
||||
ansible.builtin.debug:
|
||||
msg: '{{ _aim_patch_report }}'
|
||||
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: patch_summary_v1
|
||||
data: '{{ _aim_patch_report }}'
|
||||
|
||||
- name: Patching | Preserve native operation failure
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
The native RedHat patch operation failed. Available observed package changes and reboot state
|
||||
are in the structured result. If a reboot is reported as required, reboot before retrying.
|
||||
when: _aim_patch_action_failed | bool
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
|
||||
- name: Patching | Supported platform
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts.os_family in ['Windows', 'Debian', 'RedHat']
|
||||
fail_msg: 'Supported patching families: Windows, Debian, RedHat. No legacy Python bootstrap is performed.'
|
||||
quiet: true
|
||||
- name: Patching | Validate options
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (os_patching_reboot) is boolean or (os_patching_reboot | string | lower) in ['true', 'false']
|
||||
- (os_patching_rescan_after_reboot) is boolean or (os_patching_rescan_after_reboot | string | lower) in ['true', 'false']
|
||||
- os_patching_reboot_timeout | int > 0
|
||||
- os_patching_reboot_delay_minutes | int >= 0
|
||||
- os_patching_reboot_delay_minutes | int <= 1440
|
||||
- os_patching_reboot_message is string
|
||||
- os_patching_reboot_message | length > 0
|
||||
- os_patching_reboot_message | length <= 512
|
||||
- os_patching_windows_categories is sequence
|
||||
- os_patching_windows_categories is not string
|
||||
- os_patching_windows_categories | length > 0
|
||||
fail_msg: Invalid patching settings. Reboot/rescan flags must be boolean, reboot delay must be 0-1440 minutes and the reboot message must be 1-512 characters.
|
||||
quiet: true
|
||||
- name: Patching | Windows
|
||||
ansible.builtin.include_tasks: windows.yml
|
||||
when: ansible_facts.os_family == 'Windows'
|
||||
- name: Patching | Debian
|
||||
ansible.builtin.include_tasks: linux_debian.yml
|
||||
when: ansible_facts.os_family == 'Debian'
|
||||
- name: Patching | RedHat
|
||||
ansible.builtin.include_tasks: linux_redhat.yml
|
||||
when: ansible_facts.os_family == 'RedHat'
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
- name: Patching | Initialize Windows report state
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_action_failed: false
|
||||
_aim_patch_pre_reboot_performed: false
|
||||
_aim_patch_any_reboot_performed: false
|
||||
_aim_patch_preexisting_reboot_required: false
|
||||
_aim_patch_preexisting_reboot_reasons: []
|
||||
_aim_patch_reboot_deferred: false
|
||||
_aim_patch_reboot_required_after: false
|
||||
_aim_patch_blocked_reason: null
|
||||
_aim_patch_continuation_required: false
|
||||
_aim_patch_remaining_updates_known: false
|
||||
_aim_patch_done: false
|
||||
_aim_patch_cycles: 0
|
||||
_aim_windows_update_runs: []
|
||||
_aim_windows_searches: []
|
||||
|
||||
- name: Patching | Detect pending Windows reboot before patching
|
||||
ansible.windows.win_reboot_info:
|
||||
register: _aim_patch_pre_reboot_probe
|
||||
|
||||
- name: Patching | Record pre-existing Windows reboot state
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_preexisting_reboot_required: '{{ _aim_patch_pre_reboot_probe.reboot_required | default(false) | bool }}'
|
||||
_aim_patch_preexisting_reboot_reasons: '{{ _aim_patch_pre_reboot_probe.reboot_required_reasons | default([]) }}'
|
||||
|
||||
- name: Patching | Publish blocked Windows result when reboot is deferred
|
||||
when:
|
||||
- _aim_patch_preexisting_reboot_required | bool
|
||||
- not (os_patching_reboot | bool)
|
||||
block:
|
||||
- name: Patching | Build blocked Windows patch report
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_report: >-
|
||||
{{ 'windows' | aim_report_patch_blocked(ansible_check_mode,
|
||||
os_patching_reboot_delay_minutes | int,
|
||||
os_patching_rescan_after_reboot | bool,
|
||||
_aim_patch_preexisting_reboot_reasons) }}
|
||||
- name: AIM | Publish blocked operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: patch_summary_v1
|
||||
data: '{{ _aim_patch_report }}'
|
||||
- name: Patching | Require reboot before continuing Windows patching
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
A reboot is already pending from a previous update or installation. Reboot the host first,
|
||||
or rerun with "Reboot when required" enabled. No new Windows updates were started by this run.
|
||||
|
||||
- name: Patching | Clear pre-existing Windows reboot before patching
|
||||
ansible.windows.win_reboot:
|
||||
msg: '{{ os_patching_reboot_message }}'
|
||||
pre_reboot_delay: '{{ [2, (os_patching_reboot_delay_minutes | int) * 60] | max }}'
|
||||
reboot_timeout: '{{ os_patching_reboot_timeout | int }}'
|
||||
register: _aim_patch_pre_reboot
|
||||
when:
|
||||
- _aim_patch_preexisting_reboot_required | bool
|
||||
- os_patching_reboot | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Patching | Record pre-patch Windows reboot
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_pre_reboot_performed: '{{ _aim_patch_pre_reboot.rebooted | default(false) | bool }}'
|
||||
_aim_patch_any_reboot_performed: '{{ _aim_patch_pre_reboot.rebooted | default(false) | bool }}'
|
||||
_aim_patch_done: >-
|
||||
{{ (_aim_patch_pre_reboot.rebooted | default(false) | bool) and
|
||||
not (os_patching_rescan_after_reboot | bool) }}
|
||||
_aim_patch_continuation_required: >-
|
||||
{{ (_aim_patch_pre_reboot.rebooted | default(false) | bool) and
|
||||
not (os_patching_rescan_after_reboot | bool) }}
|
||||
_aim_patch_remaining_updates_known: false
|
||||
|
||||
- name: Patching | Search Windows updates in check mode
|
||||
ansible.windows.win_updates:
|
||||
category_names: '{{ os_patching_windows_categories }}'
|
||||
state: searched
|
||||
reboot: false
|
||||
register: _aim_windows_check_search
|
||||
when:
|
||||
- ansible_check_mode
|
||||
- not (_aim_patch_done | bool)
|
||||
|
||||
- name: Patching | Record Windows check-mode search
|
||||
ansible.builtin.set_fact:
|
||||
_aim_windows_searches: '{{ [_aim_windows_check_search] }}'
|
||||
_aim_patch_remaining_updates_known: true
|
||||
_aim_patch_continuation_required: '{{ (_aim_windows_check_search.found_update_count | default(0) | int) > 0 }}'
|
||||
_aim_patch_done: true
|
||||
when:
|
||||
- ansible_check_mode
|
||||
- _aim_windows_check_search is defined
|
||||
- not (_aim_windows_check_search.skipped | default(false) | bool)
|
||||
|
||||
- name: Patching | Process Windows patch waves
|
||||
ansible.builtin.include_tasks: windows_wave.yml
|
||||
loop: >-
|
||||
{{ (range(1, 13) | list) if (os_patching_rescan_after_reboot | bool) else [1] }}
|
||||
loop_control:
|
||||
loop_var: _aim_patch_wave_number
|
||||
label: 'Windows patch wave {{ _aim_patch_wave_number }}'
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- not (_aim_patch_done | bool)
|
||||
|
||||
- name: Patching | Guard automatic continuation wave limit
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_action_failed: true
|
||||
_aim_patch_blocked_reason: cycle_limit_reached
|
||||
_aim_patch_continuation_required: true
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- os_patching_rescan_after_reboot | bool
|
||||
- not (_aim_patch_done | bool)
|
||||
|
||||
- name: Patching | Normalize Windows update results
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_report: >-
|
||||
{{ _aim_windows_update_runs |
|
||||
aim_report_patch_windows_runs(
|
||||
_aim_windows_searches,
|
||||
ansible_check_mode,
|
||||
_aim_patch_preexisting_reboot_required | bool,
|
||||
_aim_patch_any_reboot_performed | bool,
|
||||
os_patching_reboot | bool,
|
||||
os_patching_reboot_delay_minutes | int,
|
||||
os_patching_rescan_after_reboot | bool,
|
||||
_aim_patch_cycles | int,
|
||||
_aim_patch_continuation_required | bool,
|
||||
_aim_patch_remaining_updates_known | bool,
|
||||
_aim_patch_reboot_deferred | bool,
|
||||
_aim_patch_reboot_required_after,
|
||||
_aim_patch_blocked_reason,
|
||||
not (_aim_patch_action_failed | bool),
|
||||
_aim_patch_preexisting_reboot_reasons
|
||||
) }}
|
||||
|
||||
- name: Patching | Update summary
|
||||
ansible.builtin.debug:
|
||||
msg: '{{ _aim_patch_report }}'
|
||||
|
||||
- name: AIM | Publish operation result
|
||||
ansible.builtin.set_stats:
|
||||
per_host: true
|
||||
aggregate: false
|
||||
data:
|
||||
aim_output:
|
||||
protocol: aim_output_v1
|
||||
schema: patch_summary_v1
|
||||
data: '{{ _aim_patch_report }}'
|
||||
|
||||
- name: Patching | Preserve Windows update failure
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Windows patching stopped before the approved patch wave completed. The structured result contains
|
||||
successfully installed updates, bounded failed-update reasons when available, and whether another
|
||||
operator-approved run is required. AIM did not replay the patch job automatically.
|
||||
when: _aim_patch_action_failed | bool
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
- name: Patching | Start Windows patch cycle
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_cycles: '{{ _aim_patch_cycle_number | int }}'
|
||||
_aim_patch_cycle_stop: false
|
||||
_aim_patch_cycle_reboot_performed: false
|
||||
|
||||
- name: Patching | Search available Windows updates for this wave
|
||||
ansible.windows.win_updates:
|
||||
category_names: '{{ os_patching_windows_categories }}'
|
||||
state: searched
|
||||
reboot: false
|
||||
register: _aim_windows_cycle_search
|
||||
|
||||
- name: Patching | Record Windows update discovery
|
||||
ansible.builtin.set_fact:
|
||||
_aim_windows_searches: '{{ _aim_windows_searches + [_aim_windows_cycle_search] }}'
|
||||
_aim_windows_update_queue: '{{ _aim_windows_cycle_search | aim_windows_update_queue }}'
|
||||
|
||||
- name: Patching | Reboot when discovery itself reports a required reboot
|
||||
ansible.windows.win_reboot:
|
||||
msg: '{{ os_patching_reboot_message }}'
|
||||
pre_reboot_delay: '{{ [2, (os_patching_reboot_delay_minutes | int) * 60] | max }}'
|
||||
reboot_timeout: '{{ os_patching_reboot_timeout | int }}'
|
||||
register: _aim_windows_search_reboot
|
||||
when:
|
||||
- _aim_windows_cycle_search.reboot_required | default(false) | bool
|
||||
- os_patching_reboot | bool
|
||||
|
||||
- name: Patching | Record discovery-time reboot boundary
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_cycle_stop: true
|
||||
_aim_patch_cycle_reboot_performed: '{{ _aim_windows_search_reboot.rebooted | default(false) | bool }}'
|
||||
_aim_patch_any_reboot_performed: >-
|
||||
{{ (_aim_patch_any_reboot_performed | bool) or
|
||||
(_aim_windows_search_reboot.rebooted | default(false) | bool) }}
|
||||
_aim_patch_reboot_required_after: >-
|
||||
{{ false if (_aim_windows_search_reboot.rebooted | default(false) | bool) else true }}
|
||||
when:
|
||||
- _aim_windows_search_reboot is defined
|
||||
- not (_aim_windows_search_reboot.skipped | default(false) | bool)
|
||||
|
||||
- name: Patching | Defer discovery-time required reboot
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_cycle_stop: true
|
||||
_aim_patch_done: true
|
||||
_aim_patch_reboot_deferred: true
|
||||
_aim_patch_reboot_required_after: true
|
||||
_aim_patch_continuation_required: true
|
||||
_aim_patch_remaining_updates_known: true
|
||||
when:
|
||||
- _aim_windows_cycle_search.reboot_required | default(false) | bool
|
||||
- not (os_patching_reboot | bool)
|
||||
|
||||
- name: Patching | Finish when no updates are available
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_done: true
|
||||
_aim_patch_remaining_updates_known: true
|
||||
_aim_patch_continuation_required: '{{ _aim_windows_cycle_search.reboot_required | default(false) | bool }}'
|
||||
_aim_patch_reboot_required_after: '{{ _aim_windows_cycle_search.reboot_required | default(false) | bool }}'
|
||||
when:
|
||||
- (_aim_windows_update_queue | length) == 0
|
||||
- not (_aim_patch_cycle_stop | bool)
|
||||
|
||||
- name: Patching | Install discovered Windows updates sequentially
|
||||
ansible.builtin.include_tasks: windows_update_one.yml
|
||||
loop: '{{ _aim_windows_update_queue }}'
|
||||
loop_control:
|
||||
loop_var: _aim_windows_update
|
||||
label: '{{ _aim_windows_update.title }}'
|
||||
when:
|
||||
- not (_aim_patch_done | bool)
|
||||
- not (_aim_patch_cycle_stop | bool)
|
||||
|
||||
- name: Patching | Final read-only discovery after completed non-reboot wave
|
||||
ansible.windows.win_updates:
|
||||
category_names: '{{ os_patching_windows_categories }}'
|
||||
state: searched
|
||||
reboot: false
|
||||
register: _aim_windows_cycle_final_search
|
||||
when:
|
||||
- not (_aim_patch_done | bool)
|
||||
- not (_aim_patch_cycle_stop | bool)
|
||||
- not (_aim_patch_action_failed | bool)
|
||||
|
||||
- name: Patching | Record final non-reboot wave state
|
||||
ansible.builtin.set_fact:
|
||||
_aim_windows_searches: '{{ _aim_windows_searches + [_aim_windows_cycle_final_search] }}'
|
||||
_aim_patch_remaining_updates_known: true
|
||||
_aim_patch_continuation_required: '{{ (_aim_windows_cycle_final_search.found_update_count | default(0) | int) > 0 }}'
|
||||
_aim_patch_reboot_required_after: '{{ _aim_windows_cycle_final_search.reboot_required | default(false) | bool }}'
|
||||
_aim_patch_done: true
|
||||
when:
|
||||
- _aim_windows_cycle_final_search is defined
|
||||
- not (_aim_windows_cycle_final_search.skipped | default(false) | bool)
|
||||
|
||||
- name: Patching | Stop after operator-approved reboot boundary by default
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_done: true
|
||||
_aim_patch_continuation_required: true
|
||||
_aim_patch_remaining_updates_known: false
|
||||
when:
|
||||
- _aim_patch_cycle_reboot_performed | bool
|
||||
- not (os_patching_rescan_after_reboot | bool)
|
||||
|
||||
- name: Patching | Continue only when post-reboot rescan was explicitly enabled
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
AIM completed a reboot boundary and will start another Windows patch cycle because
|
||||
os_patching_rescan_after_reboot is explicitly enabled.
|
||||
when:
|
||||
- _aim_patch_cycle_reboot_performed | bool
|
||||
- os_patching_rescan_after_reboot | bool
|
||||
- not (_aim_patch_action_failed | bool)
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
- name: Patching | Initialize single Windows update result
|
||||
ansible.builtin.set_fact:
|
||||
_aim_windows_single_result: {}
|
||||
_aim_windows_single_task_failed: false
|
||||
|
||||
- name: Patching | Install one Windows update
|
||||
block:
|
||||
- name: 'Patching | Install {{ _aim_windows_update.title }}'
|
||||
ansible.windows.win_updates:
|
||||
category_names: '{{ os_patching_windows_categories }}'
|
||||
state: installed
|
||||
reboot: false
|
||||
accept_list:
|
||||
- '{{ _aim_windows_update.selector }}'
|
||||
register: _aim_windows_single_result
|
||||
rescue:
|
||||
- name: Patching | Retain failed single-update result
|
||||
ansible.builtin.set_fact:
|
||||
_aim_windows_single_result: '{{ ansible_failed_result | default({}) }}'
|
||||
_aim_windows_single_task_failed: true
|
||||
|
||||
- name: Patching | Append single-update evidence
|
||||
ansible.builtin.set_fact:
|
||||
_aim_windows_update_runs: >-
|
||||
{{ _aim_windows_update_runs + [
|
||||
{
|
||||
'requested': _aim_windows_update,
|
||||
'result': _aim_windows_single_result,
|
||||
'task_failed': _aim_windows_single_task_failed | bool
|
||||
}
|
||||
] }}
|
||||
|
||||
- name: Patching | Classify single-update execution state
|
||||
ansible.builtin.set_fact:
|
||||
_aim_windows_single_failed: >-
|
||||
{{ (_aim_windows_single_task_failed | bool) or
|
||||
(_aim_windows_single_result | aim_windows_update_result_failed) }}
|
||||
_aim_patch_reboot_required_after: '{{ _aim_windows_single_result.reboot_required | default(false) | bool }}'
|
||||
|
||||
- name: Patching | Stop this patch wave after an update failure
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_action_failed: true
|
||||
_aim_patch_cycle_stop: true
|
||||
_aim_patch_done: true
|
||||
_aim_patch_continuation_required: true
|
||||
_aim_patch_remaining_updates_known: false
|
||||
_aim_patch_blocked_reason: '{{ _aim_windows_single_result | aim_windows_update_block_reason }}'
|
||||
when: _aim_windows_single_failed | bool
|
||||
|
||||
- name: Patching | Reboot Windows at a sequential update boundary
|
||||
ansible.windows.win_reboot:
|
||||
msg: '{{ os_patching_reboot_message }}'
|
||||
pre_reboot_delay: '{{ [2, (os_patching_reboot_delay_minutes | int) * 60] | max }}'
|
||||
reboot_timeout: '{{ os_patching_reboot_timeout | int }}'
|
||||
register: _aim_windows_single_reboot
|
||||
when:
|
||||
- not (_aim_windows_single_failed | bool)
|
||||
- _aim_windows_single_result.reboot_required | default(false) | bool
|
||||
- os_patching_reboot | bool
|
||||
|
||||
- name: Patching | Record completed sequential reboot boundary
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_cycle_stop: true
|
||||
_aim_patch_cycle_reboot_performed: '{{ _aim_windows_single_reboot.rebooted | default(false) | bool }}'
|
||||
_aim_patch_any_reboot_performed: >-
|
||||
{{ (_aim_patch_any_reboot_performed | bool) or
|
||||
(_aim_windows_single_reboot.rebooted | default(false) | bool) }}
|
||||
_aim_patch_reboot_required_after: >-
|
||||
{{ false if (_aim_windows_single_reboot.rebooted | default(false) | bool)
|
||||
else (_aim_windows_single_result.reboot_required | default(false) | bool) }}
|
||||
when:
|
||||
- _aim_windows_single_reboot is defined
|
||||
- not (_aim_windows_single_reboot.skipped | default(false) | bool)
|
||||
|
||||
- name: Patching | Defer required reboot and stop the current patch wave
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_cycle_stop: true
|
||||
_aim_patch_done: true
|
||||
_aim_patch_reboot_deferred: true
|
||||
_aim_patch_reboot_required_after: true
|
||||
_aim_patch_continuation_required: true
|
||||
_aim_patch_remaining_updates_known: false
|
||||
when:
|
||||
- not (_aim_windows_single_failed | bool)
|
||||
- _aim_windows_single_result.reboot_required | default(false) | bool
|
||||
- not (os_patching_reboot | bool)
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
- name: Patching | Start Windows patch wave
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_cycles: '{{ _aim_patch_wave_number | int }}'
|
||||
_aim_patch_wave_task_failed: false
|
||||
_aim_patch_wave_result: {}
|
||||
_aim_patch_wave_failed: false
|
||||
_aim_patch_wave_reboot_performed: false
|
||||
|
||||
- name: Patching | Install current Windows update wave
|
||||
block:
|
||||
- name: Patching | Install all currently selected Windows updates
|
||||
ansible.windows.win_updates:
|
||||
category_names: '{{ os_patching_windows_categories }}'
|
||||
state: installed
|
||||
reboot: false
|
||||
register: _aim_patch_wave_result
|
||||
rescue:
|
||||
- name: Patching | Retain failed Windows update wave result
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_wave_result: '{{ ansible_failed_result | default({}) }}'
|
||||
_aim_patch_wave_task_failed: true
|
||||
|
||||
- name: Patching | Append Windows update wave evidence
|
||||
ansible.builtin.set_fact:
|
||||
_aim_windows_update_runs: >-
|
||||
{{ _aim_windows_update_runs + [
|
||||
{
|
||||
'result': _aim_patch_wave_result,
|
||||
'task_failed': _aim_patch_wave_task_failed | bool,
|
||||
'wave': _aim_patch_wave_number | int
|
||||
}
|
||||
] }}
|
||||
|
||||
- name: Patching | Classify Windows update wave
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_wave_failed: >-
|
||||
{{ (_aim_patch_wave_task_failed | bool) or
|
||||
(_aim_patch_wave_result | aim_windows_update_result_failed) }}
|
||||
_aim_patch_reboot_required_after: '{{ _aim_patch_wave_result.reboot_required | default(false) | bool }}'
|
||||
|
||||
- name: Patching | Record Windows update wave failure
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_action_failed: true
|
||||
_aim_patch_done: true
|
||||
_aim_patch_continuation_required: true
|
||||
_aim_patch_remaining_updates_known: false
|
||||
_aim_patch_blocked_reason: '{{ _aim_patch_wave_result | aim_windows_update_block_reason }}'
|
||||
when: _aim_patch_wave_failed | bool
|
||||
|
||||
- name: Patching | Reboot after the completed Windows update wave
|
||||
ansible.windows.win_reboot:
|
||||
msg: '{{ os_patching_reboot_message }}'
|
||||
pre_reboot_delay: '{{ [2, (os_patching_reboot_delay_minutes | int) * 60] | max }}'
|
||||
reboot_timeout: '{{ os_patching_reboot_timeout | int }}'
|
||||
register: _aim_patch_wave_reboot
|
||||
when:
|
||||
- _aim_patch_wave_result.reboot_required | default(false) | bool
|
||||
- os_patching_reboot | bool
|
||||
|
||||
- name: Patching | Record completed Windows wave reboot boundary
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_wave_reboot_performed: '{{ _aim_patch_wave_reboot.rebooted | default(false) | bool }}'
|
||||
_aim_patch_any_reboot_performed: >-
|
||||
{{ (_aim_patch_any_reboot_performed | bool) or
|
||||
(_aim_patch_wave_reboot.rebooted | default(false) | bool) }}
|
||||
_aim_patch_reboot_required_after: >-
|
||||
{{ false if (_aim_patch_wave_reboot.rebooted | default(false) | bool)
|
||||
else (_aim_patch_wave_result.reboot_required | default(false) | bool) }}
|
||||
when:
|
||||
- _aim_patch_wave_reboot is defined
|
||||
- not (_aim_patch_wave_reboot.skipped | default(false) | bool)
|
||||
|
||||
- name: Patching | Defer required reboot after Windows update wave
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_done: true
|
||||
_aim_patch_reboot_deferred: true
|
||||
_aim_patch_reboot_required_after: true
|
||||
_aim_patch_continuation_required: true
|
||||
_aim_patch_remaining_updates_known: false
|
||||
when:
|
||||
- not (_aim_patch_wave_failed | bool)
|
||||
- _aim_patch_wave_result.reboot_required | default(false) | bool
|
||||
- not (os_patching_reboot | bool)
|
||||
|
||||
- name: Patching | Stop after approved Windows reboot boundary by default
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_done: true
|
||||
_aim_patch_continuation_required: true
|
||||
_aim_patch_remaining_updates_known: false
|
||||
when:
|
||||
- _aim_patch_wave_reboot_performed | bool
|
||||
- not (os_patching_rescan_after_reboot | bool)
|
||||
|
||||
- name: Patching | Stop automatic continuation after a failed Windows wave
|
||||
ansible.builtin.set_fact:
|
||||
_aim_patch_done: true
|
||||
_aim_patch_continuation_required: true
|
||||
_aim_patch_remaining_updates_known: false
|
||||
when:
|
||||
- _aim_patch_wave_failed | bool
|
||||
|
||||
- name: Patching | Final read-only discovery after completed non-reboot Windows wave
|
||||
ansible.windows.win_updates:
|
||||
category_names: '{{ os_patching_windows_categories }}'
|
||||
state: searched
|
||||
reboot: false
|
||||
register: _aim_windows_wave_final_search
|
||||
when:
|
||||
- not (_aim_patch_done | bool)
|
||||
- not (_aim_patch_wave_reboot_performed | bool)
|
||||
- not (_aim_patch_wave_result.reboot_required | default(false) | bool)
|
||||
- not (_aim_patch_wave_failed | bool)
|
||||
|
||||
- name: Patching | Record completed non-reboot Windows wave state
|
||||
ansible.builtin.set_fact:
|
||||
_aim_windows_searches: '{{ _aim_windows_searches + [_aim_windows_wave_final_search] }}'
|
||||
_aim_patch_remaining_updates_known: true
|
||||
_aim_patch_continuation_required: '{{ (_aim_windows_wave_final_search.found_update_count | default(0) | int) > 0 }}'
|
||||
_aim_patch_reboot_required_after: '{{ _aim_windows_wave_final_search.reboot_required | default(false) | bool }}'
|
||||
_aim_patch_done: true
|
||||
when:
|
||||
- _aim_windows_wave_final_search is defined
|
||||
- not (_aim_windows_wave_final_search.skipped | default(false) | bool)
|
||||
Reference in New Issue
Block a user