├── .ansible-lint ├── .pre-commit-config.yaml ├── .travis.yml ├── .yamllint ├── LICENSE ├── README.md ├── defaults └── main.yml ├── handlers └── main.yml ├── meta └── main.yml ├── molecule └── default │ ├── converge.yml │ ├── custom_tasks │ ├── after_pm_centostest_custom_tasks.yml │ ├── after_pm_ubuntutest_custom_tasks.yml │ ├── before_pm_centostest_custom_tasks.yml │ ├── before_pm_ubuntutest_custom_tasks.yml │ ├── pm_after_update_tasks_file.yml │ └── pm_before_update_tasks_file.yml │ ├── molecule.yml │ └── verify.yml ├── tasks ├── check_pm_after_update_custom_tasks_file.yml ├── check_pm_before_update_custom_tasks_file.yml ├── empty.yml ├── facts.yml ├── linux_tasks │ ├── debian_ubuntu.yml │ └── redhat_centos.yml ├── main.yml ├── patch.yml ├── reboot.yml └── setup.yml ├── templates └── pm.fact.j2 ├── tests ├── custom_tasks │ ├── after_pm_localhost_custom_tasks.yml │ ├── before_pm_localhost_custom_tasks.yml │ ├── pm_after_update_tasks_file.yml │ └── pm_before_update_tasks_file.yml ├── inventory └── test.yml └── vars └── main.yml /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | exclude_paths: 3 | - ./.gitlab-ci.yml 4 | - ./.pre-commit-config.yaml 5 | parseable: true 6 | skip_list: 7 | - '204' 8 | - '301' 9 | - '403' # Package installs should not use latest 10 | tags: 11 | - behaviour 12 | - bug 13 | - deprecated 14 | - formatting 15 | - idempotency 16 | - oddity 17 | - readability 18 | - repeatability 19 | - resources 20 | - safety 21 | use_default_rules: true 22 | verbosity: 1 23 | ... 24 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v2.3.0 5 | hooks: 6 | - id: check-yaml 7 | - id: end-of-file-fixer 8 | - id: trailing-whitespace 9 | - repo: https://github.com/ansible/ansible-lint.git 10 | rev: v4.2.0 11 | hooks: 12 | - id: ansible-lint 13 | files: \.(yaml|yml)$ 14 | exclude: .gitlab-ci.yml 15 | ... 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: python 3 | python: "2.7" 4 | 5 | # Use the new container infrastructure 6 | sudo: false 7 | 8 | # Install ansible 9 | addons: 10 | apt: 11 | packages: 12 | - python-pip 13 | 14 | install: 15 | # Install ansible 16 | - pip install ansible 17 | 18 | # Check ansible version 19 | - ansible --version 20 | 21 | # Create ansible.cfg with correct roles_path 22 | - printf '[defaults]\nroles_path=../' >ansible.cfg 23 | 24 | script: 25 | # Basic role syntax check 26 | - ansible-playbook tests/test.yml -i tests/inventory --syntax-check 27 | 28 | notifications: 29 | webhooks: https://galaxy.ansible.com/api/v1/notifications/ 30 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | rules: 4 | line-length: 5 | max: 160 6 | level: warning 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jeff Geerling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PatchManagement 2 | =============== 3 | 4 | Because a system update is rarely just a `yum update -y`, `patchmanagement` is an Ansible role to easily manage the update process and all things around it for servers under RedHat, CentOS, Debian or Ubuntu OS. 5 | 6 | During a patch management, if you want: 7 | 8 | * to set the target in a maintenance mode in your supervision 9 | * to send a message at the begining or the end of the process 10 | * react when falling 11 | * etc. 12 | 13 | this role will make your life easier. 14 | 15 | In all case, remember that you should never connect to a server to perform its upgrade. 16 | 17 | Requirements 18 | ------------ 19 | 20 | Nothing special. 21 | 22 | Features 23 | -------- 24 | 25 | * Take some notes about the system - ip address and routes, running process, mountpoints (for troubleshooting ! Don't forget it !) 26 | * Play custom tasks for all servers (see `pm_before_update_tasks_file` var) 27 | * Play custom tasks for the target server (see playbook example chapter) 28 | 29 | Our main goal : 30 | 31 | * Patch the server with apt or yum 32 | 33 | And after : 34 | * Set Ansible facts with the current date and if wanted an env var. 35 | * Play custom tasks for the target server (see playbook example chapter) 36 | * Play custom tasks for all servers (see `pm_after_update_tasks_file` var) 37 | 38 | Role Variables 39 | -------------- 40 | 41 | * `pm_restart_after_update`: default `true` 42 | 43 | Will the target restart after the PM. This ensure the last kernel is running and that the server and its services are able to reboot. Consider doing it regularly. 44 | 45 | * `pm_logpath`: default `/etc/ansible/facts.d/PM.log` 46 | 47 | Where to store the date of PM that are successfull or failed. 48 | 49 | * `pm_date_format`: default `"{{ ansible_date_time.date }}-{{ ansible_date_time.time }}"` 50 | 51 | How to format the date in fact and log path ? 52 | 53 | * `pm_fact_name`: default `pm` 54 | 55 | What is the fact name we want ? 56 | 57 | * `pm_set_env_variable`: default `true` 58 | * `pm_env_file_path`: default `/etc/profile.d/last_pm_date.sh` 59 | 60 | Do we set an env variable with last_pm date and where to store the script ? 61 | 62 | * `pm_manage_yum_clean_all`: default `true` 63 | 64 | Launch a `yum clean all` before updating 65 | You should set to `false` if, for example, you have ever download the RPMs 66 | 67 | * `pm_manage_apt_clean`: `true` 68 | 69 | Launch a apt clean before updating 70 | 71 | * `pm_manage_apt_autoremove`: `true` 72 | 73 | Autoremove deb packages 74 | 75 | * `pm_apt_verbose_package_list: `false` 76 | 77 | Print apt result list 78 | 79 | * `pm_before_update_tasks_file`: 80 | 81 | For example `custom_tasks/pm_before_update_tasks_file.yml`. You can configure the role to launch this tasks file before updating the server. Every task in the targeted file will be played. 82 | 83 | Consider placing in some tasks like sending a message, taking a snapshot, setting a maintenance mode, etc. 84 | 85 | * `pm_after_update_tasks_file`: 86 | 87 | For example `custom_tasks/pm_after_update_tasks_file.yml`. You can configure the role to launch this tasks file before updating the server. Every task in the targeted file will be played. 88 | 89 | Example Playbook 90 | ---------------- 91 | 92 | Configuring a playbook is quiet easy as you can see: 93 | 94 | - name: Start a Patch Management 95 | hosts: servers 96 | vars: 97 | pm_before_update_tasks_file: custom_tasks/ pm_before_update_tasks_file.yml 98 | pm_after_update_tasks_file: custom_tasks/pm_after_update_tasks_file.yml 99 | tasks: 100 | - name: "Include patchmanagement" 101 | include_role: 102 | name: "alemorvan.patchmanagement" 103 | 104 | In addition to this playbook, you can create 2 other tasks files by servers that will be play before and after upgrading them. It is usefull for example to remove node from a LB, flushing caches, restarting services, etc. 105 | 106 | Unlike variables `pm_before_update_tasks_file` and `pm_after_update_tasks_file` (that target tasks files which perform actions for all targets), these files focus on actions specifics to a single server. 107 | 108 | In the playbook directory, create the directory `custom_tasks` and files named `before_pm_{{ inventory_hostname_short }}_custom_tasks.yml` and `after_pm_{{ inventory_hostname_short }}_custom_tasks.yml`. 109 | 110 | - name: Play a custom tasks for this server define thanks to the after_pm_{{ inventory_hostname_short }}_custom_tasks.yml file. 111 | debug: 112 | msg: "This tasks is a custom tasks define in after_pm_{{ inventory_hostname_short }}_custom_tasks.yml file" 113 | 114 | Molecule testing 115 | ---------------- 116 | 117 | You can test this role via molecule and docker : 118 | 119 | $ molecule test 120 | 121 | See `molecule/default/converge.yml` for a good example on how to use this role. 122 | 123 | License 124 | ------- 125 | 126 | MIT 127 | 128 | Author Information 129 | ------------------ 130 | 131 | This role was originally written by Antoine Le Morvan for Vivalto Sante based on the work of Nicolas Martin and the Ansible Team at Claranet France / BU RMP (Ismaël Ouattara and EliE Deloumeau). 132 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Will we restart the server after the PM ? 3 | pm_restart_after_update: true 4 | # Where to store the date of PM that are successfull 5 | pm_logpath: /etc/ansible/facts.d/PM.log 6 | # What is the fact name we want ? 7 | pm_fact_name: pm 8 | # What is the date format ? 9 | pm_date_format: "{{ ansible_date_time.date }}-{{ ansible_date_time.time }}" 10 | # Do we set an env variable with last_pm date ? 11 | pm_set_env_variable: true 12 | pm_env_file_path: /etc/profile.d/last_pm_date.sh 13 | # Launch a yum clean all before updating 14 | # You should set to false if, for example, you have ever download the RPMs 15 | pm_manage_yum_clean_all: true 16 | # Idem for apt 17 | pm_manage_apt_clean: true 18 | pm_manage_apt_autoremove: true 19 | # Print apt result 20 | pm_apt_verbose_package_list: false 21 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for patchmanagement 3 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Antoine Le Morvan 4 | description: Ansible role to manage patchs managements on Linux for huge infrastructure with custom tasks per server basis or for all servers. 5 | company: 6 | 7 | # If the issue tracker for your role is not on github, uncomment the 8 | # next line and provide a value 9 | # issue_tracker_url: http://example.com/issue/tracker 10 | 11 | # Choose a valid license ID from https://spdx.org - some suggested licenses: 12 | # - BSD-3-Clause (default) 13 | # - MIT 14 | # - GPL-2.0-or-later 15 | # - GPL-3.0-only 16 | # - Apache-2.0 17 | # - CC-BY-4.0 18 | license: license MIT 19 | 20 | min_ansible_version: 2.1 21 | 22 | # If this a Container Enabled role, provide the minimum Ansible Container version. 23 | # min_ansible_container_version: 24 | 25 | # 26 | # Provide a list of supported platforms, and for each platform a list of versions. 27 | # If you don't wish to enumerate all versions for a particular platform, use 'all'. 28 | # To view available platforms and versions (or releases), visit: 29 | # https://galaxy.ansible.com/api/v1/platforms/ 30 | # 31 | platforms: 32 | - name: EL 33 | versions: 34 | - 7 35 | - 8 36 | - name: Ubuntu 37 | versions: 38 | - bionic 39 | - focal 40 | - name: Debian 41 | versions: 42 | - jessie 43 | # - 25 44 | # - name: SomePlatform 45 | # versions: 46 | # - all 47 | # - 1.0 48 | # - 7 49 | # - 99.99 50 | 51 | galaxy_tags: [patchmanagement, patch, update, upgrade, yum, apt, system, rhel, ubuntu] 52 | # List tags for your role here, one per line. A tag is a keyword that describes 53 | # and categorizes the role. Users find roles by searching for tags. Be sure to 54 | # remove the '[]' above, if you add tags to this list. 55 | # 56 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 57 | # Maximum 20 tags per role. 58 | 59 | dependencies: [] 60 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 61 | # if you add dependencies to this list. 62 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | serial: 1 5 | vars: 6 | pm_before_update_tasks_file: custom_tasks/pm_before_update_tasks_file.yml 7 | pm_after_update_tasks_file: custom_tasks/pm_after_update_tasks_file.yml 8 | tasks: 9 | - name: "Include patchmanagement" 10 | include_role: 11 | name: "patchmanagement" 12 | -------------------------------------------------------------------------------- /molecule/default/custom_tasks/after_pm_centostest_custom_tasks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play a custom tasks for this server define thanks to the after_pm_{{ inventory_hostname_short }}_custom_tasks.yml file. 3 | debug: 4 | msg: "This tasks is a custom tasks define in after_pm_{{ inventory_hostname_short }}_custom_tasks.yml file" 5 | -------------------------------------------------------------------------------- /molecule/default/custom_tasks/after_pm_ubuntutest_custom_tasks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play a custom tasks for this server define thanks to the after_pm_{{ inventory_hostname_short }}_custom_tasks.yml file. 3 | debug: 4 | msg: "This tasks is a custom tasks define in after_pm_{{ inventory_hostname_short }}_custom_tasks.yml file" 5 | -------------------------------------------------------------------------------- /molecule/default/custom_tasks/before_pm_centostest_custom_tasks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play a custom tasks for this server define thanks to the before_pm_{{ inventory_hostname_short }}_custom_tasks.yml file. 3 | debug: 4 | msg: "This tasks is a custom tasks define in before_pm_{{ inventory_hostname_short }}_custom_tasks.yml file" 5 | -------------------------------------------------------------------------------- /molecule/default/custom_tasks/before_pm_ubuntutest_custom_tasks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play a custom tasks for this server define thanks to the before_pm_{{ inventory_hostname_short }}_custom_tasks.yml file. 3 | debug: 4 | msg: "This tasks is a custom tasks define in before_pm_{{ inventory_hostname_short }}_custom_tasks.yml file" 5 | -------------------------------------------------------------------------------- /molecule/default/custom_tasks/pm_after_update_tasks_file.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play a custom tasks for this playbook define thanks to pm_after_update_tasks_file var. 3 | debug: 4 | msg: "This tasks is a custom tasks define in {{ pm_before_update_tasks_file }} file" 5 | -------------------------------------------------------------------------------- /molecule/default/custom_tasks/pm_before_update_tasks_file.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play a custom tasks for this playbook define thanks to pm_before_update_tasks_file var. 3 | debug: 4 | msg: "This tasks is a custom tasks define in {{ pm_before_update_tasks_file }} file" 5 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | driver: 5 | name: docker 6 | platforms: 7 | - name: centostest 8 | image: docker.io/pycontribs/centos:8 9 | pre_build_image: true 10 | - name: ubuntutest 11 | image: docker.io/pycontribs/ubuntu:latest 12 | pre_build_image: true 13 | provisioner: 14 | name: ansible 15 | verifier: 16 | name: ansible 17 | lint: | 18 | set -e 19 | yamllint . 20 | ansible-lint 21 | -------------------------------------------------------------------------------- /molecule/default/verify.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is an example playbook to execute Ansible tests. 3 | 4 | - name: Verify 5 | hosts: all 6 | gather_facts: false 7 | tasks: 8 | - name: Example assertion 9 | assert: 10 | that: true 11 | 12 | - name: display multiple file contents 13 | debug: var=item 14 | with_file: 15 | - /root/patchmanagement/patchmanagement_{{ lookup('pipe', 'date +%Y-%m-%d') }}.log 16 | - /etc/ansible/facts.d/{{ pm_fact_name }}.fact 17 | - "{{ pm_logpath }}" 18 | -------------------------------------------------------------------------------- /tasks/check_pm_after_update_custom_tasks_file.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Check if a custom tasks file exist for this hostname 3 | - name: "AFTER UPDATE CUSTOM TASKS | Does {{ playbook_dir }}/custom_tasks/after_pm_{{ inventory_hostname_short }}_custom_tasks.yml playbook exist ?" 4 | stat: 5 | path: "{{ playbook_dir }}/custom_tasks/after_pm_{{ inventory_hostname_short }}_custom_tasks.yml" 6 | register: custom_after 7 | delegate_to: localhost 8 | 9 | # Play this file's tasks if it exists 10 | - name: "AFTER UPDATE CUSTOM TASKS | {{ playbook_dir }}/custom_tasks/after_pm_{{ inventory_hostname_short }}_custom_tasks.yml exists" 11 | include_tasks: "{{ playbook_dir }}/custom_tasks/after_pm_{{ inventory_hostname_short }}_custom_tasks.yml" 12 | when: 13 | - custom_after is defined 14 | - custom_after.stat.exists 15 | -------------------------------------------------------------------------------- /tasks/check_pm_before_update_custom_tasks_file.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Check if a custom tasks file exist for this hostname 3 | - name: "BEFORE UPDATE CUSTOM TASKS | Does {{ playbook_dir }}/custom_tasks/before_pm_{{ inventory_hostname_short }}_custom_tasks.yml file exist ?" 4 | stat: 5 | path: "{{ playbook_dir }}/custom_tasks/before_pm_{{ inventory_hostname_short }}_custom_tasks.yml" 6 | register: custom_before 7 | delegate_to: localhost 8 | 9 | # Play this file's tasks if it exists 10 | - name: "BEFORE UPDATE CUSTOM TASKS | {{ playbook_dir }}/custom_tasks/before_pm_{{ inventory_hostname_short }}_custom_tasks.yml exists" 11 | include_tasks: "{{ playbook_dir }}/custom_tasks/before_pm_{{ inventory_hostname_short }}_custom_tasks.yml" 12 | when: 13 | - custom_before is defined 14 | - custom_before.stat.exists 15 | -------------------------------------------------------------------------------- /tasks/empty.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This file is intentionally left empty 3 | # and it is used in when you don't need to execute any custom tasks 4 | -------------------------------------------------------------------------------- /tasks/facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "FACTS | Create ansible config directory" 3 | file: 4 | path: "/etc/ansible/" 5 | state: "directory" 6 | owner: root 7 | group: root 8 | mode: 0755 9 | become: true 10 | become_user: root 11 | 12 | - name: "FACTS | Create custom fact directory" 13 | file: 14 | path: "/etc/ansible/facts.d" 15 | state: "directory" 16 | owner: root 17 | group: root 18 | mode: 0755 19 | become: true 20 | become_user: root 21 | 22 | - name: "FACTS | Insert fact file" 23 | template: 24 | src: templates/pm.fact.j2 25 | dest: /etc/ansible/facts.d/{{ pm_fact_name }}.fact 26 | mode: '0755' 27 | owner: root 28 | group: root 29 | become: true 30 | become_user: root 31 | 32 | # If we are still alive, PM can be considered as successfull 33 | - name: "FACTS | Save date of last PM" 34 | lineinfile: 35 | path: "{{ pm_logpath }}" 36 | line: "{{ pm_date }} success" 37 | state: present 38 | create: true 39 | changed_when: false # For idempotence 40 | become: true 41 | become_user: root 42 | 43 | - name: "FACTS | Set ENV variable with last PM" 44 | copy: 45 | content: | 46 | export LAST_PM={{ pm_date }} 47 | dest: "{{ pm_env_file_path }}" 48 | owner: root 49 | group: root 50 | mode: '0644' 51 | become: true 52 | become_user: root 53 | when: pm_set_env_variable|bool 54 | changed_when: false # For idempotence 55 | -------------------------------------------------------------------------------- /tasks/linux_tasks/debian_ubuntu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "INCLUDED | Debian & Ubuntu playbook" 3 | block: 4 | 5 | # Fix: check if snmpd.pid exists 6 | - name: 'CHECK | does snmpd.pid exist?' 7 | stat: 8 | path: /run/snmpd.pid 9 | register: snmpdpid 10 | 11 | - name: 'DEBIAN UBUNTU | fix wrong snmpd pid preventing snmpd from restarting' 12 | file: 13 | path: /run/snmpd.pid 14 | state: absent 15 | when: snmpdpid.stat.exists 16 | 17 | - name: 'DEBIAN UBUNTU | recreate correct snmpd.pid' 18 | shell: "pidof snmpd > /run/snmpd.pid" 19 | ignore_errors: true 20 | when: snmpdpid.stat.exists 21 | 22 | - name: 'DEBIAN UBUNTU | apt-get clean' 23 | command: apt-get clean warn=no 24 | when: pm_manage_apt_clean 25 | 26 | - name: 'DEBIAN UBUNTU | apt-get autoremove' 27 | apt: autoremove=yes 28 | when: pm_manage_apt_autoremove 29 | 30 | - name: 'DEBIAN UBUNTU | apt-get dist-upgrade' 31 | apt: upgrade=dist update_cache=yes 32 | register: update_resultat 33 | 34 | - name: 'DEBIAN UBUNTU | Print update result' 35 | debug: var=update_resultat.stdout_lines 36 | when: 37 | - update_resultat is defined 38 | - update_resultat | length 39 | - pm_apt_verbose_package_list|bool 40 | 41 | - name: 'DEBIAN UBUNTU | apt-get autoremove' 42 | apt: autoremove=yes 43 | 44 | rescue: 45 | - name: 'DEBIAN UBUNTU | Print update result' 46 | debug: var=update_resultat.stdout_lines 47 | when: 48 | - update_resultat is defined 49 | - update_resultat | length 50 | 51 | - name: "FAILED | Fail" 52 | fail: 53 | msg: "FAIL : something went wrong ..." 54 | 55 | become: true 56 | -------------------------------------------------------------------------------- /tasks/linux_tasks/redhat_centos.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "INCLUDED | RedHat & CentOS playbook" 3 | block: 4 | 5 | - name: 'RHEL CENTOS | yum clean all' 6 | command: yum clean all warn=no 7 | register: yum_clean_resultat 8 | when: pm_manage_yum_clean_all 9 | 10 | # Necessary to get package-cleanup 11 | - name: 'RHEL CENTOS | Ensure yum-utils is installed' 12 | yum: 13 | name: yum-utils 14 | state: installed 15 | 16 | - name: 'RHEL CENTOS | Remove old kernels' 17 | command: package-cleanup --oldkernels --count={{ keep_kernels_count | default(2) }} -y 18 | register: old_kernel_cleanup 19 | when: ansible_distribution == "CentOS" and ansible_distribution_major_version < '8' 20 | 21 | - name: 'RHEL CENTOS | Update rpm package with yum' 22 | yum: name=* state=latest update_cache=yes 23 | register: update_resultat 24 | environment: 25 | ACCEPT_EULA: 'y' 26 | 27 | rescue: 28 | - name: 'RHEL CENTOS | Print cleanup result' 29 | debug: var=update_resultat 30 | when: old_kernel_cleanup is defined and old_kernel_cleanup | length 31 | 32 | - name: 'RHEL CENTOS | Print update result' 33 | debug: var=update_resultat 34 | when: update_resultat is defined and update_resultat | length 35 | 36 | - name: 'RHEL CENTOS | Fail' 37 | fail: 38 | msg: "FAIL : something went wrong ..." 39 | 40 | # We will play this tasks only if the PM is failed 41 | - name: "FAILED | Save date of last PM failed" 42 | lineinfile: 43 | path: "{{ pm_logpath }}" 44 | line: "{{ pm_date }} failed" 45 | state: present 46 | create: true 47 | 48 | become: true 49 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for patchmanagement 3 | - name: "MAIN | Linux Patch Management Job" 4 | debug: msg="Start {{ inventory_hostname_short }} patch management" 5 | 6 | - name: "MAIN | Gather facts..." 7 | setup: 8 | gather_subset: 9 | - 'all' 10 | 11 | # Everything that should be done before modifying the system 12 | - name: "MAIN | Include setup tasks" 13 | include_tasks: setup.yml 14 | 15 | # Everything you need to do before starting the PM 16 | # eg: sending a notification on mattermost, slack or any other system 17 | - name: "MAIN | Include before update tasks file if defined" 18 | include_tasks: "{{ pm_before_update_tasks_file | default('empty.yml') }}" 19 | 20 | # Everything to be done per computer basis 21 | # like stopping services, taking snapshots, etc. 22 | - name: "MAIN | Include before update custom tasks file if defined" 23 | include_tasks: check_pm_before_update_custom_tasks_file.yml 24 | 25 | # Now we do the main job 26 | - name: "MAIN | We can now patch" 27 | include_tasks: patch.yml 28 | 29 | # Let's restart the server 30 | - name: "MAIN | We can now reboot" 31 | include_tasks: reboot.yml 32 | when: pm_restart_after_update|bool 33 | 34 | # And set some facts 35 | - name: "MAIN | Set some facts and vars" 36 | include_tasks: facts.yml 37 | 38 | # Everything to be done per computer basis 39 | # like starting services, flushing caches, etc. 40 | - name: "MAIN | Include after update tasks file if defined" 41 | include_tasks: check_pm_after_update_custom_tasks_file.yml 42 | 43 | # Everything you need to do before starting the PM 44 | # eg: sending a notification on mattermost, slack or any other system* 45 | - name: "MAIN | Include after update custom tasks file if defined" 46 | include_tasks: "{{ pm_after_update_tasks_file | default('empty.yml') }}" 47 | -------------------------------------------------------------------------------- /tasks/patch.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "PATCH | Tasks depends on distribution" 3 | debug: 4 | var: ansible_distribution 5 | 6 | # When the target runs under CENTOS / REDHAT 7 | - name: "PATCH | Include tasks for CentOS & RedHat tasks" 8 | include_tasks: "linux_tasks/redhat_centos.yml" 9 | when: ansible_distribution in rhel_distribs 10 | 11 | # When the target runs under Debian / Ubuntu 12 | - name: "PATCH | Inlude tasks for Debian & Ubuntu tasks" 13 | include_tasks: "linux_tasks/debian_ubuntu.yml" 14 | when: ansible_distribution in debian_distribs 15 | -------------------------------------------------------------------------------- /tasks/reboot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Reboot the target after the update 3 | - name: "REBOOT | Reboot triggered" 4 | reboot: 5 | msg: "Reboot initiated by Ansible. Related to Patch Management." 6 | reboot_timeout: 600 7 | test_command: "whoami" 8 | post_reboot_delay: 15 9 | changed_when: false 10 | become: true 11 | become_user: "root" 12 | tags: molecule-notest 13 | 14 | - name: "REBOOT | Ensure we are not in rescue mode" 15 | wait_for_connection: 16 | timeout: 120 17 | -------------------------------------------------------------------------------- /tasks/setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "SETUP | Keep starting time" 3 | set_fact: 4 | pm_date: "{{ pm_date_format }}" 5 | 6 | - name: "SETUP | Set log path" 7 | set_fact: 8 | pm_log_path: "/root/patchmanagement/patchmanagement_{{ pm_date|replace('/', '-') }}.log" 9 | 10 | # We take some notes about the system before running the patchmanagement 11 | - name: "SETUP | Create /root/patchmanagement" 12 | become: true 13 | file: 14 | path: /root/patchmanagement 15 | state: directory 16 | 17 | - name: "SETUP | Take some notes on the system's state" 18 | become: true 19 | shell: ip a > {{ pm_log_path }} && echo "" >> {{ pm_log_path }} && ip r >> {{ pm_log_path }} && echo "" >> {{ pm_log_path }} && ps auxfww >> {{ pm_log_path }} && echo "" >> {{ pm_log_path }} && mount >> {{ pm_log_path }} 20 | ignore_errors: true 21 | changed_when: false 22 | -------------------------------------------------------------------------------- /templates/pm.fact.j2: -------------------------------------------------------------------------------- 1 | #!{{ ansible_python.executable }} 2 | import json 3 | 4 | def render_data(data): 5 | return json.dumps(data) 6 | 7 | data = {} 8 | with open("{{ pm_logpath }}", "r") as f: 9 | for line in f.read().splitlines(): 10 | date, status = line.split(" ", 1) 11 | data[date] = status 12 | 13 | print(render_data(data)) 14 | -------------------------------------------------------------------------------- /tests/custom_tasks/after_pm_localhost_custom_tasks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play a custom tasks for this server define thanks to the after_pm_{{ inventory_hostname_short }}_custom_tasks.yml file. 3 | debug: 4 | msg: "This tasks is a custom tasks define in after_pm_{{ inventory_hostname_short }}_custom_tasks.yml file" 5 | -------------------------------------------------------------------------------- /tests/custom_tasks/before_pm_localhost_custom_tasks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play a custom tasks for this server define thanks to the before_pm_{{ inventory_hostname_short }}_custom_tasks.yml file. 3 | debug: 4 | msg: "This tasks is a custom tasks define in before_pm_{{ inventory_hostname_short }}_custom_tasks.yml file" 5 | -------------------------------------------------------------------------------- /tests/custom_tasks/pm_after_update_tasks_file.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play a custom tasks for this playbook define thanks to pm_after_update_tasks_file var. 3 | debug: 4 | msg: "This tasks is a custom tasks define in {{ pm_before_update_tasks_file }} file" 5 | -------------------------------------------------------------------------------- /tests/custom_tasks/pm_before_update_tasks_file.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play a custom tasks for this playbook define thanks to pm_before_update_tasks_file var. 3 | debug: 4 | msg: "This tasks is a custom tasks define in {{ pm_before_update_tasks_file }} file" 5 | -------------------------------------------------------------------------------- /tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - patchmanagement 6 | -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for patchmanagement 3 | rhel_distribs: 4 | - RedHat 5 | - Red Hat Enterprise Linux 6 | - CentOS 7 | - Rocky 8 | debian_distribs: 9 | - Ubuntu 10 | - Debian 11 | --------------------------------------------------------------------------------