summaryrefslogtreecommitdiff
path: root/lib/chef/provider/package/yum/yum_helper.py
blob: 47cbe2efe623396fc416b04343f67d03e5a481fe (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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#!/usr/bin/env python3
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

#
# NOTE: this actually needs to run under python2.4 and centos 5.x through python3 and centos 7.x
# please manually test changes on centos5 boxes or you will almost certainly break things.
#

import sys
import yum
import signal
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'simplejson'))
try: import json
except ImportError: import simplejson as json
import re
from rpmUtils.miscutils import stringToVersion,compareEVR
from rpmUtils.arch import getBaseArch, getArchList


try: from yum.misc import string_to_prco_tuple
except ImportError:
    # RHEL5 compat
    def string_to_prco_tuple(prcoString):
        prco_split = prcoString.split()
        n, f, v = prco_split
        (prco_e, prco_v, prco_r) = stringToVersion(v)
        return (n, f, (prco_e, prco_v, prco_r))

# hack to work around https://github.com/chef/chef/issues/7126
# see https://bugzilla.redhat.com/show_bug.cgi?id=1396248
if not hasattr(yum.packages.FakeRepository, 'compare_providers_priority'):
    yum.packages.FakeRepository.compare_providers_priority = 99

base = None

def get_base():
    global base
    if base is None:
        base = yum.YumBase()
    setup_exit_handler()
    return base

def versioncompare(versions):
    arch_list = getArchList()
    candidate_arch1 = versions[0].split(".")[-1]
    candidate_arch2 = versions[1].split(".")[-1]

    # The first version number passed to this method is always a valid nevra (the current version)
    # If the second version number looks like it does not contain a valid arch
    # then we'll chop the arch component (assuming it *is* a valid one) from the first version string
    # so we're only comparing the evr portions.
    if (candidate_arch2 not in arch_list) and (candidate_arch1 in arch_list):
       final_version1 = versions[0].replace("." + candidate_arch1,"")
    else:
       final_version1 = versions[0]

    final_version2 = versions[1]

    (e1, v1, r1) = stringToVersion(final_version1)
    (e2, v2, r2) = stringToVersion(final_version2)

    evr_comparison = compareEVR((e1, v1, r1), (e2, v2, r2))
    outpipe.write("%(e)s\n" % { 'e': evr_comparison })
    outpipe.flush()

def install_only_packages(name):
    base = get_base()
    if name in base.conf.installonlypkgs:
      outpipe.write('True')
    else:
      outpipe.write('False')
    outpipe.flush()

# python2.4 / centos5 compat
try:
    any
except NameError:
    def any(s):
        for v in s:
            if v:
                return True
        return False

def query(command):
    base = get_base()

    enabled_repos = base.repos.listEnabled()

    # Handle any repocontrols passed in with our options

    if 'repos' in command:
      for repo in command['repos']:
        if 'enable' in repo:
          base.repos.enableRepo(repo['enable'])
        if 'disable' in repo:
          base.repos.disableRepo(repo['disable'])

    args = { 'name': command['provides'] }
    do_nevra = False
    if 'epoch' in command:
        args['epoch'] = command['epoch']
        do_nevra = True
    if 'version' in command:
        args['ver'] = command['version']
        do_nevra = True
    if 'release' in command:
        args['rel'] = command['release']
        do_nevra = True
    if 'arch' in command:
        desired_arch = command['arch']
        args['arch'] = command['arch']
        do_nevra = True
    else:
        desired_arch = getBaseArch()

    obj = None
    if command['action'] == "whatinstalled":
        obj = base.rpmdb
    else:
        obj = base.pkgSack

    # if we are given "name == 1.2.3" then we must use the getProvides() API.
    #   - this means that we ignore arch and version properties when given prco tuples as a package_name
    #   - in order to fix this, something would have to happen where getProvides was called first and
    #     then the result was searchNevra'd.  please be extremely careful if attempting to fix that
    #     since searchNevra does not support prco tuples.
    if bool(re.search('\\s+', command['provides'])):
        # handles flags (<, >, =, etc) and versions, but no wildcareds
        # raises error for any invalid input like: 'FOO BAR BAZ' 
        pkgs = obj.getProvides(*string_to_prco_tuple(command['provides']))
    elif do_nevra:
        # now if we're given version or arch properties explicitly, then we do a SearchNevra.
        #  - this means that wildcard version in the package_name with an arch property will not work correctly
        #  - again don't try to fix this just by pushing bugs around in the code, you would need to call
        #    returnPackages and searchProvides and then apply the Nevra filters to those results.
        pkgs = obj.searchNevra(**args)
        if (command['action'] == "whatinstalled") and (not pkgs):
          pkgs = obj.searchNevra(name=args['name'], arch=desired_arch)
    else:
        pats = [command['provides']]
        pkgs = obj.returnPackages(patterns=pats)

        if not pkgs:
            # handles wildcards
            pkgs = obj.searchProvides(command['provides'])

    if not pkgs:
        outpipe.write(command['provides'].split().pop(0)+' nil nil\n')
        outpipe.flush()
    else:
        # make sure we picked the package with the highest version
        pkgs = base.bestPackagesFromList(pkgs,single_name=True)
        pkg = pkgs.pop(0)
        outpipe.write("%(n)s %(e)s:%(v)s-%(r)s %(a)s\n" % { 'n': pkg.name, 'e': pkg.epoch, 'v': pkg.version, 'r': pkg.release, 'a': pkg.arch })
        outpipe.flush()

    # Reset any repos we were passed in enablerepo/disablerepo to the original state in enabled_repos
    if 'repos' in command:
      for repo in command['repos']:
        if 'enable' in repo:
          if base.repos.getRepo(repo['enable']) not in enabled_repos:
            base.repos.disableRepo(repo['enable'])
        if 'disable' in repo:
          if base.repos.getRepo(repo['disable']) in enabled_repos:
            base.repos.enableRepo(repo['disable'])

# the design of this helper is that it should try to be 'brittle' and fail hard and exit in order
# to keep process tables clean.  additional error handling should probably be added to the retry loop
# on the ruby side.
def exit_handler(signal, frame):
    if base is not None:
        base.closeRpmDB()
    sys.exit(0)

def setup_exit_handler():
    signal.signal(signal.SIGINT, exit_handler)
    signal.signal(signal.SIGHUP, exit_handler)
    signal.signal(signal.SIGPIPE, exit_handler)
    signal.signal(signal.SIGQUIT, exit_handler)

if len(sys.argv) < 3:
  inpipe = sys.stdin
  outpipe = sys.stdout
else:
  inpipe = os.fdopen(int(sys.argv[1]), "r")
  outpipe = os.fdopen(int(sys.argv[2]), "w")

try:
    while 1:
        # kill self if we get orphaned (tragic)
        ppid = os.getppid()
        if ppid == 1:
            raise RuntimeError("orphaned")

        setup_exit_handler()
        line = inpipe.readline()

        try:
            command = json.loads(line)
        except ValueError, e:
            raise RuntimeError("bad json parse")

        if command['action'] == "whatinstalled":
            query(command)
        elif command['action'] == "whatavailable":
            query(command)
        elif command['action'] == "versioncompare":
            versioncompare(command['versions'])
        elif command['action'] == "installonlypkgs":
             install_only_packages(command['package'])
        else:
            raise RuntimeError("bad command")
finally:
    if base is not None:
        base.closeRpmDB()