#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2012, Boyd Adamson <boyd () boydadamson.com>
#
# 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 absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = '''
---
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 will 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 doesn't 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. (added in Ansible 1.4)
    required: false
    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.
    required: false
    default: "all"
    choices: ["current", "all"]
    type: str
  category:
    description:
      - Install/Remove category instead of a single package.
    required: false
    type: bool
    default: false
'''

EXAMPLES = '''
- 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(' '.join(cmd))
    if rc == 0:
        return True
    else:
        return False


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(default=None),
            proxy=dict(default=None),
            response_file=dict(default=None),
            zone=dict(required=False, 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 = 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 </tmp/pkgutil.pkg>
    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()