mirror of https://github.com/ansible/ansible.git
Add packaging module for Gentoo Portage.
This is in no way comprehensive enough to cover all use cases, but hopefully is sufficient to cover the common ones.reviewable/pr18780/r1
parent
2921775944
commit
0ccafc5255
@ -0,0 +1,389 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Yap Sok Ann
|
||||
# Written by Yap Sok Ann <sokann@gmail.com>
|
||||
# Based on apt module written by Matthew Williams <matthew@flowroute.com>
|
||||
#
|
||||
# This module 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 software 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 software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: portage
|
||||
short_description: Package manager for Gentoo
|
||||
description:
|
||||
- Manages Gentoo packages
|
||||
|
||||
version_added: "1.4"
|
||||
|
||||
options:
|
||||
package:
|
||||
description:
|
||||
- Package atom or set, e.g. C(sys-apps/foo) or C(>foo-2.13) or C(@world)
|
||||
required: false
|
||||
default: null
|
||||
|
||||
state:
|
||||
description:
|
||||
- State of the package atom
|
||||
required: false
|
||||
default: "present"
|
||||
choices: [ "present", "installed", "emerged", "absent", "removed", "unmerged" ]
|
||||
|
||||
update:
|
||||
description:
|
||||
- Update packages to the best version available (--update)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes" ]
|
||||
|
||||
deep:
|
||||
description:
|
||||
- Consider the entire dependency tree of packages (--deep)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes" ]
|
||||
|
||||
newuse:
|
||||
description:
|
||||
- Include installed packages where USE flags have changed (--newuse)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes" ]
|
||||
|
||||
oneshot:
|
||||
description:
|
||||
- Do not add the packages to the world file (--oneshot)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes" ]
|
||||
|
||||
noreplace:
|
||||
description:
|
||||
- Do not re-emerge installed packages (--noreplace)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes" ]
|
||||
|
||||
nodeps:
|
||||
description:
|
||||
- Only merge packages but not their dependencies (--nodeps)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes" ]
|
||||
|
||||
onlydeps:
|
||||
description:
|
||||
- Only merge packages' dependencies but not the packages (--onlydeps)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes" ]
|
||||
|
||||
depclean:
|
||||
description:
|
||||
- Remove packages not needed by explicitly merged packages (--depclean)
|
||||
- If no package is specified, clean up the world's dependencies
|
||||
- Otherwise, --depclean serves as a dependency aware version of --unmerge
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes" ]
|
||||
|
||||
quiet:
|
||||
description:
|
||||
- Run emerge in quiet mode (--quiet)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes" ]
|
||||
|
||||
verbose:
|
||||
description:
|
||||
- Run emerge in verbose mode (--verbose)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes" ]
|
||||
|
||||
sync:
|
||||
description:
|
||||
- Sync package repositories first
|
||||
- If yes, perform "emerge --sync"
|
||||
- If web, perform "emerge-webrsync"
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "yes", "web" ]
|
||||
|
||||
requirements: [ gentoolkit ]
|
||||
author: Yap Sok Ann
|
||||
notes: []
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Make sure package foo is installed
|
||||
- portage: package=foo state=present
|
||||
|
||||
# Make sure package foo is not installed
|
||||
- portage: package=foo state=absent
|
||||
|
||||
# Update package foo to the "best" version
|
||||
- portage: package=foo update=yes
|
||||
|
||||
# Sync repositories and update world
|
||||
- portage: package=@world update=yes deep=yes sync=yes
|
||||
|
||||
# Remove unneeded packages
|
||||
- portage: depclean=yes
|
||||
|
||||
# Remove package foo if it is not explicitly needed
|
||||
- portage: package=foo state=absent depclean=yes
|
||||
'''
|
||||
|
||||
|
||||
import os
|
||||
import pipes
|
||||
|
||||
|
||||
def query_package(module, package, action):
|
||||
if package.startswith('@'):
|
||||
return query_set(module, package, action)
|
||||
return query_atom(module, package, action)
|
||||
|
||||
|
||||
def query_atom(module, atom, action):
|
||||
cmd = '%s list %s' % (module.equery_path, atom)
|
||||
|
||||
rc, out, err = module.run_command(cmd)
|
||||
return rc == 0
|
||||
|
||||
|
||||
def query_set(module, set, action):
|
||||
system_sets = [
|
||||
'@live-rebuild',
|
||||
'@module-rebuild',
|
||||
'@preserved-rebuild',
|
||||
'@security',
|
||||
'@selected',
|
||||
'@system',
|
||||
'@world',
|
||||
'@x11-module-rebuild',
|
||||
]
|
||||
|
||||
if set in system_sets:
|
||||
if action == 'unmerge':
|
||||
module.fail_json(msg='set %s cannot be removed' % set)
|
||||
return False
|
||||
|
||||
world_sets_path = '/var/lib/portage/world_sets'
|
||||
if not os.path.exists(world_sets_path):
|
||||
return False
|
||||
|
||||
cmd = 'grep %s %s' % (set, world_sets_path)
|
||||
|
||||
rc, out, err = module.run_command(cmd)
|
||||
return rc == 0
|
||||
|
||||
|
||||
def sync_repositories(module, webrsync=False):
|
||||
if module.check_mode:
|
||||
module.fail_json(msg='check mode not supported by sync')
|
||||
|
||||
if webrsync:
|
||||
webrsync_path = module.get_bin_path('emerge-webrsync', required=True)
|
||||
cmd = '%s --quiet' % webrsync_path
|
||||
else:
|
||||
cmd = '%s --sync --quiet' % module.emerge_path
|
||||
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc != 0:
|
||||
module.fail_json(msg='could not sync package repositories')
|
||||
|
||||
|
||||
# Note: In the 3 functions below, equery is done one-by-one, but emerge is done
|
||||
# in one go. If that is not desirable, split the packages into multiple tasks
|
||||
# instead of joining them together with comma.
|
||||
|
||||
|
||||
def emerge_packages(module, packages):
|
||||
p = module.params
|
||||
|
||||
if not (p['update'] or p['noreplace']):
|
||||
for package in packages:
|
||||
if not query_package(module, package, 'emerge'):
|
||||
break
|
||||
else:
|
||||
module.exit_json(changed=False, msg='Packages already present.')
|
||||
|
||||
args = []
|
||||
for flag in [
|
||||
'update', 'deep', 'newuse',
|
||||
'oneshot', 'noreplace',
|
||||
'nodeps', 'onlydeps',
|
||||
'quiet', 'verbose',
|
||||
]:
|
||||
if p[flag]:
|
||||
args.append('--%s' % flag)
|
||||
|
||||
cmd, (rc, out, err) = run_emerge(module, packages, *args)
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
cmd=cmd, rc=rc, stdout=out, stderr=err,
|
||||
msg='Packages not installed.',
|
||||
)
|
||||
|
||||
changed = True
|
||||
for line in out.splitlines():
|
||||
if line.startswith('>>> Emerging (1 of'):
|
||||
break
|
||||
else:
|
||||
changed = False
|
||||
|
||||
module.exit_json(
|
||||
changed=changed, cmd=cmd, rc=rc, stdout=out, stderr=err,
|
||||
msg='Packages installed.',
|
||||
)
|
||||
|
||||
|
||||
def unmerge_packages(module, packages):
|
||||
p = module.params
|
||||
|
||||
for package in packages:
|
||||
if query_package(module, package, 'unmerge'):
|
||||
break
|
||||
else:
|
||||
module.exit_json(changed=False, msg='Packages already absent.')
|
||||
|
||||
args = ['--unmerge']
|
||||
|
||||
for flag in ['quiet', 'verbose']:
|
||||
if p[flag]:
|
||||
args.append('--%s' % flag)
|
||||
|
||||
cmd, (rc, out, err) = run_emerge(module, packages, *args)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
cmd=cmd, rc=rc, stdout=out, stderr=err,
|
||||
msg='Packages not removed.',
|
||||
)
|
||||
|
||||
module.exit_json(
|
||||
changed=True, cmd=cmd, rc=rc, stdout=out, stderr=err,
|
||||
msg='Packages removed.',
|
||||
)
|
||||
|
||||
|
||||
def cleanup_packages(module, packages):
|
||||
p = module.params
|
||||
|
||||
if packages:
|
||||
for package in packages:
|
||||
if query_package(module, package, 'unmerge'):
|
||||
break
|
||||
else:
|
||||
module.exit_json(changed=False, msg='Packages already absent.')
|
||||
|
||||
args = ['--depclean']
|
||||
|
||||
for flag in ['quiet', 'verbose']:
|
||||
if p[flag]:
|
||||
args.append('--%s' % flag)
|
||||
|
||||
cmd, (rc, out, err) = run_emerge(module, packages, *args)
|
||||
if rc != 0:
|
||||
module.fail_json(cmd=cmd, rc=rc, stdout=out, stderr=err)
|
||||
|
||||
removed = 0
|
||||
for line in out.splitlines():
|
||||
if not line.startswith('Number removed:'):
|
||||
continue
|
||||
parts = line.split(':')
|
||||
removed = int(parts[1].strip())
|
||||
changed = removed > 0
|
||||
|
||||
module.exit_json(
|
||||
changed=changed, cmd=cmd, rc=rc, stdout=out, stderr=err,
|
||||
msg='Depclean completed.',
|
||||
)
|
||||
|
||||
|
||||
def run_emerge(module, packages, *args):
|
||||
args = list(args)
|
||||
|
||||
if module.check_mode:
|
||||
args.append('--pretend')
|
||||
|
||||
cmd = [module.emerge_path] + args + packages
|
||||
return cmd, module.run_command(cmd)
|
||||
|
||||
|
||||
portage_present_states = ['present', 'emerged', 'installed']
|
||||
portage_absent_states = ['absent', 'unmerged', 'removed']
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
package=dict(default=None, aliases=['name']),
|
||||
state=dict(
|
||||
default=portage_present_states[0],
|
||||
choices=portage_present_states + portage_absent_states,
|
||||
),
|
||||
update=dict(default=None, choices=['yes']),
|
||||
deep=dict(default=None, choices=['yes']),
|
||||
newuse=dict(default=None, choices=['yes']),
|
||||
oneshot=dict(default=None, choices=['yes']),
|
||||
noreplace=dict(default=None, choices=['yes']),
|
||||
nodeps=dict(default=None, choices=['yes']),
|
||||
onlydeps=dict(default=None, choices=['yes']),
|
||||
depclean=dict(default=None, choices=['yes']),
|
||||
quiet=dict(default=None, choices=['yes']),
|
||||
verbose=dict(default=None, choices=['yes']),
|
||||
sync=dict(default=None, choices=['yes', 'web']),
|
||||
),
|
||||
required_one_of=[['package', 'sync', 'depclean']],
|
||||
mutually_exclusive=[['nodeps', 'onlydeps'], ['quiet', 'verbose']],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
module.emerge_path = module.get_bin_path('emerge', required=True)
|
||||
module.equery_path = module.get_bin_path('equery', required=True)
|
||||
|
||||
p = module.params
|
||||
|
||||
if p['sync']:
|
||||
sync_repositories(module, webrsync=(p['sync'] == 'web'))
|
||||
if not p['package']:
|
||||
return
|
||||
|
||||
packages = p['package'].split(',') if p['package'] else []
|
||||
|
||||
if p['depclean']:
|
||||
if packages and p['state'] not in portage_absent_states:
|
||||
module.fail_json(
|
||||
msg='Depclean can only be used with package when the state is '
|
||||
'one of: %s' % portage_absent_states,
|
||||
)
|
||||
|
||||
cleanup_packages(module, packages)
|
||||
|
||||
elif p['state'] in portage_present_states:
|
||||
emerge_packages(module, packages)
|
||||
|
||||
elif p['state'] in portage_absent_states:
|
||||
unmerge_packages(module, packages)
|
||||
|
||||
|
||||
# this is magic, see lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
|
||||
main()
|
Loading…
Reference in New Issue