diff --git a/roles/linux_patch/defaults/main.yml b/roles/linux_patch/defaults/main.yml index 0720b8f..94d2249 100644 --- a/roles/linux_patch/defaults/main.yml +++ b/roles/linux_patch/defaults/main.yml @@ -1,2 +1,6 @@ --- -# linux_patch default variables +patch_mode: "full" # full or security +auto_reboot: false # set true per client if reboots are approved +packages_updated: [] +packages_pre_patch: {} +packages_post_patch: {} diff --git a/roles/linux_patch/tasks/main.yml b/roles/linux_patch/tasks/main.yml index e7a8c64..03911dd 100644 --- a/roles/linux_patch/tasks/main.yml +++ b/roles/linux_patch/tasks/main.yml @@ -1,6 +1,131 @@ --- -# linux_patch tasks -# Implementation to follow -- name: Placeholder +- name: Gather package facts before patching + ansible.builtin.package_facts: + manager: auto + register: packages_before + +- name: Store pre-patch package versions + ansible.builtin.set_fact: + packages_pre_patch: "{{ ansible_facts.packages }}" + +- name: Check for available updates (Debian/Ubuntu) + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + when: ansible_os_family == "Debian" + changed_when: false + +- name: Get list of upgradable packages (Debian/Ubuntu) + ansible.builtin.shell: | + apt list --upgradable 2>/dev/null | grep -v "Listing..." | awk -F'/' '{print $1}' + register: upgradable_packages + changed_when: false + when: ansible_os_family == "Debian" + +- name: Get list of upgradable packages (RHEL/CentOS) + ansible.builtin.shell: | + dnf check-update --quiet | awk '{print $1}' | grep -v "^$" + register: upgradable_packages + changed_when: false + failed_when: upgradable_packages.rc not in [0, 100] + when: ansible_os_family == "RedHat" + +- name: Log packages to be updated ansible.builtin.debug: - msg: "linux_patch role - tasks to be implemented" + msg: "Packages to be updated on {{ inventory_hostname }}: {{ upgradable_packages.stdout_lines | length }} packages" + +- name: Perform full upgrade (Debian/Ubuntu) + ansible.builtin.apt: + upgrade: dist + autoremove: true + autoclean: true + register: apt_upgrade_result + when: + - ansible_os_family == "Debian" + - patch_mode == "full" or patch_mode == "security" + +- name: Perform security-only upgrade (RHEL/CentOS) + ansible.builtin.dnf: + name: "*" + state: latest + security: "{{ patch_mode == 'security' }}" + update_cache: true + register: dnf_upgrade_result + when: ansible_os_family == "RedHat" + +- name: Gather package facts after patching + ansible.builtin.package_facts: + manager: auto + +- name: Store post-patch package versions + ansible.builtin.set_fact: + packages_post_patch: "{{ ansible_facts.packages }}" + +- name: Calculate changed packages + ansible.builtin.set_fact: + packages_updated: >- + {{ + packages_post_patch | dict2items + | selectattr('key', 'in', packages_pre_patch) + | selectattr('value', '!=', packages_pre_patch[item.key] | default([])) + | list + | map(attribute='key') + | list + }} + loop: "{{ packages_post_patch | dict2items }}" + when: false + +- name: Build packages updated list + ansible.builtin.set_fact: + packages_updated: >- + {%- set updated = [] -%} + {%- for pkg, details in packages_post_patch.items() -%} + {%- if pkg in packages_pre_patch -%} + {%- if details[0].version != packages_pre_patch[pkg][0].version -%} + {%- set _ = updated.append({ + 'name': pkg, + 'version_before': packages_pre_patch[pkg][0].version, + 'version_after': details[0].version, + 'type': 'updated' + }) -%} + {%- endif -%} + {%- else -%} + {%- set _ = updated.append({ + 'name': pkg, + 'version_before': 'not installed', + 'version_after': details[0].version, + 'type': 'new' + }) -%} + {%- endif -%} + {%- endfor -%} + {{ updated }} + +- name: Log updated packages + ansible.builtin.debug: + msg: "Updated: {{ item.name }} {{ item.version_before }} -> {{ item.version_after }}" + loop: "{{ packages_updated }}" + +- name: Check if reboot is required after patching (Debian/Ubuntu) + ansible.builtin.stat: + path: /var/run/reboot-required + register: reboot_required_post + when: ansible_os_family == "Debian" + +- name: Update reboot required fact + ansible.builtin.set_fact: + host_reboot_required: "{{ reboot_required_post.stat.exists | default(false) }}" + when: ansible_os_family == "Debian" + +- name: Reboot if required and auto_reboot is enabled + ansible.builtin.reboot: + reboot_timeout: 300 + pre_reboot_delay: 10 + post_reboot_delay: 30 + msg: "Rebooting after patch run — initiated by Ansible" + when: + - host_reboot_required | bool + - auto_reboot | bool + +- name: Patching complete + ansible.builtin.debug: + msg: "Patching complete on {{ inventory_hostname }} — {{ packages_updated | length }} packages updated"