From 07815bde3b2aa730df74046105add8b81d4e375a Mon Sep 17 00:00:00 2001 From: Peter Oliver Date: Wed, 31 Dec 2014 11:59:40 +0000 Subject: [PATCH 1/6] Add Solaris 11 package management - Module pkg5 handles installing and uninstalling packages. - Module pkg5_publisher manages repository configuration. --- packaging/os/pkg5.py | 109 ++++++++++++++++++++ packaging/os/pkg5_publisher.py | 176 +++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 packaging/os/pkg5.py create mode 100644 packaging/os/pkg5_publisher.py diff --git a/packaging/os/pkg5.py b/packaging/os/pkg5.py new file mode 100644 index 00000000000..ffe9e083a63 --- /dev/null +++ b/packaging/os/pkg5.py @@ -0,0 +1,109 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +DOCUMENTATION = ''' +--- +module: pkg5 +author: Peter Oliver +short_description: Manages packages with the Solaris 11 Image Packaging System +description: + - IPS packages are the native packages in Solaris 11 and higher. +notes: + - The naming of IPS packages is explained at http://www.oracle.com/technetwork/articles/servers-storage-admin/ips-package-versioning-2232906.html. +options: + name: + description: + - An FRMI of the package(s) to be installed/removed/updated. + - Multiple packages may be specified, separated by C(,). If C(,) + appears in an FRMI, you can replace it with C(-). + required: true + state: + description: + - Whether to install (C(present), C(latest)), or remove (C(absent)) a + package. + required: false + default: present + choices: [ present, latest, absent ] +''' +EXAMPLES = ''' +# Install Vim: +- pkg5: name=editor/vim + +# Remove finger daemon: +- pkg5: name=service/network/finger state=absent +''' + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, type='list'), + state=dict( + default='present', + choices=[ + 'present', + 'installed', + 'latest', + 'absent', + 'uninstalled', + 'removed', + ] + ), + ) + ) + + params = module.params + if params['state'] in ['present', 'installed']: + ensure(module, 'present', params['name']) + elif params['state'] in ['latest']: + ensure(module, 'latest', params['name']) + elif params['state'] in ['absent', 'uninstalled', 'removed']: + ensure(module, 'absent', params['name']) + + +def ensure(module, state, packages): + response = { + 'results': [], + 'msg': '', + } + behaviour = { + 'present': { + 'filter': lambda p: not is_installed(module, p), + 'subcommand': 'install', + }, + 'latest': { + 'filter': lambda p: not is_latest(module, p), + 'subcommand': 'install', + }, + 'absent': { + 'filter': lambda p: is_installed(module, p), + 'subcommand': 'uninstall', + }, + } + + to_modify = filter(behaviour[state]['filter'], packages) + if to_modify: + rc, out, err = module.run_command( + ['pkg', behaviour[state]['subcommand'], '-q', '--'] + to_modify + ) + response['rc'] = rc + response['results'].append(out) + response['msg'] += err + response['changed'] = True + if rc != 0: + module.fail_json(**response) + + module.exit_json(**response) + + +def is_installed(module, package): + rc, out, err = module.run_command(['pkg', 'list', '--', package]) + return True if rc == 0 else False + + +def is_latest(module, package): + rc, out, err = module.run_command(['pkg', 'list', '-u', '--', package]) + return True if rc == 1 else False + + +from ansible.module_utils.basic import * +main() diff --git a/packaging/os/pkg5_publisher.py b/packaging/os/pkg5_publisher.py new file mode 100644 index 00000000000..46bb6c6407b --- /dev/null +++ b/packaging/os/pkg5_publisher.py @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +DOCUMENTATION = ''' +--- +module: pkg5_publisher +author: Peter Oliver +short_description: Manages Solaris 11 Image Packaging System publishers +description: + - IPS packages are the native packages in Solaris 11 and higher. + - This modules will configure which publishers a client will download IPS + packages from. +options: + name: + description: + - The publisher's name. + required: true + aliases: [ publisher ] + state: + description: + - Whether to ensure that a publisher is present or absent. + required: false + default: present + choices: [ present, absent ] + sticky: + description: + - Packages installed from a sticky repository can only receive updates + from that repository. + required: false + default: null + choices: [ true, false ] + enabled: + description: + - Is the repository enabled or disabled? + required: false + default: null + choices: [ true, false ] + origin: + description: + - A path or URL to the repository. + - Multiple values may be provided. + required: false + default: null + mirror: + description: + - A path or URL to the repository mirror. + - Multiple values may be provided. + required: false + default: null +''' +EXAMPLES = ''' +# Fetch packages for the solaris publisher direct from Oracle: +- pkg5_publisher: name=solaris sticky=true origin=https://pkg.oracle.com/solaris/support/ + +# Configure a publisher for locally-produced packages: +- pkg5_publisher: name=site origin=https://pkg.example.com/site/ +''' + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, aliases=['publisher']), + state=dict(default='present', choices=['present', 'absent']), + sticky=dict(choices=BOOLEANS), + enabled=dict(choices=BOOLEANS), + # search_after=dict(), + # search_before=dict(), + origin=dict(type='list'), + mirror=dict(type='list'), + ) + ) + + for option in ['origin', 'mirror']: + if module.params[option] == ['']: + module.params[option] = [] + + if module.params['state'] == 'present': + modify_publisher(module, module.params) + else: + unset_publisher(module, module.params['name']) + + +def modify_publisher(module, params): + name = params['name'] + existing = get_publishers(module) + + if name in existing: + for option in ['origin', 'mirror', 'sticky', 'enabled']: + if params[option] != None: + if params[option] != existing[name][option]: + return set_publisher(module, params) + else: + return set_publisher(module, params) + + module.exit_json() + + +def set_publisher(module, params): + name = params['name'] + args = [] + + if params['origin'] != None: + args.append('--remove-origin=*') + args.extend(['--add-origin=' + u for u in params['origin']]) + if params['mirror'] != None: + args.append('--remove-mirror=*') + args.extend(['--add-mirror=' + u for u in params['mirror']]) + + if params['sticky'] != None: + args.append('--sticky' if params['sticky'] else '--non-sticky') + if params['enabled'] != None: + args.append('--enable' if params['enabled'] else '--disable') + + rc, out, err = module.run_command( + ["pkg", "set-publisher"] + args + [name], + check_rc=True + ) + response = { + 'rc': rc, + 'results': [out], + 'msg': err, + 'changed': True, + } + module.exit_json(**response) + + +def unset_publisher(module, publisher): + rc, out, err = module.run_command( + ["pkg", "unset-publisher", publisher], + check_rc=True + ) + response = { + 'rc': rc, + 'results': [out], + 'msg': err, + 'changed': True, + } + module.exit_json(**response) + + +def get_publishers(module): + rc, out, err = module.run_command(["pkg", "publisher", "-Ftsv"], True) + + lines = out.splitlines() + keys = lines.pop(0).lower().split("\t") + + publishers = {} + for line in lines: + values = dict(zip(keys, map(unstringify, line.split("\t")))) + name = values['publisher'] + + if not name in publishers: + publishers[name] = dict( + (k, values[k]) for k in ['sticky', 'enabled'] + ) + publishers[name]['origin'] = [] + publishers[name]['mirror'] = [] + + publishers[name][values['type']].append(values['uri']) + + return publishers + + +def unstringify(val): + if val == "-": + return None + elif val == "true": + return True + elif val == "false": + return False + else: + return val + + +from ansible.module_utils.basic import * +main() From ec54b00fdfa93f8d64a6c18631e01addb2b09170 Mon Sep 17 00:00:00 2001 From: Peter Oliver Date: Wed, 31 Dec 2014 12:48:59 +0000 Subject: [PATCH 2/6] Add missing boilerplate. --- packaging/os/pkg5.py | 15 +++++++++++++++ packaging/os/pkg5_publisher.py | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/packaging/os/pkg5.py b/packaging/os/pkg5.py index ffe9e083a63..271b95fffe6 100644 --- a/packaging/os/pkg5.py +++ b/packaging/os/pkg5.py @@ -1,6 +1,21 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright 2014 Peter Oliver +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + DOCUMENTATION = ''' --- module: pkg5 diff --git a/packaging/os/pkg5_publisher.py b/packaging/os/pkg5_publisher.py index 46bb6c6407b..20b0c0a659c 100644 --- a/packaging/os/pkg5_publisher.py +++ b/packaging/os/pkg5_publisher.py @@ -1,6 +1,21 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright 2014 Peter Oliver +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + DOCUMENTATION = ''' --- module: pkg5_publisher From 5a7695c440956d881ce22a04bb2c37b7d60ea7ee Mon Sep 17 00:00:00 2001 From: Peter Oliver Date: Wed, 31 Dec 2014 13:27:21 +0000 Subject: [PATCH 3/6] Try to fix up commas in version numbers. --- packaging/os/pkg5.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packaging/os/pkg5.py b/packaging/os/pkg5.py index 271b95fffe6..bbdf3eb006b 100644 --- a/packaging/os/pkg5.py +++ b/packaging/os/pkg5.py @@ -48,6 +48,7 @@ EXAMPLES = ''' - pkg5: name=service/network/finger state=absent ''' + def main(): module = AnsibleModule( argument_spec=dict( @@ -67,12 +68,26 @@ def main(): ) params = module.params + packages = [] + + # pkg(5) FRMIs include a comma before the release number, but + # AnsibleModule will have split this into multiple items for us. + # Try to spot where this has happened and fix it. + for fragment in params['name']: + if ( + re.search('^\d+(?:\.\d+)*', fragment) + and packages and re.search('@[^,]*$', packages[-1]) + ): + packages[-1] += ',' + fragment + else: + packages.append(fragment) + if params['state'] in ['present', 'installed']: - ensure(module, 'present', params['name']) + ensure(module, 'present', packages) elif params['state'] in ['latest']: - ensure(module, 'latest', params['name']) + ensure(module, 'latest', packages) elif params['state'] in ['absent', 'uninstalled', 'removed']: - ensure(module, 'absent', params['name']) + ensure(module, 'absent', packages) def ensure(module, state, packages): From 2eae1820ff80e37c35b8c92c2c170354d6f8412e Mon Sep 17 00:00:00 2001 From: Peter Oliver Date: Sun, 25 Jan 2015 15:42:52 +0000 Subject: [PATCH 4/6] Tweak documentation. --- packaging/os/pkg5.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packaging/os/pkg5.py b/packaging/os/pkg5.py index bbdf3eb006b..beb04441505 100644 --- a/packaging/os/pkg5.py +++ b/packaging/os/pkg5.py @@ -24,17 +24,16 @@ short_description: Manages packages with the Solaris 11 Image Packaging System description: - IPS packages are the native packages in Solaris 11 and higher. notes: - - The naming of IPS packages is explained at http://www.oracle.com/technetwork/articles/servers-storage-admin/ips-package-versioning-2232906.html. + - The naming of IPS packages is explained at U(http://www.oracle.com/technetwork/articles/servers-storage-admin/ips-package-versioning-2232906.html). options: name: description: - An FRMI of the package(s) to be installed/removed/updated. - - Multiple packages may be specified, separated by C(,). If C(,) - appears in an FRMI, you can replace it with C(-). + - Multiple packages may be specified, separated by C(,). required: true state: description: - - Whether to install (C(present), C(latest)), or remove (C(absent)) a + - Whether to install (I(present), I(latest)), or remove (I(absent)) a package. required: false default: present From 3524330e5d91663d399e054aa6f38dbda1dbd589 Mon Sep 17 00:00:00 2001 From: Peter Oliver Date: Sun, 25 Jan 2015 15:44:32 +0000 Subject: [PATCH 5/6] Fix idempotency when removing packages. If the package is already not present, then we have nothing to do. --- packaging/os/pkg5_publisher.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packaging/os/pkg5_publisher.py b/packaging/os/pkg5_publisher.py index 20b0c0a659c..2993c1107cc 100644 --- a/packaging/os/pkg5_publisher.py +++ b/packaging/os/pkg5_publisher.py @@ -140,6 +140,9 @@ def set_publisher(module, params): def unset_publisher(module, publisher): + if not publisher in get_publishers(module): + module.exit_json() + rc, out, err = module.run_command( ["pkg", "unset-publisher", publisher], check_rc=True From e1e861fa35dccc5146c7b1c8a0ba71bd3e38c7d8 Mon Sep 17 00:00:00 2001 From: Peter Oliver Date: Mon, 26 Jan 2015 21:11:38 +0000 Subject: [PATCH 6/6] Add another example. --- packaging/os/pkg5.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packaging/os/pkg5.py b/packaging/os/pkg5.py index beb04441505..83c08af0c3b 100644 --- a/packaging/os/pkg5.py +++ b/packaging/os/pkg5.py @@ -45,6 +45,12 @@ EXAMPLES = ''' # Remove finger daemon: - pkg5: name=service/network/finger state=absent + +# Install several packages at once: +- pkg5: + name: + - /file/gnu-findutils + - /text/gnu-grep '''