1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2013, Alexander Winkler <mail () winkler-alexander.de> 5# based on svr4pkg by 6# Boyd Adamson <boyd () boydadamson.com> (2012) 7# 8# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 9 10from __future__ import absolute_import, division, print_function 11__metaclass__ = type 12 13 14DOCUMENTATION = r''' 15--- 16module: pkgutil 17short_description: OpenCSW package management on Solaris 18description: 19- This module installs, updates and removes packages from the OpenCSW project for Solaris. 20- Unlike the M(community.general.svr4pkg) module, it will resolve and download dependencies. 21- See U(https://www.opencsw.org/) for more information about the project. 22author: 23- Alexander Winkler (@dermute) 24- David Ponessa (@scathatheworm) 25options: 26 name: 27 description: 28 - The name of the package. 29 - When using C(state=latest), this can be C('*'), which updates all installed packages managed by pkgutil. 30 type: list 31 required: true 32 elements: str 33 aliases: [ pkg ] 34 site: 35 description: 36 - The repository path to install the package from. 37 - Its global definition is in C(/etc/opt/csw/pkgutil.conf). 38 required: false 39 type: str 40 state: 41 description: 42 - Whether to install (C(present)/C(installed)), or remove (C(absent)/C(removed)) packages. 43 - The upgrade (C(latest)) operation will update/install the packages to the latest version available. 44 type: str 45 required: true 46 choices: [ absent, installed, latest, present, removed ] 47 update_catalog: 48 description: 49 - If you always want to refresh your catalog from the mirror, even when it's not stale, set this to C(yes). 50 type: bool 51 default: no 52 force: 53 description: 54 - To allow the update process to downgrade packages to match what is present in the repository, set this to C(yes). 55 - This is useful for rolling back to stable from testing, or similar operations. 56 type: bool 57 default: false 58 version_added: 1.2.0 59notes: 60- In order to check the availability of packages, the catalog cache under C(/var/opt/csw/pkgutil) may be refreshed even in check mode. 61''' 62 63EXAMPLES = r''' 64- name: Install a package 65 community.general.pkgutil: 66 name: CSWcommon 67 state: present 68 69- name: Install a package from a specific repository 70 community.general.pkgutil: 71 name: CSWnrpe 72 site: ftp://myinternal.repo/opencsw/kiel 73 state: latest 74 75- name: Remove a package 76 community.general.pkgutil: 77 name: CSWtop 78 state: absent 79 80- name: Install several packages 81 community.general.pkgutil: 82 name: 83 - CSWsudo 84 - CSWtop 85 state: present 86 87- name: Update all packages 88 community.general.pkgutil: 89 name: '*' 90 state: latest 91 92- name: Update all packages and force versions to match latest in catalog 93 community.general.pkgutil: 94 name: '*' 95 state: latest 96 force: yes 97''' 98 99RETURN = r''' # ''' 100 101from ansible.module_utils.basic import AnsibleModule 102 103 104def packages_not_installed(module, names): 105 ''' Check if each package is installed and return list of the ones absent ''' 106 pkgs = [] 107 for pkg in names: 108 rc, out, err = run_command(module, ['pkginfo', '-q', pkg]) 109 if rc != 0: 110 pkgs.append(pkg) 111 return pkgs 112 113 114def packages_installed(module, names): 115 ''' Check if each package is installed and return list of the ones present ''' 116 pkgs = [] 117 for pkg in names: 118 if not pkg.startswith('CSW'): 119 continue 120 rc, out, err = run_command(module, ['pkginfo', '-q', pkg]) 121 if rc == 0: 122 pkgs.append(pkg) 123 return pkgs 124 125 126def packages_not_latest(module, names, site, update_catalog): 127 ''' Check status of each package and return list of the ones with an upgrade available ''' 128 cmd = ['pkgutil'] 129 if update_catalog: 130 cmd.append('-U') 131 cmd.append('-c') 132 if site is not None: 133 cmd.extend(['-t', site]) 134 if names != ['*']: 135 cmd.extend(names) 136 rc, out, err = run_command(module, cmd) 137 138 # Find packages in the catalog which are not up to date 139 packages = [] 140 for line in out.split('\n')[1:-1]: 141 if 'catalog' not in line and 'SAME' not in line: 142 packages.append(line.split(' ')[0]) 143 144 # Remove duplicates 145 return list(set(packages)) 146 147 148def run_command(module, cmd, **kwargs): 149 progname = cmd[0] 150 cmd[0] = module.get_bin_path(progname, True, ['/opt/csw/bin']) 151 return module.run_command(cmd, **kwargs) 152 153 154def package_install(module, state, pkgs, site, update_catalog, force): 155 cmd = ['pkgutil'] 156 if module.check_mode: 157 cmd.append('-n') 158 cmd.append('-iy') 159 if update_catalog: 160 cmd.append('-U') 161 if site is not None: 162 cmd.extend(['-t', site]) 163 if force: 164 cmd.append('-f') 165 cmd.extend(pkgs) 166 return run_command(module, cmd) 167 168 169def package_upgrade(module, pkgs, site, update_catalog, force): 170 cmd = ['pkgutil'] 171 if module.check_mode: 172 cmd.append('-n') 173 cmd.append('-uy') 174 if update_catalog: 175 cmd.append('-U') 176 if site is not None: 177 cmd.extend(['-t', site]) 178 if force: 179 cmd.append('-f') 180 cmd += pkgs 181 return run_command(module, cmd) 182 183 184def package_uninstall(module, pkgs): 185 cmd = ['pkgutil'] 186 if module.check_mode: 187 cmd.append('-n') 188 cmd.append('-ry') 189 cmd.extend(pkgs) 190 return run_command(module, cmd) 191 192 193def main(): 194 module = AnsibleModule( 195 argument_spec=dict( 196 name=dict(type='list', elements='str', required=True, aliases=['pkg']), 197 state=dict(type='str', required=True, choices=['absent', 'installed', 'latest', 'present', 'removed']), 198 site=dict(type='str'), 199 update_catalog=dict(type='bool', default=False), 200 force=dict(type='bool', default=False), 201 ), 202 supports_check_mode=True, 203 ) 204 name = module.params['name'] 205 state = module.params['state'] 206 site = module.params['site'] 207 update_catalog = module.params['update_catalog'] 208 force = module.params['force'] 209 210 rc = None 211 out = '' 212 err = '' 213 result = dict( 214 name=name, 215 state=state, 216 ) 217 218 if state in ['installed', 'present']: 219 # Fail with an explicit error when trying to "install" '*' 220 if name == ['*']: 221 module.fail_json(msg="Can not use 'state: present' with name: '*'") 222 223 # Build list of packages that are actually not installed from the ones requested 224 pkgs = packages_not_installed(module, name) 225 226 # If the package list is empty then all packages are already present 227 if pkgs == []: 228 module.exit_json(changed=False) 229 230 (rc, out, err) = package_install(module, state, pkgs, site, update_catalog, force) 231 if rc != 0: 232 module.fail_json(msg=(err or out)) 233 234 elif state in ['latest']: 235 # When using latest for * 236 if name == ['*']: 237 # Check for packages that are actually outdated 238 pkgs = packages_not_latest(module, name, site, update_catalog) 239 240 # If the package list comes up empty, everything is already up to date 241 if pkgs == []: 242 module.exit_json(changed=False) 243 244 # If there are packages to update, just empty the list and run the command without it 245 # pkgutil logic is to update all when run without packages names 246 pkgs = [] 247 (rc, out, err) = package_upgrade(module, pkgs, site, update_catalog, force) 248 if rc != 0: 249 module.fail_json(msg=(err or out)) 250 else: 251 # Build list of packages that are either outdated or not installed 252 pkgs = packages_not_installed(module, name) 253 pkgs += packages_not_latest(module, name, site, update_catalog) 254 255 # If the package list is empty that means all packages are installed and up to date 256 if pkgs == []: 257 module.exit_json(changed=False) 258 259 (rc, out, err) = package_upgrade(module, pkgs, site, update_catalog, force) 260 if rc != 0: 261 module.fail_json(msg=(err or out)) 262 263 elif state in ['absent', 'removed']: 264 # Build list of packages requested for removal that are actually present 265 pkgs = packages_installed(module, name) 266 267 # If the list is empty, no packages need to be removed 268 if pkgs == []: 269 module.exit_json(changed=False) 270 271 (rc, out, err) = package_uninstall(module, pkgs) 272 if rc != 0: 273 module.fail_json(msg=(err or out)) 274 275 if rc is None: 276 # pkgutil was not executed because the package was already present/absent/up to date 277 result['changed'] = False 278 elif rc == 0: 279 result['changed'] = True 280 else: 281 result['changed'] = False 282 result['failed'] = True 283 284 if out: 285 result['stdout'] = out 286 if err: 287 result['stderr'] = err 288 289 module.exit_json(**result) 290 291 292if __name__ == '__main__': 293 main() 294