Compare commits
62 Commits
mhorak-pat
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| feb0fc6433 | |||
| b87e461515 | |||
| ff3ce22d95 | |||
| 6085075e26 | |||
| c61f39f5fb | |||
| 37c6298fea | |||
| aa2d8f7350 | |||
| 23ff69931d | |||
| 05d402898f | |||
| 6f4ba5620d | |||
| 24bcd498ea | |||
| e2b2881b38 | |||
| 5a1b3343c1 | |||
| 6a1a25c85c | |||
| ae100c28f4 | |||
| f5c980827a | |||
| 85b84107c5 | |||
| 75f2bd520e | |||
| dcf9c12365 | |||
| 93e90b375a | |||
| 1ddd80a6f7 | |||
| 6b4fdecedd | |||
| fd2967afad | |||
| c60f824ffd | |||
| 904b504a08 | |||
| c7f936875f | |||
| caa958af74 | |||
| 7c227ce583 | |||
| 8f97076381 | |||
| 0198b3b2e2 | |||
| b76a10b645 | |||
| ad228cb068 | |||
| 3dc52d7f61 | |||
| 245a5a945b | |||
| e286ecf934 | |||
| 2c92878d7b | |||
| 1f6faa1cdd | |||
| f5d9cff4a6 | |||
| 9404e92771 | |||
| 9bb6c76f27 | |||
| 6c23adf67d | |||
| 52faca0af7 | |||
| 34a7948b18 | |||
| 595e0983aa | |||
| 30bac571ef | |||
| d98c9439b8 | |||
| d91792dc47 | |||
| 99db5e2aca | |||
| 3047cded48 | |||
| 432ca44b8d | |||
| 1688ead9c8 | |||
| 34d1b93288 | |||
| 30cfa03f64 | |||
| 01a44aaf73 | |||
| 1997fc90cb | |||
| 2a02f54ba7 | |||
| 2e6b21b495 | |||
| 5a511375dc | |||
| 3cf64d848e | |||
| 66970dca65 | |||
| bf173dd80c | |||
| e91e29d600 |
137
Sharepoint.yaml
137
Sharepoint.yaml
@@ -43,6 +43,51 @@
|
|||||||
no_log: true
|
no_log: true
|
||||||
failed_when: graph_token.status not in [200]
|
failed_when: graph_token.status not in [200]
|
||||||
|
|
||||||
|
# --- Get recap from AWX and turn it into flat totals ---
|
||||||
|
- name: Fetch play recap (playbook_on_stats)
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
uri:
|
||||||
|
url: "{{ lookup('env','AWX_API_URL') }}/api/v2/jobs/{{ tower_job_id }}/job_events/?event=playbook_on_stats"
|
||||||
|
method: GET
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ lookup('env','AWX_API_TOKEN') }}"
|
||||||
|
Content-Type: "application/json"
|
||||||
|
return_content: true
|
||||||
|
status_code: 200
|
||||||
|
register: _recap
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Parse recap totals
|
||||||
|
run_once: true
|
||||||
|
vars:
|
||||||
|
stats: "{{ (_recap.json.results | default([])) | first | default({}) }}"
|
||||||
|
data: "{{ stats.event_data | default({}) }}"
|
||||||
|
set_fact:
|
||||||
|
recap_ok: "{{ (data.ok | default({})) | dict2items | map(attribute='value') | map('int') | sum }}"
|
||||||
|
recap_changed: "{{ (data.changed | default({})) | dict2items | map(attribute='value') | map('int') | sum }}"
|
||||||
|
recap_failed: "{{ (data.failures | default({})) | dict2items | map(attribute='value') | map('int') | sum }}"
|
||||||
|
recap_skipped: "{{ (data.skipped | default({})) | dict2items | map(attribute='value') | map('int') | sum }}"
|
||||||
|
recap_unreach: "{{ (data.dark | default({})) | dict2items | map(attribute='value') | map('int') | sum }}"
|
||||||
|
|
||||||
|
- name: Build SharePoint recap line (store on localhost for later use)
|
||||||
|
run_once: true
|
||||||
|
delegate_to: localhost
|
||||||
|
delegate_facts: true
|
||||||
|
set_fact:
|
||||||
|
recap_line: >-
|
||||||
|
OK={{ recap_ok | default(0) }},
|
||||||
|
Changed={{ recap_changed | default(0) }},
|
||||||
|
Failed={{ recap_failed | default(0) }},
|
||||||
|
Skipped={{ recap_skipped | default(0) }},
|
||||||
|
Unreachable={{ recap_unreach | default(0) }}
|
||||||
|
|
||||||
|
- name: Build final status from recap
|
||||||
|
run_once: true
|
||||||
|
set_fact:
|
||||||
|
status_final: "{{ 'failed' if (recap_failed | int > 0 or recap_unreach | int > 0) else 'successful' }}"
|
||||||
|
|
||||||
|
# --- Optional probes (keep while debugging) ---
|
||||||
- name: Verify siteId resolves
|
- name: Verify siteId resolves
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
run_once: true
|
run_once: true
|
||||||
@@ -67,7 +112,7 @@
|
|||||||
return_content: true
|
return_content: true
|
||||||
status_code: 200
|
status_code: 200
|
||||||
register: lists_probe
|
register: lists_probe
|
||||||
no_log: false
|
no_log: true
|
||||||
|
|
||||||
- name: Show lists (sanitized)
|
- name: Show lists (sanitized)
|
||||||
run_once: true
|
run_once: true
|
||||||
@@ -85,60 +130,64 @@
|
|||||||
return_content: true
|
return_content: true
|
||||||
status_code: 200
|
status_code: 200
|
||||||
register: cols_probe
|
register: cols_probe
|
||||||
no_log: false
|
no_log: true
|
||||||
|
|
||||||
- name: Print internal names
|
|
||||||
run_once: true
|
|
||||||
debug:
|
|
||||||
var: cols_probe.json.value | map(attribute='name') | list
|
|
||||||
|
|
||||||
- name: Show internal column names (safe)
|
- name: Show internal column names (safe)
|
||||||
|
run_once: true
|
||||||
debug:
|
debug:
|
||||||
msg:
|
msg:
|
||||||
names: "{{ (cols_probe.json.value | default([])) | map(attribute='name') | list }}"
|
names: "{{ (cols_probe.json.value | default([])) | map(attribute='name') | list }}"
|
||||||
|
|
||||||
|
# --- Create list item (no block/rescue; use ignore_errors + diagnostics) ---
|
||||||
- name: Create SharePoint list item (Graph)
|
- name: Create SharePoint list item (Graph)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
run_once: true
|
run_once: true
|
||||||
block:
|
uri:
|
||||||
- uri:
|
url: "https://graph.microsoft.com/v1.0/sites/{{ site_id }}/lists/{{ list_id }}/items"
|
||||||
url: "https://graph.microsoft.com/v1.0/sites/{{ site_id }}/lists/{{ list_id }}/items"
|
method: POST
|
||||||
method: POST
|
headers:
|
||||||
headers:
|
Authorization: "Bearer {{ graph_token.json.access_token }}"
|
||||||
Authorization: "Bearer {{ graph_token.json.access_token }}"
|
Content-Type: "application/json"
|
||||||
Content-Type: "application/json"
|
body_format: json
|
||||||
body_format: json
|
return_content: true
|
||||||
return_content: true
|
status_code: [200, 201]
|
||||||
status_code: [200, 201]
|
body:
|
||||||
body:
|
fields:
|
||||||
fields:
|
Title: "{{ job_name }} ({{ job_id }})"
|
||||||
Title: "{{ job_name }} ({{ job_id }})"
|
Status: "{{ status_final }}"
|
||||||
Status: "{{ status }}"
|
RunStart: "{{ run_start }}"
|
||||||
RunStart: "{{ run_start }}"
|
RunEnd: "{{ run_end }}"
|
||||||
RunEnd: "{{ run_end }}"
|
Notes: |-
|
||||||
Notes: "{{ summary_text }}"
|
{{ summary_text }}
|
||||||
register: sp_create
|
Recap: {{ hostvars['localhost'].recap_line }}
|
||||||
no_log: false
|
register: sp_create
|
||||||
|
ignore_errors: true
|
||||||
|
no_log: true
|
||||||
|
|
||||||
rescue:
|
- name: Show sanitized Graph error (if any)
|
||||||
- name: Sanitize and print the error
|
run_once: true
|
||||||
run_once: true
|
when: sp_create is failed
|
||||||
vars:
|
vars:
|
||||||
_json: "{{ sp_create.json | default({}) }}"
|
_json: "{{ sp_create.json | default({}) }}"
|
||||||
debug:
|
debug:
|
||||||
msg:
|
msg:
|
||||||
status: "{{ sp_create.status | default('n/a') }}"
|
status: "{{ sp_create.status | default('n/a') }}"
|
||||||
graph_error: >-
|
graph_error: >-
|
||||||
{{ _json.error.message
|
{{ _json.error.message
|
||||||
| default(_json.message
|
| default(_json.message
|
||||||
| default(sp_create.msg | default('Unknown error'))) }}
|
| default(sp_create.msg | default('Unknown error'))) }}
|
||||||
hint: >
|
hint: >
|
||||||
400: column internal names; 401: scope/audience; 403: permissions;
|
400: column internal names; 401: scope/audience; 403: permissions;
|
||||||
404: siteId/listId.
|
404: siteId/listId.
|
||||||
- fail:
|
|
||||||
msg: "Failed to create SharePoint item (see previous message)."
|
- name: Fail if SharePoint item was not created
|
||||||
|
run_once: true
|
||||||
|
when: sp_create is failed
|
||||||
|
fail:
|
||||||
|
msg: "Failed to create SharePoint item (see previous message)."
|
||||||
|
|
||||||
- name: Show created list item id
|
- name: Show created list item id
|
||||||
run_once: true
|
run_once: true
|
||||||
|
when: sp_create is succeeded
|
||||||
debug:
|
debug:
|
||||||
var: sp_create.json.id
|
var: sp_create.json.id
|
||||||
6
collections/requirements.yml
Normal file
6
collections/requirements.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
collections:
|
||||||
|
- name: ansible.windows
|
||||||
|
version: ">=2.5.0,<3.0.0"
|
||||||
|
- name: community.windows
|
||||||
|
version: ">=2.3.0,<3.0.0"
|
||||||
7
patch-dc-controllers.yaml
Normal file
7
patch-dc-controllers.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
- name: Patch Domain Controllers via PSRP
|
||||||
|
hosts: domain_controllers
|
||||||
|
gather_facts: no # ✅ Important: disables Python requirement
|
||||||
|
tasks:
|
||||||
|
- name: Install updates
|
||||||
|
command: Install-WindowsUpdate -AcceptAll -AutoReboot
|
||||||
262
win-updates-troubleshooting.yaml
Normal file
262
win-updates-troubleshooting.yaml
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
---
|
||||||
|
- name: Windows Update Installation from Assessment Report
|
||||||
|
hosts: windows
|
||||||
|
gather_facts: no
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Get current timestamp
|
||||||
|
set_fact:
|
||||||
|
current_timestamp: "{{ lookup('pipe', 'date +%Y-%m-%dT%H:%M:%S') }}"
|
||||||
|
|
||||||
|
- name: Check if KB updates report file exists
|
||||||
|
win_stat:
|
||||||
|
path: 'C:\Temp\windows_updates_with_kb.txt'
|
||||||
|
register: kb_updates_file
|
||||||
|
|
||||||
|
# ---- DO NOT hard-fail; flag host and stop further tasks on this host ----
|
||||||
|
- name: Mark host failed if report is missing (but continue overall run)
|
||||||
|
set_fact:
|
||||||
|
patch_failed_host: true
|
||||||
|
patch_failed_count: "{{ (patch_failed_count | default(0) | int) + 1 }}"
|
||||||
|
patch_fail_reason: "KB report missing at C:\\Temp\\windows_updates_with_kb.txt"
|
||||||
|
when: not kb_updates_file.stat.exists
|
||||||
|
|
||||||
|
- name: Stop further tasks on this host (report missing)
|
||||||
|
meta: end_host
|
||||||
|
when: not kb_updates_file.stat.exists
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Read KB updates report content
|
||||||
|
win_shell: Get-Content -Path 'C:\Temp\windows_updates_with_kb.txt'
|
||||||
|
register: updates_content
|
||||||
|
when: kb_updates_file.stat.exists
|
||||||
|
|
||||||
|
- name: Extract KB numbers from report file
|
||||||
|
set_fact:
|
||||||
|
kb_numbers: "{{ updates_content.stdout_lines
|
||||||
|
| select('match', '.*KB: .*')
|
||||||
|
| map('regex_replace', '.*KB: ([0-9,\\s]+).*', '\\1')
|
||||||
|
| map('split', ',') | flatten | map('trim')
|
||||||
|
| select('match', '^[0-9]+$') | list | unique }}"
|
||||||
|
when:
|
||||||
|
- kb_updates_file.stat.exists
|
||||||
|
- updates_content.stdout_lines is defined
|
||||||
|
|
||||||
|
- name: Display KB numbers to be installed
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "Found {{ kb_numbers | length }} unique KB numbers to install:"
|
||||||
|
- "{{ kb_numbers | join(', ') }}"
|
||||||
|
when:
|
||||||
|
- kb_updates_file.stat.exists
|
||||||
|
- kb_numbers is defined
|
||||||
|
- kb_numbers | length > 0
|
||||||
|
|
||||||
|
# ---- Patch with failure capture (block/rescue) ----
|
||||||
|
- name: Install Windows updates by KB numbers (with failure capture)
|
||||||
|
block:
|
||||||
|
- name: Install Windows updates by KB numbers
|
||||||
|
win_updates:
|
||||||
|
category_names: '*'
|
||||||
|
state: installed
|
||||||
|
accept_list: "{{ kb_numbers }}"
|
||||||
|
log_path: 'C:\Temp\windows_update_installation.log'
|
||||||
|
register: installation_result
|
||||||
|
when:
|
||||||
|
- kb_updates_file.stat.exists
|
||||||
|
- kb_numbers is defined
|
||||||
|
- kb_numbers | length > 0
|
||||||
|
|
||||||
|
- name: Mark host failed if any KB installs failed (partial failures)
|
||||||
|
set_fact:
|
||||||
|
patch_failed_host: "{{ (installation_result.failed_update_count | default(0) | int) > 0 }}"
|
||||||
|
patch_failed_count: "{{ installation_result.failed_update_count | default(0) | int }}"
|
||||||
|
when: installation_result is defined
|
||||||
|
|
||||||
|
rescue:
|
||||||
|
- name: Mark host failed because win_updates task error/exception
|
||||||
|
set_fact:
|
||||||
|
patch_failed_host: true
|
||||||
|
patch_failed_count: "{{ (patch_failed_count | default(0) | int) + 1 }}"
|
||||||
|
|
||||||
|
always:
|
||||||
|
- name: Ensure failure flags exist (default to false/0)
|
||||||
|
set_fact:
|
||||||
|
patch_failed_host: "{{ patch_failed_host | default(false) }}"
|
||||||
|
patch_failed_count: "{{ patch_failed_count | default(0) | int }}"
|
||||||
|
|
||||||
|
- name: Display installation summary
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "=== WINDOWS UPDATE INSTALLATION COMPLETE ==="
|
||||||
|
- "Host: {{ inventory_hostname }}"
|
||||||
|
- "Updates Found: {{ installation_result.found_update_count | default(0) }}"
|
||||||
|
- "Updates Installed: {{ installation_result.installed_update_count | default(0) }}"
|
||||||
|
- "Updates Failed: {{ installation_result.failed_update_count | default(0) }}"
|
||||||
|
- "Reboot Required: {{ 'Yes' if installation_result.reboot_required | default(false) else 'No' }}"
|
||||||
|
- "Patch failed flag: {{ patch_failed_host | default(false) }}"
|
||||||
|
when:
|
||||||
|
- kb_updates_file.stat.exists
|
||||||
|
- kb_numbers is defined
|
||||||
|
- kb_numbers | length > 0
|
||||||
|
|
||||||
|
- name: Reboot if required
|
||||||
|
win_reboot:
|
||||||
|
reboot_timeout: 1800
|
||||||
|
when: installation_result is defined and (installation_result.reboot_required | default(false))
|
||||||
|
|
||||||
|
- name: Create installation report
|
||||||
|
set_fact:
|
||||||
|
installation_summary: |
|
||||||
|
Windows Update Installation Report
|
||||||
|
=================================
|
||||||
|
Host: {{ inventory_hostname }}
|
||||||
|
Date: {{ current_timestamp }}
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
--------
|
||||||
|
Total Updates Found: {{ installation_result.found_update_count | default(0) }}
|
||||||
|
Successfully Installed: {{ installation_result.installed_update_count | default(0) }}
|
||||||
|
Failed Installations: {{ installation_result.failed_update_count | default(0) }}
|
||||||
|
Reboot Required: {{ installation_result.reboot_required | default('No') }}
|
||||||
|
|
||||||
|
Requested KB Numbers: {{ kb_numbers | default([]) | join(', ') }}
|
||||||
|
|
||||||
|
{% if installation_result is defined and installation_result.updates is defined %}
|
||||||
|
Installed Updates:
|
||||||
|
-----------------
|
||||||
|
{% for update_id, update_info in installation_result.updates.items() %}
|
||||||
|
- {{ update_info.title }}
|
||||||
|
KB: {{ update_info.kb | join(', ') if update_info.kb else 'None' }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
when: kb_updates_file.stat.exists
|
||||||
|
|
||||||
|
- name: Save installation report to file
|
||||||
|
win_copy:
|
||||||
|
content: "{{ installation_summary }}"
|
||||||
|
dest: 'C:\Temp\windows_update_installation_report.txt'
|
||||||
|
when: installation_summary is defined
|
||||||
|
|
||||||
|
- name: Give a report when no KB numbers were found on updates
|
||||||
|
debug:
|
||||||
|
msg: "No valid KB numbers found in the updates report file. Please verify the assessment report."
|
||||||
|
when:
|
||||||
|
- kb_updates_file.stat.exists
|
||||||
|
- (kb_numbers is not defined or kb_numbers | length == 0)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Aggregate results and post to SharePoint (always runs)
|
||||||
|
hosts: localhost
|
||||||
|
connection: local
|
||||||
|
gather_facts: false
|
||||||
|
|
||||||
|
vars:
|
||||||
|
tenant_id: "{{ lookup('env', 'SP_TENANT_ID') }}"
|
||||||
|
client_id: "{{ lookup('env', 'SP_CLIENT_ID') }}"
|
||||||
|
client_secret: "{{ lookup('env', 'SP_CLIENT_SECRET') }}"
|
||||||
|
site_id: "{{ lookup('env', 'SP_SITE_ID') }}"
|
||||||
|
list_id: "{{ lookup('env', 'SP_LIST_ID') }}"
|
||||||
|
|
||||||
|
job_id: "{{ tower_job_id | default('n/a') }}"
|
||||||
|
job_name: "{{ tower_job_template_name | default('Patch run') }}"
|
||||||
|
job_url: "{{ tower_job_url | default('') }}"
|
||||||
|
|
||||||
|
run_start: "{{ lookup('pipe','date -u +%Y-%m-%dT%H:%M:%SZ') }}"
|
||||||
|
run_end: "{{ lookup('pipe','date -u +%Y-%m-%dT%H:%M:%SZ') }}"
|
||||||
|
|
||||||
|
summary_text: >-
|
||||||
|
Job {{ job_id }}.
|
||||||
|
Template={{ job_name }}.
|
||||||
|
URL="http://172.18.13.188:10445/".
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Init failed hosts list
|
||||||
|
set_fact:
|
||||||
|
failed_hosts_list: []
|
||||||
|
|
||||||
|
- name: Collect hosts that flagged patch failure
|
||||||
|
set_fact:
|
||||||
|
failed_hosts_list: "{{ failed_hosts_list + [item] }}"
|
||||||
|
loop: "{{ groups['windows'] | default([]) }}"
|
||||||
|
when: hostvars[item].patch_failed_host | default(false)
|
||||||
|
|
||||||
|
- name: Compute final status and CSV
|
||||||
|
set_fact:
|
||||||
|
any_patch_failed: "{{ (failed_hosts_list | length) > 0 }}"
|
||||||
|
failed_hosts_csv: "{{ failed_hosts_list | join(', ') if failed_hosts_list | length > 0 else 'None' }}"
|
||||||
|
status_final: "{{ 'failed' if (failed_hosts_list | length) > 0 else 'successful' }}"
|
||||||
|
|
||||||
|
- name: Sanity — status to post
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
status_final: "{{ status_final }}"
|
||||||
|
failed_hosts_csv: "{{ failed_hosts_csv }}"
|
||||||
|
failed_hosts_list: "{{ failed_hosts_list }}"
|
||||||
|
|
||||||
|
- name: Acquire Graph token (client credentials)
|
||||||
|
uri:
|
||||||
|
url: "https://login.microsoftonline.com/{{ tenant_id }}/oauth2/v2.0/token"
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Content-Type: "application/x-www-form-urlencoded"
|
||||||
|
body: >
|
||||||
|
client_id={{ client_id }}
|
||||||
|
&client_secret={{ client_secret | urlencode }}
|
||||||
|
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
|
||||||
|
&grant_type=client_credentials
|
||||||
|
register: graph_token
|
||||||
|
no_log: true
|
||||||
|
failed_when: graph_token.status not in [200]
|
||||||
|
|
||||||
|
- name: Create SharePoint list item (Graph)
|
||||||
|
uri:
|
||||||
|
url: "https://graph.microsoft.com/v1.0/sites/{{ site_id }}/lists/{{ list_id }}/items"
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ graph_token.json.access_token }}"
|
||||||
|
Content-Type: "application/json"
|
||||||
|
body_format: json
|
||||||
|
return_content: true
|
||||||
|
status_code: [200, 201]
|
||||||
|
body:
|
||||||
|
fields:
|
||||||
|
Title: "{{ job_name }} ({{ job_id }})"
|
||||||
|
Status: "{{ status_final }}"
|
||||||
|
RunStart: "{{ run_start }}"
|
||||||
|
RunEnd: "{{ run_end }}"
|
||||||
|
Notes: |-
|
||||||
|
{{ summary_text }}
|
||||||
|
Failed hosts: {{ failed_hosts_csv }}
|
||||||
|
register: sp_create
|
||||||
|
ignore_errors: true
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Show sanitized Graph error (if any)
|
||||||
|
when: sp_create is failed
|
||||||
|
vars:
|
||||||
|
_json: "{{ sp_create.json | default({}) }}"
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
status: "{{ sp_create.status | default('n/a') }}"
|
||||||
|
graph_error: >-
|
||||||
|
{{ _json.error.message
|
||||||
|
| default(_json.message
|
||||||
|
| default(sp_create.msg | default('Unknown error'))) }}
|
||||||
|
|
||||||
|
- name: Fail if SharePoint item was not created
|
||||||
|
when: sp_create is failed
|
||||||
|
fail:
|
||||||
|
msg: "Failed to create SharePoint item (see previous message)."
|
||||||
|
|
||||||
|
- name: Show created list item id
|
||||||
|
when: sp_create is succeeded
|
||||||
|
debug:
|
||||||
|
var: sp_create.json.id
|
||||||
|
|
||||||
|
# flip the AWX job status based on your aggregate
|
||||||
|
- name: Mark AWX job as FAILED if any host failed
|
||||||
|
when: status_final == 'failed'
|
||||||
|
fail:
|
||||||
|
msg: "Patch run failed on hosts: {{ failed_hosts_csv }}"
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
- name: Check Free Disk Space
|
- name: Check Free Disk Space
|
||||||
hosts: windows
|
hosts: windows
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: "Check Free Disk Space in C:"
|
- name: "Check Free Disk Space in C:"
|
||||||
win_shell: |
|
win_shell: |
|
||||||
@@ -18,4 +19,66 @@
|
|||||||
fail:
|
fail:
|
||||||
msg: "the node {{ inventory_hostname }} has insufficient disk space: {{ freediskspace.stdout | trim }} GB (minimum required: 20 GB)"
|
msg: "the node {{ inventory_hostname }} has insufficient disk space: {{ freediskspace.stdout | trim }} GB (minimum required: 20 GB)"
|
||||||
when:
|
when:
|
||||||
- freediskspace.stdout | trim | float < 20
|
- freediskspace.stdout | trim | float < 20
|
||||||
|
|
||||||
|
- name: Find Recovery partitions (PowerShell → JSON)
|
||||||
|
ansible.windows.win_powershell:
|
||||||
|
script: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$guid = '{DE94BBA4-06D1-4D40-A16A-BFD50179D6AC}'
|
||||||
|
$parts = @( Get-Partition |
|
||||||
|
Where-Object { $_.GptType -eq $guid -or $_.Type -eq 'Recovery' -or $_.Type -eq 'Unknown' } |
|
||||||
|
Select-Object DiskNumber, PartitionNumber,
|
||||||
|
@{n='SizeBytes';e={$_.Size}},
|
||||||
|
@{n='SizeMB';e={[math]::Round($_.Size/1MB,2)}},
|
||||||
|
GptType, Type )
|
||||||
|
$parts = @($parts)
|
||||||
|
$parts | ConvertTo-Json -Compress -Depth 4
|
||||||
|
register: winre_raw
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Parse JSON (string → object)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
winre_parts: "{{ (winre_raw.output | join('') | trim | default('[]')) | from_json }}"
|
||||||
|
|
||||||
|
- name: Normalize to list
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
winre_parts: "{{ [winre_parts] }}"
|
||||||
|
when: winre_parts is mapping
|
||||||
|
|
||||||
|
# Thresholds
|
||||||
|
- name: Set Recovery size threshold
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
recovery_threshold_mib: 500
|
||||||
|
recovery_threshold_bytes: "{{ 500 * 1024 * 1024 }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
# Build a list of sizes as INTS
|
||||||
|
- name: Extract recovery sizes (bytes → ints)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
recovery_sizes_bytes: "{{ winre_parts | map(attribute='SizeBytes') | map('int') | list }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
# Compute smallest size (bytes)
|
||||||
|
- name: Compute smallest Recovery partition size (bytes)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
recovery_min_bytes: "{{ recovery_sizes_bytes | min }}"
|
||||||
|
when: recovery_sizes_bytes | length > 0
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
# Fail this host if smallest Recovery partition < 500 MiB
|
||||||
|
- name: Enforce minimum Recovery partition size
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: >-
|
||||||
|
Recovery partition too small on {{ inventory_hostname }}:
|
||||||
|
smallest={{ ((recovery_min_bytes | int) / 1048576.0) | round(2) }} MiB,
|
||||||
|
required >= {{ recovery_threshold_mib | int }} MiB.
|
||||||
|
Details: {{ winre_parts }}
|
||||||
|
when:
|
||||||
|
- recovery_sizes_bytes | length > 0
|
||||||
|
- (recovery_min_bytes | int) < (recovery_threshold_bytes | int)
|
||||||
|
|
||||||
|
- name: Show Recovery partition sizes
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "Disk {{ item.DiskNumber }}, Part {{ item.PartitionNumber }}, Size: {{ item.SizeMB }} MB ({{ item.SizeBytes }} bytes)"
|
||||||
|
loop: "{{ winre_parts }}"
|
||||||
Reference in New Issue
Block a user