Add Ansible-based maintenance scripts for infrastructure operations: - CVE scanner using NIST NVD database - Package update checker with OpenAI risk assessment - Docker cleanup playbook - Log archiver for rotated logs - Disk space analyzer Supports Ubuntu 20.04/22.04/24.04, Debian 11/12/13, and Alpine Linux
200 lines
7.7 KiB
YAML
200 lines
7.7 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 }}",
|
|
"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
|
|
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
|