1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# Copyright: (c) 2013, Philippe Makowski
5# Written by Philippe Makowski <philippem@mageia.org>
6# Based on apt module written by Matthew Williams <matthew@flowroute.com>
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
13DOCUMENTATION = '''
14---
15module: urpmi
16short_description: Urpmi manager
17description:
18  - Manages packages with I(urpmi) (such as for Mageia or Mandriva)
19options:
20  name:
21    description:
22      - A list of package names to install, upgrade or remove.
23    required: yes
24    aliases: [ package, pkg ]
25    type: list
26    elements: str
27  state:
28    description:
29      - Indicates the desired package state.
30    choices: [ absent, present, installed, removed ]
31    default: present
32    type: str
33  update_cache:
34    description:
35      - Update the package database first C(urpmi.update -a).
36      - Alias C(update-cache) has been deprecated and will be removed in community.general 5.0.0.
37    type: bool
38    default: no
39    aliases: ['update-cache']
40  no_recommends:
41    description:
42      - Corresponds to the C(--no-recommends) option for I(urpmi).
43      - Alias C(no-recommends) has been deprecated and will be removed in community.general 5.0.0.
44    type: bool
45    default: yes
46    aliases: ['no-recommends']
47  force:
48    description:
49      - Assume "yes" is the answer to any question urpmi has to ask.
50        Corresponds to the C(--force) option for I(urpmi).
51    type: bool
52    default: yes
53  root:
54    description:
55      - Specifies an alternative install root, relative to which all packages will be installed.
56        Corresponds to the C(--root) option for I(urpmi).
57    aliases: [ installroot ]
58    type: str
59author:
60- Philippe Makowski (@pmakowski)
61'''
62
63EXAMPLES = '''
64- name: Install package foo
65  community.general.urpmi:
66    pkg: foo
67    state: present
68
69- name: Remove package foo
70  community.general.urpmi:
71    pkg: foo
72    state: absent
73
74- name: Remove packages foo and bar
75  community.general.urpmi:
76    pkg: foo,bar
77    state: absent
78
79- name: Update the package database (urpmi.update -a -q) and install bar (bar will be the updated if a newer version exists)
80- community.general.urpmi:
81    name: bar
82    state: present
83    update_cache: yes
84'''
85
86
87import os
88import shlex
89import sys
90
91from ansible.module_utils.basic import AnsibleModule
92
93
94def query_package(module, name, root):
95    # rpm -q returns 0 if the package is installed,
96    # 1 if it is not installed
97    rpm_path = module.get_bin_path("rpm", True)
98    cmd = "%s -q %s %s" % (rpm_path, name, root_option(root))
99    rc, stdout, stderr = module.run_command(cmd, check_rc=False)
100    if rc == 0:
101        return True
102    else:
103        return False
104
105
106def query_package_provides(module, name, root):
107    # rpm -q returns 0 if the package is installed,
108    # 1 if it is not installed
109    rpm_path = module.get_bin_path("rpm", True)
110    cmd = "%s -q --whatprovides %s %s" % (rpm_path, name, root_option(root))
111    rc, stdout, stderr = module.run_command(cmd, check_rc=False)
112    return rc == 0
113
114
115def update_package_db(module):
116
117    urpmiupdate_path = module.get_bin_path("urpmi.update", True)
118    cmd = "%s -a -q" % (urpmiupdate_path,)
119    rc, stdout, stderr = module.run_command(cmd, check_rc=False)
120    if rc != 0:
121        module.fail_json(msg="could not update package db")
122
123
124def remove_packages(module, packages, root):
125
126    remove_c = 0
127    # Using a for loop in case of error, we can report the package that failed
128    for package in packages:
129        # Query the package first, to see if we even need to remove
130        if not query_package(module, package, root):
131            continue
132
133        urpme_path = module.get_bin_path("urpme", True)
134        cmd = "%s --auto %s %s" % (urpme_path, root_option(root), package)
135        rc, stdout, stderr = module.run_command(cmd, check_rc=False)
136
137        if rc != 0:
138            module.fail_json(msg="failed to remove %s" % (package))
139
140        remove_c += 1
141
142    if remove_c > 0:
143
144        module.exit_json(changed=True, msg="removed %s package(s)" % remove_c)
145
146    module.exit_json(changed=False, msg="package(s) already absent")
147
148
149def install_packages(module, pkgspec, root, force=True, no_recommends=True):
150
151    packages = ""
152    for package in pkgspec:
153        if not query_package_provides(module, package, root):
154            packages += "'%s' " % package
155
156    if len(packages) != 0:
157        if no_recommends:
158            no_recommends_yes = '--no-recommends'
159        else:
160            no_recommends_yes = ''
161
162        if force:
163            force_yes = '--force'
164        else:
165            force_yes = ''
166
167        urpmi_path = module.get_bin_path("urpmi", True)
168        cmd = ("%s --auto %s --quiet %s %s %s" % (urpmi_path, force_yes,
169                                                  no_recommends_yes,
170                                                  root_option(root),
171                                                  packages))
172
173        rc, out, err = module.run_command(cmd)
174
175        for package in pkgspec:
176            if not query_package_provides(module, package, root):
177                module.fail_json(msg="'urpmi %s' failed: %s" % (package, err))
178
179        # urpmi always have 0 for exit code if --force is used
180        if rc:
181            module.fail_json(msg="'urpmi %s' failed: %s" % (packages, err))
182        else:
183            module.exit_json(changed=True, msg="%s present(s)" % packages)
184    else:
185        module.exit_json(changed=False)
186
187
188def root_option(root):
189    if (root):
190        return "--root=%s" % (root)
191    else:
192        return ""
193
194
195def main():
196    module = AnsibleModule(
197        argument_spec=dict(
198            state=dict(type='str', default='present',
199                       choices=['absent', 'installed', 'present', 'removed']),
200            update_cache=dict(
201                type='bool', default=False, aliases=['update-cache'],
202                deprecated_aliases=[dict(name='update-cache', version='5.0.0', collection_name='community.general')]),
203            force=dict(type='bool', default=True),
204            no_recommends=dict(
205                type='bool', default=True, aliases=['no-recommends'],
206                deprecated_aliases=[dict(name='no-recommends', version='5.0.0', collection_name='community.general')]),
207            name=dict(type='list', elements='str', required=True, aliases=['package', 'pkg']),
208            root=dict(type='str', aliases=['installroot']),
209        ),
210    )
211
212    p = module.params
213
214    if p['update_cache']:
215        update_package_db(module)
216
217    if p['state'] in ['installed', 'present']:
218        install_packages(module, p['name'], p['root'], p['force'], p['no_recommends'])
219
220    elif p['state'] in ['removed', 'absent']:
221        remove_packages(module, p['name'], p['root'])
222
223
224if __name__ == '__main__':
225    main()
226