1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2019-2020, Andrew Klaus <andrewklaus@gmail.com> 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import (absolute_import, division, print_function) 8__metaclass__ = type 9 10DOCUMENTATION = r''' 11--- 12module: syspatch 13 14short_description: Manage OpenBSD system patches 15 16 17description: 18 - "Manage OpenBSD system patches using syspatch." 19 20options: 21 revert: 22 description: 23 - Revert system patches. 24 type: str 25 choices: [ all, one ] 26 27author: 28 - Andrew Klaus (@precurse) 29''' 30 31EXAMPLES = ''' 32- name: Apply all available system patches 33 community.general.syspatch: 34 35- name: Revert last patch 36 community.general.syspatch: 37 revert: one 38 39- name: Revert all patches 40 community.general.syspatch: 41 revert: all 42 43# NOTE: You can reboot automatically if a patch requires it: 44- name: Apply all patches and store result 45 community.general.syspatch: 46 register: syspatch 47 48- name: Reboot if patch requires it 49 ansible.builtin.reboot: 50 when: syspatch.reboot_needed 51''' 52 53RETURN = r''' 54rc: 55 description: The command return code (0 means success) 56 returned: always 57 type: int 58stdout: 59 description: syspatch standard output. 60 returned: always 61 type: str 62 sample: "001_rip6cksum" 63stderr: 64 description: syspatch standard error. 65 returned: always 66 type: str 67 sample: "syspatch: need root privileges" 68reboot_needed: 69 description: Whether or not a reboot is required after an update. 70 returned: always 71 type: bool 72 sample: True 73''' 74 75from ansible.module_utils.basic import AnsibleModule 76 77 78def run_module(): 79 # define available arguments/parameters a user can pass to the module 80 module_args = dict( 81 revert=dict(type='str', choices=['all', 'one']) 82 ) 83 84 module = AnsibleModule( 85 argument_spec=module_args, 86 supports_check_mode=True, 87 ) 88 89 result = syspatch_run(module) 90 91 module.exit_json(**result) 92 93 94def syspatch_run(module): 95 cmd = module.get_bin_path('syspatch', True) 96 changed = False 97 reboot_needed = False 98 warnings = [] 99 100 # Set safe defaults for run_flag and check_flag 101 run_flag = ['-c'] 102 check_flag = ['-c'] 103 if module.params['revert']: 104 check_flag = ['-l'] 105 106 if module.params['revert'] == 'all': 107 run_flag = ['-R'] 108 else: 109 run_flag = ['-r'] 110 else: 111 check_flag = ['-c'] 112 run_flag = [] 113 114 # Run check command 115 rc, out, err = module.run_command([cmd] + check_flag) 116 117 if rc != 0: 118 module.fail_json(msg="Command %s failed rc=%d, out=%s, err=%s" % (cmd, rc, out, err)) 119 120 if len(out) > 0: 121 # Changes pending 122 change_pending = True 123 else: 124 # No changes pending 125 change_pending = False 126 127 if module.check_mode: 128 changed = change_pending 129 elif change_pending: 130 rc, out, err = module.run_command([cmd] + run_flag) 131 132 # Workaround syspatch ln bug: 133 # http://openbsd-archive.7691.n7.nabble.com/Warning-applying-latest-syspatch-td354250.html 134 if rc != 0 and err != 'ln: /usr/X11R6/bin/X: No such file or directory\n': 135 module.fail_json(msg="Command %s failed rc=%d, out=%s, err=%s" % (cmd, rc, out, err)) 136 elif out.lower().find('create unique kernel') >= 0: 137 # Kernel update applied 138 reboot_needed = True 139 elif out.lower().find('syspatch updated itself') >= 0: 140 warnings.append('Syspatch was updated. Please run syspatch again.') 141 142 # If no stdout, then warn user 143 if len(out) == 0: 144 warnings.append('syspatch had suggested changes, but stdout was empty.') 145 146 changed = True 147 else: 148 changed = False 149 150 return dict( 151 changed=changed, 152 reboot_needed=reboot_needed, 153 rc=rc, 154 stderr=err, 155 stdout=out, 156 warnings=warnings 157 ) 158 159 160def main(): 161 run_module() 162 163 164if __name__ == '__main__': 165 main() 166