1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# (c) 2013, Jimmy Tang <jcftang@gmail.com> 5# Based on okpg (Patrick Pelletier <pp.pelletier@gmail.com>), pacman 6# (Afterburn) and pkgin (Shaun Zinck) modules 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 = ''' 15--- 16module: macports 17author: "Jimmy Tang (@jcftang)" 18short_description: Package manager for MacPorts 19description: 20 - Manages MacPorts packages (ports) 21options: 22 name: 23 description: 24 - A list of port names. 25 aliases: ['port'] 26 type: list 27 elements: str 28 selfupdate: 29 description: 30 - Update Macports and the ports tree, either prior to installing ports or as a separate step. 31 - Equivalent to running C(port selfupdate). 32 aliases: ['update_cache', 'update_ports'] 33 default: "no" 34 type: bool 35 state: 36 description: 37 - Indicates the desired state of the port. 38 choices: [ 'present', 'absent', 'active', 'inactive', 'installed', 'removed'] 39 default: present 40 type: str 41 upgrade: 42 description: 43 - Upgrade all outdated ports, either prior to installing ports or as a separate step. 44 - Equivalent to running C(port upgrade outdated). 45 default: "no" 46 type: bool 47 variant: 48 description: 49 - A port variant specification. 50 - 'C(variant) is only supported with state: I(installed)/I(present).' 51 aliases: ['variants'] 52 type: str 53''' 54EXAMPLES = ''' 55- name: Install the foo port 56 community.general.macports: 57 name: foo 58 59- name: Install the universal, x11 variant of the foo port 60 community.general.macports: 61 name: foo 62 variant: +universal+x11 63 64- name: Install a list of ports 65 community.general.macports: 66 name: "{{ ports }}" 67 vars: 68 ports: 69 - foo 70 - foo-tools 71 72- name: Update Macports and the ports tree, then upgrade all outdated ports 73 community.general.macports: 74 selfupdate: yes 75 upgrade: yes 76 77- name: Update Macports and the ports tree, then install the foo port 78 community.general.macports: 79 name: foo 80 selfupdate: yes 81 82- name: Remove the foo port 83 community.general.macports: 84 name: foo 85 state: absent 86 87- name: Activate the foo port 88 community.general.macports: 89 name: foo 90 state: active 91 92- name: Deactivate the foo port 93 community.general.macports: 94 name: foo 95 state: inactive 96''' 97 98import re 99 100from ansible.module_utils.basic import AnsibleModule 101from ansible.module_utils.six.moves import shlex_quote 102 103 104def selfupdate(module, port_path): 105 """ Update Macports and the ports tree. """ 106 107 rc, out, err = module.run_command("%s -v selfupdate" % port_path) 108 109 if rc == 0: 110 updated = any( 111 re.search(r'Total number of ports parsed:\s+[^0]', s.strip()) or 112 re.search(r'Installing new Macports release', s.strip()) 113 for s in out.split('\n') 114 if s 115 ) 116 if updated: 117 changed = True 118 msg = "Macports updated successfully" 119 else: 120 changed = False 121 msg = "Macports already up-to-date" 122 123 return (changed, msg, out, err) 124 else: 125 module.fail_json(msg="Failed to update Macports", stdout=out, stderr=err) 126 127 128def upgrade(module, port_path): 129 """ Upgrade outdated ports. """ 130 131 rc, out, err = module.run_command("%s upgrade outdated" % port_path) 132 133 # rc is 1 when nothing to upgrade so check stdout first. 134 if out.strip() == "Nothing to upgrade.": 135 changed = False 136 msg = "Ports already upgraded" 137 return (changed, msg, out, err) 138 elif rc == 0: 139 changed = True 140 msg = "Outdated ports upgraded successfully" 141 return (changed, msg, out, err) 142 else: 143 module.fail_json(msg="Failed to upgrade outdated ports", stdout=out, stderr=err) 144 145 146def query_port(module, port_path, name, state="present"): 147 """ Returns whether a port is installed or not. """ 148 149 if state == "present": 150 151 rc, out, err = module.run_command([port_path, "-q", "installed", name]) 152 153 if rc == 0 and out.strip().startswith(name + " "): 154 return True 155 156 return False 157 158 elif state == "active": 159 160 rc, out, err = module.run_command([port_path, "-q", "installed", name]) 161 162 if rc == 0 and "(active)" in out: 163 return True 164 165 return False 166 167 168def remove_ports(module, port_path, ports, stdout, stderr): 169 """ Uninstalls one or more ports if installed. """ 170 171 remove_c = 0 172 # Using a for loop in case of error, we can report the port that failed 173 for port in ports: 174 # Query the port first, to see if we even need to remove 175 if not query_port(module, port_path, port): 176 continue 177 178 rc, out, err = module.run_command("%s uninstall %s" % (port_path, port)) 179 stdout += out 180 stderr += err 181 if query_port(module, port_path, port): 182 module.fail_json(msg="Failed to remove %s: %s" % (port, err), stdout=stdout, stderr=stderr) 183 184 remove_c += 1 185 186 if remove_c > 0: 187 188 module.exit_json(changed=True, msg="Removed %s port(s)" % remove_c, stdout=stdout, stderr=stderr) 189 190 module.exit_json(changed=False, msg="Port(s) already absent", stdout=stdout, stderr=stderr) 191 192 193def install_ports(module, port_path, ports, variant, stdout, stderr): 194 """ Installs one or more ports if not already installed. """ 195 196 install_c = 0 197 198 for port in ports: 199 if query_port(module, port_path, port): 200 continue 201 202 rc, out, err = module.run_command("%s install %s %s" % (port_path, port, variant)) 203 stdout += out 204 stderr += err 205 if not query_port(module, port_path, port): 206 module.fail_json(msg="Failed to install %s: %s" % (port, err), stdout=stdout, stderr=stderr) 207 208 install_c += 1 209 210 if install_c > 0: 211 module.exit_json(changed=True, msg="Installed %s port(s)" % (install_c), stdout=stdout, stderr=stderr) 212 213 module.exit_json(changed=False, msg="Port(s) already present", stdout=stdout, stderr=stderr) 214 215 216def activate_ports(module, port_path, ports, stdout, stderr): 217 """ Activate a port if it's inactive. """ 218 219 activate_c = 0 220 221 for port in ports: 222 if not query_port(module, port_path, port): 223 module.fail_json(msg="Failed to activate %s, port(s) not present" % (port), stdout=stdout, stderr=stderr) 224 225 if query_port(module, port_path, port, state="active"): 226 continue 227 228 rc, out, err = module.run_command("%s activate %s" % (port_path, port)) 229 stdout += out 230 stderr += err 231 232 if not query_port(module, port_path, port, state="active"): 233 module.fail_json(msg="Failed to activate %s: %s" % (port, err), stdout=stdout, stderr=stderr) 234 235 activate_c += 1 236 237 if activate_c > 0: 238 module.exit_json(changed=True, msg="Activated %s port(s)" % (activate_c), stdout=stdout, stderr=stderr) 239 240 module.exit_json(changed=False, msg="Port(s) already active", stdout=stdout, stderr=stderr) 241 242 243def deactivate_ports(module, port_path, ports, stdout, stderr): 244 """ Deactivate a port if it's active. """ 245 246 deactivated_c = 0 247 248 for port in ports: 249 if not query_port(module, port_path, port): 250 module.fail_json(msg="Failed to deactivate %s, port(s) not present" % (port), stdout=stdout, stderr=stderr) 251 252 if not query_port(module, port_path, port, state="active"): 253 continue 254 255 rc, out, err = module.run_command("%s deactivate %s" % (port_path, port)) 256 stdout += out 257 stderr += err 258 if query_port(module, port_path, port, state="active"): 259 module.fail_json(msg="Failed to deactivate %s: %s" % (port, err), stdout=stdout, stderr=stderr) 260 261 deactivated_c += 1 262 263 if deactivated_c > 0: 264 module.exit_json(changed=True, msg="Deactivated %s port(s)" % (deactivated_c), stdout=stdout, stderr=stderr) 265 266 module.exit_json(changed=False, msg="Port(s) already inactive", stdout=stdout, stderr=stderr) 267 268 269def main(): 270 module = AnsibleModule( 271 argument_spec=dict( 272 name=dict(type='list', elements='str', aliases=["port"]), 273 selfupdate=dict(aliases=["update_cache", "update_ports"], default=False, type='bool'), 274 state=dict(default="present", choices=["present", "installed", "absent", "removed", "active", "inactive"]), 275 upgrade=dict(default=False, type='bool'), 276 variant=dict(aliases=["variants"], default=None, type='str') 277 ) 278 ) 279 280 stdout = "" 281 stderr = "" 282 283 port_path = module.get_bin_path('port', True, ['/opt/local/bin']) 284 285 p = module.params 286 287 if p["selfupdate"]: 288 (changed, msg, out, err) = selfupdate(module, port_path) 289 stdout += out 290 stderr += err 291 if not (p["name"] or p["upgrade"]): 292 module.exit_json(changed=changed, msg=msg, stdout=stdout, stderr=stderr) 293 294 if p["upgrade"]: 295 (changed, msg, out, err) = upgrade(module, port_path) 296 stdout += out 297 stderr += err 298 if not p["name"]: 299 module.exit_json(changed=changed, msg=msg, stdout=stdout, stderr=stderr) 300 301 pkgs = p["name"] 302 303 variant = p["variant"] 304 305 if p["state"] in ["present", "installed"]: 306 install_ports(module, port_path, pkgs, variant, stdout, stderr) 307 308 elif p["state"] in ["absent", "removed"]: 309 remove_ports(module, port_path, pkgs, stdout, stderr) 310 311 elif p["state"] == "active": 312 activate_ports(module, port_path, pkgs, stdout, stderr) 313 314 elif p["state"] == "inactive": 315 deactivate_ports(module, port_path, pkgs, stdout, stderr) 316 317 318if __name__ == '__main__': 319 main() 320