summaryrefslogtreecommitdiff
path: root/library/apt
blob: be4e491ddf381c1a744f02112075c2f4b3766095 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#!/usr/bin/python -tt
# (c) 2012, Flowroute LLC
# Written by Matthew Williams <matthew@flowroute.com>
# Based on yum module written by Seth Vidal <skvidal at fedoraproject.org>
#
# 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/>.
#

import traceback
# added to stave off future warnings about apt api
import warnings;
warnings.filterwarnings('ignore', "apt API not stable yet", FutureWarning)

# APT related constants
APT_PATH = "/usr/bin/apt-get"
APT = "DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical %s" % APT_PATH

def run_apt(command):
    try:
        cmd = subprocess.Popen(command, shell=True,
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        out, err = cmd.communicate()
    except (OSError, IOError), e:
        rc = 1
        err = str(e)
        out = ''
    except:
        rc = 1
        err = traceback.format_exc()
        out = ''
    else:
        rc = cmd.returncode
        return rc, out, err

def package_split(pkgspec):
    parts = pkgspec.split('=')
    if len(parts) > 1:
        return parts[0], parts[1]
    else:
        return parts[0], None

def package_status(m, pkgname, version, cache):
    try:
        pkg = cache[pkgname]
    except KeyError:
        m.fail_json(msg="No package matching '%s' is available" % pkgname)
    if version:
        try :
            return pkg.is_installed and pkg.installed.version == version, False
        except AttributeError:
            #assume older version of python-apt is installed
            return pkg.isInstalled and pkg.installedVersion == version, False
    else:
        try :
            return pkg.is_installed, pkg.is_upgradable
        except AttributeError:
            #assume older version of python-apt is installed
            return pkg.isInstalled, pkg.isUpgradable

def install(m, pkgspec, cache, upgrade=False, default_release=None, install_recommends=True, force=False):
    packages = ""
    for package in pkgspec:
        name, version = package_split(package)
        installed, upgradable = package_status(m, name, version, cache)
        if not installed or (upgrade and upgradable):
            packages += "'%s' " % package
    
    if len(packages) != 0:
        if force:
            force_yes = '--force-yes'
        else:
            force_yes = ''

        cmd = "%s --option Dpkg::Options::=--force-confold -q -y %s install %s" % (APT, force_yes,packages)
        if default_release:
            cmd += " -t '%s'" % (default_release,)
        if not install_recommends:
            cmd += " --no-install-recommends"

        rc, out, err = run_apt(cmd)
        if rc:
            m.fail_json(msg="'apt-get install %s' failed: %s" % (packages, err))
        else:
            m.exit_json(changed=True)
    else:
        m.exit_json(changed=False)

def remove(m, pkgspec, cache, purge=False):
    packages = ""
    for package in pkgspec:
        name, version = package_split(package)
        installed, upgradable = package_status(m, name, version, cache)
        if installed:
            packages += "'%s' " % package
    
    if len(packages) == 0:
        m.exit_json(changed=False)
    else:
        purge = '--purge' if purge else ''
        cmd = "%s -q -y %s remove %s" % (APT, purge,packages)
        rc, out, err = run_apt(cmd)
        if rc:
            m.fail_json(msg="'apt-get remove %s' failed: %s" % (packages, err))
        m.exit_json(changed=True)


def main():
    module = AnsibleModule(
        argument_spec = dict(
            state = dict(default='installed', choices=['installed', 'latest', 'removed']),
            update_cache = dict(default='no', choices=['yes', 'no'], aliases=['update-cache']),
            purge = dict(default='no', choices=['yes', 'no']),
            package = dict(default=None, aliases=['pkg', 'name']),
            default_release = dict(default=None, aliases=['default-release']),
            install_recommends = dict(default='yes', aliases=['install-recommends'], choices=['yes', 'no']),
            force = dict(default='no', choices=['yes', 'no'])
        )
    )

    try:
        import apt, apt_pkg
    except:
        module.fail_json("Could not import python modules: apt, apt_pkg. Please install python-apt package.")

    if not os.path.exists(APT_PATH):
        module.fail_json(msg="Cannot find apt-get")

    p = module.params
    if p['package'] is None and p['update_cache'] != 'yes':
        module.fail_json(msg='pkg=name and/or update_cache=yes is required')

    install_recommends = module.boolean(p['install_recommends'])

    cache = apt.Cache()
    if p['default_release']:
        apt_pkg.config['APT::Default-Release'] = p['default_release']
        # reopen cache w/ modified config
        cache.open(progress=None)

    if module.boolean(p['update_cache']):
        cache.update()
        cache.open(progress=None)
        if p['package'] == None:
            module.exit_json(changed=False)

    force_yes = module.boolean(p['force'])
    
    packages = p['package'].split(',')
    latest = p['state'] == 'latest' 
    for package in packages:
        if package.count('=') > 1:
            module.fail_json(msg="invalid package spec: %s" % package)
        if latest and '=' in package:
            module.fail_json(msg='version number inconsistent with state=latest: %s' % package)

    if p['state'] == 'latest':
        install(module, packages, cache, upgrade=True,
                default_release=p['default_release'],
                install_recommends=install_recommends,
                force=force_yes)
    elif p['state'] == 'installed':
        install(module, packages, cache, default_release=p['default_release'],
                  install_recommends=install_recommends,force=force_yes)
    elif p['state'] == 'removed':
        remove(module, packages, cache, purge = module.boolean(p['purge']))

# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>

main()