collect the coupling stuff together
[tools/mic.git] / plugins / backend / zypppkgmgr.py
1 #!/usr/bin/python -tt
2 #
3 # Copyright (c) 2010, 2011 Intel, Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the Free
7 # Software Foundation; version 2 of the License
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 # for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc., 59
16 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18 import os
19 import shutil
20 import urlparse
21 import rpm
22
23 import zypp
24 if not hasattr(zypp, 'PoolQuery') or \
25    not hasattr(zypp.RepoManager, 'loadSolvFile'):
26     raise ImportError("python-zypp in host system cannot support PoolQuery or "
27                       "loadSolvFile interface, please update it to enhanced "
28                       "version which can be found in repo.meego.com/tools")
29
30 from mic import msger
31 from mic.kickstart import ksparser
32 from mic.utils import misc, rpmmisc, runner, fs_related
33 from mic.utils.proxy import get_proxy_for
34 from mic.utils.errors import CreatorError
35 from mic.imager.baseimager import BaseImageCreator
36
37 class RepositoryStub:
38     def __init__(self):
39         self.name = None
40         self.baseurl = []
41         self.mirrorlist = None
42         self.proxy = None
43         self.proxy_username = None
44         self.proxy_password = None
45
46         self.enabled = True
47         self.autorefresh = True
48         self.keeppackages = True
49         self.priority = None
50
51 class RepoError(CreatorError):
52     pass
53
54 class RpmError(CreatorError):
55     pass
56
57 from mic.pluginbase import BackendPlugin
58 class Zypp(BackendPlugin):
59     name = 'zypp'
60
61     def __init__(self, target_arch, instroot, cachedir):
62         self.cachedir = cachedir
63         self.instroot  = instroot
64         self.target_arch = target_arch
65
66         self.__pkgs_license = {}
67         self.__pkgs_content = {}
68         self.repos = []
69         self.to_deselect = []
70         self.localpkgs = {}
71         self.repo_manager = None
72         self.repo_manager_options = None
73         self.Z = None
74         self.ts = None
75         self.probFilterFlags = []
76         self.incpkgs = {}
77         self.excpkgs = {}
78
79         self.has_prov_query = True
80
81     def doFileLogSetup(self, uid, logfile):
82         # don't do the file log for the livecd as it can lead to open fds
83         # being left and an inability to clean up after ourself
84         pass
85
86     def closeRpmDB(self):
87         pass
88
89     def close(self):
90         if self.ts:
91             self.ts.closeDB()
92             self.ts = None
93
94         self.closeRpmDB()
95
96         if not os.path.exists("/etc/fedora-release") and \
97            not os.path.exists("/etc/meego-release"):
98             for i in range(3, os.sysconf("SC_OPEN_MAX")):
99                 try:
100                     os.close(i)
101                 except:
102                     pass
103
104     def __del__(self):
105         self.close()
106
107     def _cleanupRpmdbLocks(self, installroot):
108         # cleans up temporary files left by bdb so that differing
109         # versions of rpm don't cause problems
110         import glob
111         for f in glob.glob(installroot + "/var/lib/rpm/__db*"):
112             os.unlink(f)
113
114     def setup(self):
115         self._cleanupRpmdbLocks(self.instroot)
116
117     def whatObsolete(self, pkg):
118         query = zypp.PoolQuery()
119         query.addKind(zypp.ResKind.package)
120         query.addAttribute(zypp.SolvAttr.obsoletes, pkg)
121         query.setMatchExact()
122         for pi in query.queryResults(self.Z.pool()):
123             return pi
124         return None
125
126     def _splitPkgString(self, pkg):
127         sp = pkg.rsplit(".",1)
128         name = sp[0]
129         arch = None
130         if len(sp) == 2:
131             arch = sp[1]
132             sysarch = zypp.Arch(self.target_arch)
133             if not zypp.Arch(arch).compatible_with (sysarch):
134                 arch = None
135                 name = ".".join(sp)
136         return name, arch
137
138     def selectPackage(self, pkg):
139         """Select a given package or package pattern, can be specified
140         with name.arch or name* or *name
141         """
142
143         if not self.Z:
144             self.__initialize_zypp()
145
146         def markPoolItem(obs, pi):
147             if obs == None:
148                 pi.status().setToBeInstalled (zypp.ResStatus.USER)
149             else:
150                 obs.status().setToBeInstalled (zypp.ResStatus.USER)
151
152         found = False
153         startx = pkg.startswith("*")
154         endx = pkg.endswith("*")
155         ispattern = startx or endx
156         name, arch = self._splitPkgString(pkg)
157
158         q = zypp.PoolQuery()
159         q.addKind(zypp.ResKind.package)
160
161         if ispattern:
162             if startx and not endx:
163                 pattern = '%s$' % (pkg[1:])
164             if endx and not startx:
165                 pattern = '^%s' % (pkg[0:-1])
166             if endx and startx:
167                 pattern = '%s' % (pkg[1:-1])
168             q.setMatchRegex()
169             q.addAttribute(zypp.SolvAttr.name,pattern)
170
171         elif arch:
172             q.setMatchExact()
173             q.addAttribute(zypp.SolvAttr.name,name)
174
175         else:
176             q.setMatchExact()
177             q.addAttribute(zypp.SolvAttr.name,pkg)
178
179         for item in sorted(
180                         q.queryResults(self.Z.pool()),
181                         key=lambda item: str(item.edition()),
182                         reverse=True):
183
184             if item.name() in self.excpkgs.keys() and \
185                self.excpkgs[item.name()] == item.repoInfo().name():
186                 continue
187             if item.name() in self.incpkgs.keys() and \
188                self.incpkgs[item.name()] != item.repoInfo().name():
189                 continue
190
191             found = True
192             obspkg = self.whatObsolete(item.name())
193             if arch:
194                 if arch == str(item.arch()):
195                     item.status().setToBeInstalled (zypp.ResStatus.USER)
196             else:
197                 markPoolItem(obspkg, item)
198             if not ispattern:
199                 break
200
201         # Can't match using package name, then search from packge
202         # provides infomation
203         if found == False and not ispattern:
204             q.addAttribute(zypp.SolvAttr.provides, pkg)
205             q.addAttribute(zypp.SolvAttr.name,'')
206
207             for item in sorted(
208                             q.queryResults(self.Z.pool()),
209                             key=lambda item: str(item.edition()),
210                             reverse=True):
211                 if item.name() in self.excpkgs.keys() and \
212                    self.excpkgs[item.name()] == item.repoInfo().name():
213                     continue
214                 if item.name() in self.incpkgs.keys() and \
215                    self.incpkgs[item.name()] != item.repoInfo().name():
216                     continue
217
218                 found = True
219                 obspkg = self.whatObsolete(item.name())
220                 markPoolItem(obspkg, item)
221                 break
222
223         if found:
224             return None
225         else:
226             raise CreatorError("Unable to find package: %s" % (pkg,))
227
228     def inDeselectPackages(self, item):
229         """check if specified pacakges are in the list of inDeselectPackages
230         """
231
232         name = item.name()
233         for pkg in self.to_deselect:
234             startx = pkg.startswith("*")
235             endx = pkg.endswith("*")
236             ispattern = startx or endx
237             pkgname, pkgarch = self._splitPkgString(pkg)
238             if not ispattern:
239                 if pkgarch:
240                     if name == pkgname and str(item.arch()) == pkgarch:
241                         return True;
242                 else:
243                     if name == pkgname:
244                         return True;
245             else:
246                 if startx and name.endswith(pkg[1:]):
247                         return True;
248                 if endx and name.startswith(pkg[:-1]):
249                         return True;
250
251         return False;
252
253     def deselectPackage(self, pkg):
254         """collect packages should not be installed"""
255         self.to_deselect.append(pkg)
256
257     def selectGroup(self, grp, include = ksparser.GROUP_DEFAULT):
258         if not self.Z:
259             self.__initialize_zypp()
260         found = False
261         q=zypp.PoolQuery()
262         q.addKind(zypp.ResKind.pattern)
263         for item in q.queryResults(self.Z.pool()):
264             summary = "%s" % item.summary()
265             name = "%s" % item.name()
266             if name == grp or summary == grp:
267                 found = True
268                 item.status().setToBeInstalled (zypp.ResStatus.USER)
269                 break
270
271         if found:
272             if include == ksparser.GROUP_REQUIRED:
273                 map(
274                     lambda p: self.deselectPackage(p),
275                     grp.default_packages.keys())
276
277             return None
278         else:
279             raise CreatorError("Unable to find pattern: %s" % (grp,))
280
281     def addRepository(self, name,
282                             url = None,
283                             mirrorlist = None,
284                             proxy = None,
285                             proxy_username = None,
286                             proxy_password = None,
287                             inc = None,
288                             exc = None,
289                             ssl_verify = True,
290                             cost=None,
291                             priority=None):
292         # TODO: Handle cost attribute for repos
293
294         if not self.repo_manager:
295             self.__initialize_repo_manager()
296
297         repo = RepositoryStub()
298         repo.name = name
299         repo.id = name
300         repo.proxy = proxy
301         repo.proxy_username = proxy_username
302         repo.proxy_password = proxy_password
303         repo.ssl_verify = ssl_verify
304         repo.baseurl.append(url)
305         if inc:
306             for pkg in inc:
307                 self.incpkgs[pkg] = name
308         if exc:
309             for pkg in exc:
310                 self.excpkgs[pkg] = name
311
312         # check LICENSE files
313         if not rpmmisc.checkRepositoryEULA(name, repo):
314             msger.warning('skip repo:%s for failed EULA confirmation' % name)
315             return None
316
317         if mirrorlist:
318             repo.mirrorlist = mirrorlist
319
320         # Enable gpg check for verifying corrupt packages
321         repo.gpgcheck = 1
322         if priority:
323             repo.priority = priority
324         self.repos.append(repo)
325
326         try:
327             repo_info = zypp.RepoInfo()
328             repo_info.setAlias(repo.name)
329             repo_info.setName(repo.name)
330             repo_info.setEnabled(repo.enabled)
331             repo_info.setAutorefresh(repo.autorefresh)
332             repo_info.setKeepPackages(repo.keeppackages)
333             baseurl = zypp.Url(repo.baseurl[0])
334             if not ssl_verify:
335                 baseurl.setQueryParam("ssl_verify", "no")
336             if proxy:
337                 scheme, host, path, parm, query, frag = urlparse.urlparse(proxy)
338
339                 proxyinfo = host.split(":")
340                 host = proxyinfo[0]
341
342                 port = "80"
343                 if len(proxyinfo) > 1:
344                     port = proxyinfo[1]
345
346                 if proxy.startswith("socks") and len(proxy.rsplit(':', 1)) == 2:
347                     host = proxy.rsplit(':', 1)[0]
348                     port = proxy.rsplit(':', 1)[1]
349
350                 baseurl.setQueryParam ("proxy", host)
351                 baseurl.setQueryParam ("proxyport", port)
352
353             repo_info.addBaseUrl(baseurl)
354
355             if repo.priority:
356                 repo_info.setPriority(repo.priority)
357
358             self.repo_manager.addRepository(repo_info)
359
360             self.__build_repo_cache(name)
361
362         except RuntimeError, e:
363             raise CreatorError(str(e))
364
365         msger.verbose('repo: %s was added' % name)
366         return repo
367
368     def installHasFile(self, file):
369         return False
370
371     def runInstall(self, checksize = 0):
372         os.environ["HOME"] = "/"
373         self.buildTransaction()
374
375         todo = zypp.GetResolvablesToInsDel(self.Z.pool())
376         installed_pkgs = todo._toInstall
377         dlpkgs = []
378         for item in installed_pkgs:
379             if not zypp.isKindPattern(item) and \
380                not self.inDeselectPackages(item):
381                 dlpkgs.append(item)
382
383         # record all pkg and the content
384         localpkgs = self.localpkgs.keys()
385         for pkg in dlpkgs:
386             license = ''
387             if pkg.name() in localpkgs:
388                 hdr = rpmmisc.readRpmHeader(self.ts, self.localpkgs[pkg.name()])
389                 pkg_long_name = misc.RPM_FMT % {
390                                     'name': hdr['name'],
391                                     'arch': hdr['arch'],
392                                     'ver_rel': '%s-%s' % (hdr['version'],
393                                                           hdr['release']),
394                                 }
395                 license = hdr['license']
396
397             else:
398                 pkg_long_name = misc.RPM_FMT % {
399                                     'name': pkg.name(),
400                                     'arch': pkg.arch(),
401                                     'ver_rel': pkg.edition(),
402                                 }
403
404                 package = zypp.asKindPackage(pkg)
405                 license = package.license()
406
407             self.__pkgs_content[pkg_long_name] = {} #TBD: to get file list
408
409             if license in self.__pkgs_license.keys():
410                 self.__pkgs_license[license].append(pkg_long_name)
411             else:
412                 self.__pkgs_license[license] = [pkg_long_name]
413
414         total_count = len(dlpkgs)
415         cached_count = 0
416         download_total_size = sum(map(lambda x: int(x.downloadSize()), dlpkgs))
417         localpkgs = self.localpkgs.keys()
418
419         msger.info("Checking packages cache and packages integrity ...")
420         for po in dlpkgs:
421             # Check if it is cached locally
422             if po.name() in localpkgs:
423                 cached_count += 1
424             else:
425                 local = self.getLocalPkgPath(po)
426                 if os.path.exists(local):
427                     if self.checkPkg(local) != 0:
428                         os.unlink(local)
429                     else:
430                         download_total_size -= int(po.downloadSize())
431                         cached_count += 1
432         cache_avail_size = misc.get_filesystem_avail(self.cachedir)
433         if cache_avail_size < download_total_size:
434             raise CreatorError("No enough space used for downloading.")
435
436         # record the total size of installed pkgs
437         install_total_size = sum(map(lambda x: int(x.installSize()), dlpkgs))
438         # check needed size before actually download and install
439         if checksize and install_total_size > checksize:
440             raise CreatorError("No enough space used for installing, "
441                                "please resize partition size in ks file")
442
443         download_count =  total_count - cached_count
444         msger.info("%d packages to be installed, "
445                    "%d packages gotten from cache, "
446                    "%d packages to be downloaded" \
447                    % (total_count, cached_count, download_count))
448
449         try:
450             if download_count > 0:
451                 msger.info("Downloading packages ...")
452             self.downloadPkgs(dlpkgs, download_count)
453
454             self.installPkgs(dlpkgs)
455
456         except RepoError, e:
457             raise CreatorError("Unable to download from repo : %s" % (e,))
458         except RpmError, e:
459             raise CreatorError("Unable to install: %s" % (e,))
460
461     def getAllContent(self):
462         return self.__pkgs_content
463
464     def getPkgsLicense(self):
465         return self.__pkgs_license
466
467     def __initialize_repo_manager(self):
468         if self.repo_manager:
469             return
470
471         # Clean up repo metadata
472         shutil.rmtree(self.cachedir + "/etc", ignore_errors = True)
473         shutil.rmtree(self.cachedir + "/solv", ignore_errors = True)
474         shutil.rmtree(self.cachedir + "/raw", ignore_errors = True)
475
476         zypp.KeyRing.setDefaultAccept( zypp.KeyRing.ACCEPT_UNSIGNED_FILE
477                                      | zypp.KeyRing.ACCEPT_VERIFICATION_FAILED
478                                      | zypp.KeyRing.ACCEPT_UNKNOWNKEY
479                                      | zypp.KeyRing.TRUST_KEY_TEMPORARILY
480                                      )
481
482         self.repo_manager_options = \
483                 zypp.RepoManagerOptions(zypp.Pathname(self.instroot))
484
485         self.repo_manager_options.knownReposPath = \
486                 zypp.Pathname(self.cachedir + "/etc/zypp/repos.d")
487
488         self.repo_manager_options.repoCachePath = \
489                 zypp.Pathname(self.cachedir)
490
491         self.repo_manager_options.repoRawCachePath = \
492                 zypp.Pathname(self.cachedir + "/raw")
493
494         self.repo_manager_options.repoSolvCachePath = \
495                 zypp.Pathname(self.cachedir + "/solv")
496
497         self.repo_manager_options.repoPackagesCachePath = \
498                 zypp.Pathname(self.cachedir + "/packages")
499
500         self.repo_manager = zypp.RepoManager(self.repo_manager_options)
501
502     def __build_repo_cache(self, name):
503         repo = self.repo_manager.getRepositoryInfo(name)
504         if self.repo_manager.isCached(repo) or not repo.enabled():
505             return
506
507         self.repo_manager.buildCache(repo, zypp.RepoManager.BuildIfNeeded)
508
509     def __initialize_zypp(self):
510         if self.Z:
511             return
512
513         zconfig = zypp.ZConfig_instance()
514
515         # Set system architecture
516         if self.target_arch:
517             zconfig.setSystemArchitecture(zypp.Arch(self.target_arch))
518
519         msger.info("zypp architecture is <%s>" % zconfig.systemArchitecture())
520
521         # repoPackagesCachePath is corrected by this
522         self.repo_manager = zypp.RepoManager(self.repo_manager_options)
523         repos = self.repo_manager.knownRepositories()
524         for repo in repos:
525             if not repo.enabled():
526                 continue
527             self.repo_manager.loadFromCache(repo)
528
529         self.Z = zypp.ZYppFactory_instance().getZYpp()
530         self.Z.initializeTarget(zypp.Pathname(self.instroot))
531         self.Z.target().load()
532
533     def buildTransaction(self):
534         if not self.Z.resolver().resolvePool():
535             msger.warning("Problem count: %d" \
536                           % len(self.Z.resolver().problems()))
537
538             for problem in self.Z.resolver().problems():
539                 msger.warning("Problem: %s, %s" \
540                               % (problem.description().decode("utf-8"),
541                                  problem.details().decode("utf-8")))
542
543     def getLocalPkgPath(self, po):
544         repoinfo = po.repoInfo()
545         cacheroot = repoinfo.packagesPath()
546         location= zypp.asKindPackage(po).location()
547         rpmpath = str(location.filename())
548         pkgpath = "%s/%s" % (cacheroot, rpmpath)
549         return pkgpath
550
551     def installLocal(self, pkg, po=None, updateonly=False):
552         if not self.ts:
553             self.__initialize_transaction()
554
555         solvfile = "%s/.solv" % (self.cachedir)
556
557         rc, out = runner.runtool([fs_related.find_binary_path("rpms2solv"),
558                                   pkg])
559         if rc == 0:
560             f = open(solvfile, "w+")
561             f.write(out)
562             f.close()
563
564             warnmsg = self.repo_manager.loadSolvFile(solvfile,
565                                                      os.path.basename(pkg))
566             if warnmsg:
567                 msger.warning(warnmsg)
568
569             os.unlink(solvfile)
570         else:
571             msger.warning('Can not get %s solv data.' % pkg)
572
573         hdr = rpmmisc.readRpmHeader(self.ts, pkg)
574         arch = zypp.Arch(hdr['arch'])
575         sysarch = zypp.Arch(self.target_arch)
576
577         if arch.compatible_with (sysarch):
578             pkgname = hdr['name']
579             self.localpkgs[pkgname] = pkg
580             self.selectPackage(pkgname)
581             msger.info("Marking %s to be installed" % (pkg))
582
583         else:
584             msger.warning("Cannot add package %s to transaction. "
585                           "Not a compatible architecture: %s" \
586                           % (pkg, hdr['arch']))
587
588     def downloadPkgs(self, package_objects, count):
589         localpkgs = self.localpkgs.keys()
590         progress_obj = rpmmisc.TextProgress(count)
591
592         for po in package_objects:
593             if po.name() in localpkgs:
594                 continue
595
596             filename = self.getLocalPkgPath(po)
597             if os.path.exists(filename):
598                 if self.checkPkg(filename) == 0:
599                     continue
600
601             dirn = os.path.dirname(filename)
602             if not os.path.exists(dirn):
603                 os.makedirs(dirn)
604
605             baseurl = str(po.repoInfo().baseUrls()[0])
606             index = baseurl.find("?")
607             if index > -1:
608                 baseurl = baseurl[:index]
609
610             proxy = self.get_proxy(po.repoInfo())
611             proxies = {}
612             if proxy:
613                 proxies = {str(proxy.split(":")[0]):str(proxy)}
614
615             location = zypp.asKindPackage(po).location()
616             location = str(location.filename())
617             if location.startswith("./"):
618                 location = location[2:]
619
620             url = baseurl + "/%s" % location
621
622             try:
623                 filename = rpmmisc.myurlgrab(url,
624                                              filename,
625                                              proxies,
626                                              progress_obj)
627             except CreatorError:
628                 self.close()
629                 raise
630
631     def installPkgs(self, package_objects):
632         if not self.ts:
633             self.__initialize_transaction()
634
635         # Set filters
636         probfilter = 0
637         for flag in self.probFilterFlags:
638             probfilter |= flag
639         self.ts.setProbFilter(probfilter)
640
641         localpkgs = self.localpkgs.keys()
642
643         for po in package_objects:
644             pkgname = po.name()
645             if pkgname in localpkgs:
646                 rpmpath = self.localpkgs[pkgname]
647             else:
648                 rpmpath = self.getLocalPkgPath(po)
649
650             if not os.path.exists(rpmpath):
651                 # Maybe it is a local repo
652                 baseurl = str(po.repoInfo().baseUrls()[0])
653                 baseurl = baseurl.strip()
654
655                 location = zypp.asKindPackage(po).location()
656                 location = str(location.filename())
657
658                 if baseurl.startswith("file:/"):
659                     rpmpath = baseurl[5:] + "/%s" % (location)
660
661             if not os.path.exists(rpmpath):
662                 raise RpmError("Error: %s doesn't exist" % rpmpath)
663
664             h = rpmmisc.readRpmHeader(self.ts, rpmpath)
665             self.ts.addInstall(h, rpmpath, 'u')
666
667         unresolved_dependencies = self.ts.check()
668         if not unresolved_dependencies:
669             self.ts.order()
670             cb = rpmmisc.RPMInstallCallback(self.ts)
671             installlogfile = "%s/__catched_stderr.buf" % (self.instroot)
672
673             # start to catch stderr output from librpm
674             msger.enable_logstderr(installlogfile)
675
676             errors = self.ts.run(cb.callback, '')
677             if errors is None:
678                 pass
679
680             elif len(errors) == 0:
681                 msger.warning('scriptlet or other non-fatal errors occurred '
682                               'during transaction.')
683
684             else:
685                 for e in errors:
686                     msger.warning(e[0])
687                 msger.error('Could not run transaction.')
688
689             # stop catch
690             msger.disable_logstderr()
691
692             self.ts.closeDB()
693             self.ts = None
694
695         else:
696             for pkg, need, needflags, sense, key in unresolved_dependencies:
697                 package = '-'.join(pkg)
698
699                 if needflags == rpm.RPMSENSE_LESS:
700                     deppkg = ' < '.join(need)
701                 elif needflags == rpm.RPMSENSE_EQUAL:
702                     deppkg = ' = '.join(need)
703                 elif needflags == rpm.RPMSENSE_GREATER:
704                     deppkg = ' > '.join(need)
705                 else:
706                     deppkg = '-'.join(need)
707
708                 if sense == rpm.RPMDEP_SENSE_REQUIRES:
709                     msger.warning("[%s] Requires [%s], which is not provided" \
710                                   % (package, deppkg))
711
712                 elif sense == rpm.RPMDEP_SENSE_CONFLICTS:
713                     msger.warning("[%s] Conflicts with [%s]" %(package,deppkg))
714
715             raise RepoError("Unresolved dependencies, transaction failed.")
716
717     def __initialize_transaction(self):
718         if not self.ts:
719             self.ts = rpm.TransactionSet(self.instroot)
720             # Set to not verify DSA signatures.
721             self.ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES|rpm._RPMVSF_NODIGESTS)
722
723     def checkPkg(self, pkg):
724         ret = 1
725         if not os.path.exists(pkg):
726             return ret
727         ret = rpmmisc.checkRpmIntegrity('rpm', pkg)
728         if ret != 0:
729             msger.warning("package %s is damaged: %s" \
730                           % (os.path.basename(pkg), pkg))
731
732         return ret
733
734     def _add_prob_flags(self, *flags):
735         for flag in flags:
736            if flag not in self.probFilterFlags:
737                self.probFilterFlags.append(flag)
738
739     def get_proxy(self, repoinfo):
740         proxy = None
741         reponame = "%s" % repoinfo.name()
742         for repo in self.repos:
743             if repo.name == reponame:
744                 proxy = repo.proxy
745                 break
746
747         if proxy:
748             return proxy
749         else:
750             repourl = str(repoinfo.baseUrls()[0])
751             return get_proxy_for(repourl)