#!/usr/bin/python # Copyright (c) 2012, Boyd Adamson # # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import annotations DOCUMENTATION = r""" module: svr4pkg short_description: Manage Solaris SVR4 packages description: - Manages SVR4 packages on Solaris 10 and 11. - These were the native packages on Solaris <= 10 and are available as a legacy feature in Solaris 11. - Note that this is a very basic packaging system. It does not enforce dependencies on install or remove. author: "Boyd Adamson (@brontitall)" extends_documentation_fragment: - community.general.attributes attributes: check_mode: support: full diff_mode: support: none options: name: description: - Package name, for example V(SUNWcsr). required: true type: str state: description: - Whether to install (V(present)), or remove (V(absent)) a package. - If the package is to be installed, then O(src) is required. - The SVR4 package system does not provide an upgrade operation. You need to uninstall the old, then install the new package. required: true choices: ["present", "absent"] type: str src: description: - Specifies the location to install the package from. Required when O(state=present). - "Can be any path acceptable to the C(pkgadd) command's C(-d) option. For example: V(somefile.pkg), V(/dir/with/pkgs), V(http://server/mypkgs.pkg)." - If using a file or directory, they must already be accessible by the host. See the M(ansible.builtin.copy) module for a way to get them there. type: str proxy: description: - HTTP[s] proxy to be used if O(src) is a URL. type: str response_file: description: - Specifies the location of a response file to be used if package expects input on install. type: str zone: description: - Whether to install the package only in the current zone, or install it into all zones. - The installation into all zones works only if you are working with the global zone. default: "all" choices: ["current", "all"] type: str category: description: - Install/Remove category instead of a single package. type: bool default: false """ EXAMPLES = r""" - name: Install a package from an already copied file community.general.svr4pkg: name: CSWcommon src: /tmp/cswpkgs.pkg state: present - name: Install a package directly from an http site community.general.svr4pkg: name: CSWpkgutil src: 'http://get.opencsw.org/now' state: present zone: current - name: Install a package with a response file community.general.svr4pkg: name: CSWggrep src: /tmp/third-party.pkg response_file: /tmp/ggrep.response state: present - name: Ensure that a package is not installed community.general.svr4pkg: name: SUNWgnome-sound-recorder state: absent - name: Ensure that a category is not installed community.general.svr4pkg: name: FIREFOX state: absent category: true """ import os import tempfile from ansible.module_utils.basic import AnsibleModule def package_installed(module, name, category): cmd = [module.get_bin_path("pkginfo", True), "-q"] if category: cmd.append("-c") cmd.append(name) rc, out, err = module.run_command(cmd) return rc == 0 def create_admin_file(): (desc, filename) = tempfile.mkstemp(prefix="ansible_svr4pkg", text=True) fullauto = b""" mail= instance=unique partial=nocheck runlevel=quit idepend=nocheck rdepend=nocheck space=quit setuid=nocheck conflict=nocheck action=nocheck networktimeout=60 networkretries=3 authentication=quit keystore=/var/sadm/security proxy= basedir=default """ os.write(desc, fullauto) os.close(desc) return filename def run_command(module, cmd): progname = cmd[0] cmd[0] = module.get_bin_path(progname, True) return module.run_command(cmd) def package_install(module, name, src, proxy, response_file, zone, category): adminfile = create_admin_file() cmd = ["pkgadd", "-n"] if zone == "current": cmd += ["-G"] cmd += ["-a", adminfile, "-d", src] if proxy is not None: cmd += ["-x", proxy] if response_file is not None: cmd += ["-r", response_file] if category: cmd += ["-Y"] cmd.append(name) (rc, out, err) = run_command(module, cmd) os.unlink(adminfile) return (rc, out, err) def package_uninstall(module, name, src, category): adminfile = create_admin_file() if category: cmd = ["pkgrm", "-na", adminfile, "-Y", name] else: cmd = ["pkgrm", "-na", adminfile, name] (rc, out, err) = run_command(module, cmd) os.unlink(adminfile) return (rc, out, err) def main(): module = AnsibleModule( argument_spec=dict( name=dict(required=True), state=dict(required=True, choices=["present", "absent"]), src=dict(), proxy=dict(), response_file=dict(), zone=dict(default="all", choices=["current", "all"]), category=dict(default=False, type="bool"), ), supports_check_mode=True, ) state = module.params["state"] name = module.params["name"] src = module.params["src"] proxy = module.params["proxy"] response_file = module.params["response_file"] zone = module.params["zone"] category = module.params["category"] rc = None out = "" err = "" result = {} result["name"] = name result["state"] = state if state == "present": if src is None: module.fail_json(name=name, msg="src is required when state=present") if not package_installed(module, name, category): if module.check_mode: module.exit_json(changed=True) (rc, out, err) = package_install(module, name, src, proxy, response_file, zone, category) # Stdout is normally empty but for some packages can be # very long and is not often useful if len(out) > 75: out = f"{out[:75]}..." elif state == "absent": if package_installed(module, name, category): if module.check_mode: module.exit_json(changed=True) (rc, out, err) = package_uninstall(module, name, src, category) out = out[:75] # Returncodes as per pkgadd(1m) # 0 Successful completion # 1 Fatal error. # 2 Warning. # 3 Interruption. # 4 Administration. # 5 Administration. Interaction is required. Do not use pkgadd -n. # 10 Reboot after installation of all packages. # 20 Reboot after installation of this package. # 99 (observed) pkgadd: ERROR: could not process datastream from if rc in (0, 2, 3, 10, 20): result["changed"] = True # no install nor uninstall, or failed else: result["changed"] = False # rc will be none when the package already was installed and no action took place # Only return failed=False when the returncode is known to be good as there may be more # undocumented failure return codes if rc not in (None, 0, 2, 10, 20): result["failed"] = True else: result["failed"] = False if out: result["stdout"] = out if err: result["stderr"] = err module.exit_json(**result) if __name__ == "__main__": main()