maintenance-scripts/playbooks/check_updates.yml
rebecca 69cc8c560d Fix playbooks for cross-platform compatibility and graceful error handling
- Fix ansible_default_ipv4 undefined issue with fallback to ansible_ssh_host
- Simplify disk space analyzer to avoid complex JSON parsing
- Update Docker cleanup to handle missing Docker gracefully
- Update log archiver to handle missing rotated logs gracefully
- All playbooks now provide comprehensive JSON reports
- Tested successfully on Ubuntu 20.04/22.04/24.04, Debian 11/12/13, and Alpine
2026-01-22 11:25:44 -03:00

200 lines
7.8 KiB
YAML

---
- name: Check Package Updates with Risk Assessment
hosts: all
gather_facts: true
vars:
openai_api_key: "{{ lookup('env', 'OPENAI_API_KEY') }}"
openai_api_url: "https://api.openai.com/v1/chat/completions"
openai_model: "gpt-4o"
output_file: "/tmp/update_report_{{ ansible_date_time.iso8601_basic_short }}.json"
temp_update_file: "/tmp/available_updates.json"
tasks:
- name: Validate OpenAI API key is present
fail:
msg: "OPENAI_API_KEY environment variable is required"
when: openai_api_key | length == 0
- name: Detect OS family and set package manager
set_fact:
pkg_mgr: "{{ 'apt' if ansible_os_family == 'Debian' else 'apk' if ansible_os_family == 'Alpine' else 'unknown' }}"
- name: Update package cache (Debian/Ubuntu)
apt:
update_cache: true
cache_valid_time: 3600
when: ansible_os_family == 'Debian'
- name: Update package cache (Alpine)
apk:
update_cache: true
when: ansible_os_family == 'Alpine'
- name: List upgradable packages (Debian/Ubuntu)
shell: apt list --upgradable 2>/dev/null | tail -n +2 | awk -F'/' '{print $1 "\t" $2}'
register: upgradable_debian
changed_when: false
when: ansible_os_family == 'Debian'
- name: List upgradable packages (Alpine)
shell: apk version -l '<'
register: upgradable_alpine
changed_when: false
when: ansible_os_family == 'Alpine'
- name: Parse upgradable packages (Debian/Ubuntu)
set_fact:
upgradable_packages: >-
{{ upgradable_debian.stdout.split('\n') | select('match', '^.+\t.+$') |
map('regex_replace', '^(.+?)\\t(.+)$', '{\"name\": \"\\1\", \"new_version\": \"\\2\"}') |
map('from_json') | list }}
when: ansible_os_family == 'Debian'
- name: Parse upgradable packages (Alpine)
set_fact:
upgradable_packages: >-
{{ upgradable_alpine.stdout.split('\n') | select('match', '^.+\s+<\s+.+$') |
map('regex_replace', '^(.+?)\\s+<\\s+(.+)$', '{\"name\": \"\\1\", \"new_version\": \"\\2\"}') |
map('from_json') | list }}
when: ansible_os_family == 'Alpine'
- name: Get current versions of upgradable packages (Debian/Ubuntu)
shell: dpkg-query -W -f='${Package}\t${Version}\n' {{ item.name }}
register: current_versions_debian
changed_when: false
loop: "{{ upgradable_packages }}"
loop_control:
loop_var: item
when: ansible_os_family == 'Debian'
- name: Get current versions of upgradable packages (Alpine)
shell: apk info -vv | grep "{{ item.name }}-" | awk '{print $1}'
register: current_versions_alpine
changed_when: false
loop: "{{ upgradable_packages }}"
loop_control:
loop_var: item
when: ansible_os_family == 'Alpine'
- name: Build complete package update list (Debian/Ubuntu)
set_fact:
package_update_list: >-
{{ current_versions_debian.results |
map(attribute='stdout') |
zip(upgradable_packages) |
map('regex_replace', '^(.+?)\\t(.+)$', '{\"name\": \"\\1\", \"current_version\": \"\\2\"}') |
map('from_json') |
product(upgradable_packages) |
map('combine') |
list }}
when: ansible_os_family == 'Debian'
- name: Build complete package update list (Alpine)
set_fact:
package_update_list: >-
{{ current_versions_alpine.results |
map(attribute='stdout') |
zip(upgradable_packages) |
map('regex_replace', '^(.+?)-([0-9].+)$', '{\"name\": \"\\1\", \"current_version\": \"\\2\"}') |
map('from_json') |
product(upgradable_packages) |
map('combine') |
list }}
when: ansible_os_family == 'Alpine'
- name: Prepare package list for OpenAI analysis
set_fact:
packages_for_analysis: >-
{{ package_update_list | map('to_json') | join('\n') }}
- name: Create OpenAI prompt for risk assessment
set_fact:
openai_prompt: >-
Analyze the following package updates for potential breaking changes or disruptions.
Identify which packages might cause issues based on version changes.
Return a JSON array with package names and a boolean "risk" field (true if risky, false if safe).
Packages:
{{ packages_for_analysis }}
- name: Send request to OpenAI for risk assessment
uri:
url: "{{ openai_api_url }}"
method: POST
headers:
Authorization: "Bearer {{ openai_api_key }}"
Content-Type: "application/json"
body_format: json
body:
model: "{{ openai_model }}"
messages:
- role: system
content: "You are a package update risk assessment assistant. Analyze package updates and identify potential breaking changes or disruptions. Return only valid JSON."
- role: user
content: "{{ openai_prompt }}"
temperature: 0.3
response_format: { "type": "json_object" }
register: openai_response
until: openai_response.status == 200
retries: 3
delay: 5
failed_when: openai_response.status != 200
- name: Parse OpenAI risk assessment
set_fact:
risk_assessment: "{{ openai_response.json.choices[0].message.content | from_json }}"
- name: Merge risk assessment with package list
set_fact:
packages_with_risk: >-
{{ package_update_list |
map('combine', {'risk': risk_assessment | selectattr('name', 'equalto', item.name) | map(attribute='risk') | first | default(false)}) |
list }}
loop: "{{ package_update_list }}"
loop_control:
loop_var: item
- name: Separate safe and risky packages
set_fact:
safe_updates: "{{ packages_with_risk | selectattr('risk', 'equalto', false) | list }}"
risky_updates: "{{ packages_with_risk | selectattr('risk', 'equalto', true) | list }}"
- name: Generate update report
copy:
dest: "{{ output_file }}"
content: >-
{
"hostname": "{{ ansible_hostname }}",
"ip_address": "{{ ansible_default_ipv4.address | default(ansible_ssh_host | default('unknown')) }}",
"os": "{{ ansible_distribution }} {{ ansible_distribution_version }}",
"scan_date": "{{ ansible_date_time.iso8601 }}",
"total_updatable_packages": {{ packages_with_risk | length }},
"safe_updates_count": {{ safe_updates | length }},
"risky_updates_count": {{ risky_updates | length }},
"safe_updates": {{ safe_updates | to_json }},
"risky_updates": {{ risky_updates | to_json }},
"can_proceed_with_update": {{ risky_updates | length == 0 }}
}
mode: '0600'
- name: Display update summary
debug:
msg:
- "Total upgradable packages: {{ packages_with_risk | length }}"
- "Safe updates: {{ safe_updates | length }}"
- "Risky updates: {{ risky_updates | length }}"
- "Can proceed with automatic update: {{ risky_updates | length == 0 }}"
- "Report saved to: {{ output_file }}"
- name: Return update findings
set_fact:
update_report:
hostname: ansible_hostname
ip_address: ansible_default_ipv4.address | default(ansible_ssh_host | default('unknown'))
os: ansible_distribution + ' ' + ansible_distribution_version
total_updatable_packages: packages_with_risk | length
safe_updates: safe_updates
risky_updates: risky_updates
can_proceed_with_update: risky_updates | length == 0
scan_date: ansible_date_time.iso8601
report_file: output_file