Fix pylint error, reimport glob
[tools/mic.git] / plugins / backend / zypppkgmgr.py
1 #!/usr/bin/python3 -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 urllib.parse
21 import rpm
22 import glob
23
24 import zypp #pylint: disable=import-error
25 if not hasattr(zypp, 'PoolQuery') or \
26    not hasattr(zypp.RepoManager, 'loadSolvFile'):
27     raise ImportError("python-zypp in host system cannot support PoolQuery or "
28                       "loadSolvFile interface, please update it to enhanced "
29                       "version which can be found in download.tizen.org/tools")
30
31 from mic import msger
32 from mic.kickstart import ksparser
33 from mic.utils import misc, rpmmisc, runner, fs_related
34 from mic.utils.grabber import myurlgrab, TextProgress
35 from mic.utils.proxy import get_proxy_for
36 from mic.utils.errors import CreatorError, RepoError, RpmError
37 from mic.conf import configmgr
38
39 class RepositoryStub:
40     def __init__(self):
41         self.name = None
42         self.baseurl = []
43         self.mirrorlist = None
44         self.proxy = None
45         self.proxy_username = None
46         self.proxy_password = None
47         self.nocache = False
48
49         self.enabled = True
50         self.autorefresh = True
51         self.keeppackages = True
52         self.priority = None
53
54 from mic.pluginbase import BackendPlugin
55 class Zypp(BackendPlugin):
56     name = 'zypp'
57
58     def __init__(self, target_arch, instroot, cachedir, strict_mode = False):
59         self.cachedir = cachedir
60         self.instroot  = instroot
61         self.target_arch = target_arch
62         self.strict_mode = strict_mode
63
64         self.__pkgs_license = {}
65         self.__pkgs_content = {}
66         self.__pkgs_vcsinfo = {}
67         self.repos = []
68         self.to_deselect = []
69         self.localpkgs = {}
70         self.repo_manager = None
71         self.repo_manager_options = None
72         self.Z = None
73         self.ts = None
74         self.ts_pre = None
75         self.incpkgs = {}
76         self.excpkgs = {}
77         self.pre_pkgs = []
78         self.check_pkgs = []
79         self.probFilterFlags = [ rpm.RPMPROB_FILTER_OLDPACKAGE,
80                                  rpm.RPMPROB_FILTER_REPLACEPKG ]
81
82         self.has_prov_query = True
83         self.install_debuginfo = False
84         # this can't be changed, it is used by zypp
85         self.tmp_file_path = '/var/tmp'
86
87     def doFileLogSetup(self, uid, logfile):
88         # don't do the file log for the livecd as it can lead to open fds
89         # being left and an inability to clean up after ourself
90         pass
91
92     def closeRpmDB(self):
93         pass
94
95     def close(self):
96         if self.ts:
97             self.ts.closeDB()
98             self.ts = None
99
100         if self.ts_pre:
101             self.ts_pre.closeDB()
102             self.ts = None
103
104         self.closeRpmDB()
105
106     def __del__(self):
107         self.close()
108
109     def _cleanupRpmdbLocks(self, installroot):
110         # cleans up temporary files left by bdb so that differing
111         # versions of rpm don't cause problems
112         for f in glob.glob(installroot + "/var/lib/rpm/__db*"):
113             os.unlink(f)
114
115     def _cleanupZyppJunk(self, installroot):
116         try:
117             shutil.rmtree(os.path.join(installroot, '.zypp'))
118         except:
119             pass
120
121     def setup(self):
122         self._cleanupRpmdbLocks(self.instroot)
123         # '/var/tmp' is used by zypp to build cache, so make sure
124         # if it exists
125         if not os.path.exists(self.tmp_file_path ):
126             os.makedirs(self.tmp_file_path)
127
128     def whatObsolete(self, pkg):
129         query = zypp.PoolQuery()
130         query.addKind(zypp.ResKind.package)
131         query.addDependency(zypp.SolvAttr.obsoletes, pkg.name(), pkg.edition())
132         query.setMatchExact()
133         for pi in query.queryResults(self.Z.pool()):
134             return pi
135         return None
136
137     def _zyppQueryPackage(self, pkg):
138         query = zypp.PoolQuery()
139         query.addKind(zypp.ResKind.package)
140         query.addAttribute(zypp.SolvAttr.name, pkg)
141         query.setMatchExact()
142         for pi in query.queryResults(self.Z.pool()):
143             return pi
144         return None
145
146     def _splitPkgString(self, pkg):
147         sp = pkg.rsplit(".", 1)
148         name = sp[0]
149         arch = None
150         if len(sp) == 2:
151             arch = sp[1]
152             sysarch = zypp.Arch(self.target_arch)
153             if not zypp.Arch(arch).compatible_with (sysarch):
154                 arch = None
155                 name = ".".join(sp)
156         return name, arch
157
158     def selectPackage(self, pkg):
159         """Select a given package or package pattern, can be specified
160         with name.arch or name* or *name
161         """
162
163         if not self.Z:
164             self.__initialize_zypp()
165
166         def markPoolItem(obs, pi):
167             if obs == None:
168                 pi.status().setToBeInstalled (zypp.ResStatus.USER)
169             else:
170                 obs.status().setToBeInstalled (zypp.ResStatus.USER)
171
172         def cmpEVR(p1, p2):
173             # compare criterion: arch compatibility first, then repo
174             # priority, and version last
175             a1 = p1.arch()
176             a2 = p2.arch()
177             if str(a1) != str(a2):
178                 if a1.compatible_with(a2):
179                     return -1
180                 else:
181                     return 1
182             # Priority of a repository is an integer value between 0 (the
183             # highest priority) and 99 (the lowest priority)
184             pr1 = int(p1.repoInfo().priority())
185             pr2 = int(p2.repoInfo().priority())
186             if pr1 > pr2:
187                 return -1
188             elif pr1 < pr2:
189                 return 1
190
191             ed1 = p1.edition()
192             ed2 = p2.edition()
193             (e1, v1, r1) = list(map(str, [ed1.epoch(), ed1.version(), ed1.release()]))
194             (e2, v2, r2) = list(map(str, [ed2.epoch(), ed2.version(), ed2.release()]))
195             return rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
196
197         found = False
198         startx = pkg.startswith("*")
199         endx = pkg.endswith("*")
200         ispattern = startx or endx
201         name, arch = self._splitPkgString(pkg)
202
203         q = zypp.PoolQuery()
204         q.addKind(zypp.ResKind.package)
205
206         if ispattern:
207             if startx and not endx:
208                 pattern = '%s$' % (pkg[1:])
209             if endx and not startx:
210                 pattern = '^%s' % (pkg[0:-1])
211             if endx and startx:
212                 pattern = '%s' % (pkg[1:-1])
213             q.setMatchRegex()
214             q.addAttribute(zypp.SolvAttr.name, pattern)
215
216         elif arch:
217             q.setMatchExact()
218             q.addAttribute(zypp.SolvAttr.name, name)
219
220         else:
221             q.setMatchExact()
222             q.addAttribute(zypp.SolvAttr.name, pkg)
223
224         for pitem in sorted(
225                         q.queryResults(self.Z.pool()),
226                         cmp=lambda x,y: cmpEVR(zypp.asKindPackage(x), zypp.asKindPackage(y)),
227                         reverse=True):
228             item = zypp.asKindPackage(pitem)
229             if item.name() in list(self.excpkgs.keys()) and \
230                self.excpkgs[item.name()] == item.repoInfo().name():
231                 continue
232             if item.name() in list(self.incpkgs.keys()) and \
233                self.incpkgs[item.name()] != item.repoInfo().name():
234                 continue
235
236             found = True
237             obspkg = self.whatObsolete(item)
238             if arch:
239                 if arch == str(item.arch()):
240                     pitem.status().setToBeInstalled (zypp.ResStatus.USER)
241             else:
242                 markPoolItem(obspkg, pitem)
243             if not ispattern:
244                 break
245
246         # Can't match using package name, then search from packge
247         # provides infomation
248         if found == False and not ispattern:
249             q.addAttribute(zypp.SolvAttr.provides, pkg)
250             q.addAttribute(zypp.SolvAttr.name,'')
251
252             for pitem in sorted(
253                             q.queryResults(self.Z.pool()),
254                             cmp=lambda x,y: cmpEVR(zypp.asKindPackage(x), zypp.asKindPackage(y)),
255                             reverse=True):
256                 item = zypp.asKindPackage(pitem)
257                 if item.name() in list(self.excpkgs.keys()) and \
258                    self.excpkgs[item.name()] == item.repoInfo().name():
259                     continue
260                 if item.name() in list(self.incpkgs.keys()) and \
261                    self.incpkgs[item.name()] != item.repoInfo().name():
262                     continue
263
264                 found = True
265                 obspkg = self.whatObsolete(item)
266                 markPoolItem(obspkg, pitem)
267                 break
268
269         if found:
270             return None
271         else:
272             raise CreatorError("Unable to find package: %s" % (pkg,))
273
274     def inDeselectPackages(self, pitem):
275         """check if specified pacakges are in the list of inDeselectPackages
276         """
277         item = zypp.asKindPackage(pitem)
278         name = item.name()
279         for pkg in self.to_deselect:
280             startx = pkg.startswith("*")
281             endx = pkg.endswith("*")
282             ispattern = startx or endx
283             pkgname, pkgarch = self._splitPkgString(pkg)
284             if not ispattern:
285                 if pkgarch:
286                     if name == pkgname and str(item.arch()) == pkgarch:
287                         return True
288                 else:
289                     if name == pkgname:
290                         return True
291             else:
292                 if startx and name.endswith(pkg[1:]):
293                     return True
294                 if endx and name.startswith(pkg[:-1]):
295                     return True
296
297         return False
298
299     def deselectPackage(self, pkg):
300         """collect packages should not be installed"""
301         self.to_deselect.append(pkg)
302
303     def selectGroup(self, grp, include = ksparser.GROUP_DEFAULT):
304         def compareGroup(pitem):
305             item = zypp.asKindPattern(pitem)
306             return item.repoInfo().priority()
307         if not self.Z:
308             self.__initialize_zypp()
309         found = False
310         q = zypp.PoolQuery()
311         q.addKind(zypp.ResKind.pattern)
312         for pitem in sorted(q.queryResults(self.Z.pool()), key=compareGroup):
313             item = zypp.asKindPattern(pitem)
314             summary = "%s" % item.summary()
315             name = "%s" % item.name()
316             if name == grp or summary == grp:
317                 found = True
318                 pitem.status().setToBeInstalled (zypp.ResStatus.USER)
319                 break
320
321         if found:
322             if include == ksparser.GROUP_REQUIRED:
323                 list([self.deselectPackage(p) for p in list(grp.default_packages.keys())])
324
325             return None
326         else:
327             raise CreatorError("Unable to find pattern: %s" % (grp,))
328
329     def addRepository(self, name,
330                             url = None,
331                             mirrorlist = None,
332                             proxy = None,
333                             proxy_username = None,
334                             proxy_password = None,
335                             inc = None,
336                             exc = None,
337                             ssl_verify = True,
338                             nocache = False,
339                             cost=None,
340                             priority=None):
341         # TODO: Handle cost attribute for repos
342
343         if not self.repo_manager:
344             self.__initialize_repo_manager()
345
346         if not proxy and url:
347             proxy = get_proxy_for(url)
348
349         repo = RepositoryStub()
350         repo.name = name
351         repo.id = name
352         repo.proxy = proxy
353         repo.proxy_username = proxy_username
354         repo.proxy_password = proxy_password
355         repo.ssl_verify = ssl_verify
356         repo.nocache = nocache
357         repo.baseurl.append(url)
358         if inc:
359             for pkg in inc:
360                 self.incpkgs[pkg] = name
361         if exc:
362             for pkg in exc:
363                 self.excpkgs[pkg] = name
364
365         if mirrorlist:
366             repo.mirrorlist = mirrorlist
367
368         # Enable gpg check for verifying corrupt packages
369         repo.gpgcheck = 1
370         if priority is not None:
371             # priority 0 has issue in RepoInfo.setPriority
372             repo.priority = priority + 1
373
374         try:
375             repo_info = zypp.RepoInfo()
376             repo_info.setAlias(repo.name)
377             repo_info.setName(repo.name)
378             repo_info.setEnabled(repo.enabled)
379             repo_info.setAutorefresh(repo.autorefresh)
380             repo_info.setKeepPackages(repo.keeppackages)
381             baseurl = zypp.Url(repo.baseurl[0].full)
382             if not ssl_verify:
383                 baseurl.setQueryParam("ssl_verify", "no")
384             if proxy:
385                 host = urllib.parse.urlparse(proxy)[1]
386                 # scheme, host, path, parm, query, frag = urlparse.urlparse(proxy)
387
388                 proxyinfo = host.rsplit(":", 1)
389                 host = proxyinfo[0]
390
391                 port = "80"
392                 if len(proxyinfo) > 1:
393                     port = proxyinfo[1]
394
395                 if proxy.startswith("socks") and len(proxy.rsplit(':', 1)) == 2:
396                     host = proxy.rsplit(':', 1)[0]
397                     port = proxy.rsplit(':', 1)[1]
398
399                 # parse user/pass from proxy host
400                 proxyinfo = host.rsplit("@", 1)
401                 if len(proxyinfo) == 2:
402                     host = proxyinfo[1]
403                     # Known Issue: If password contains ":", which should be
404                     # quoted, for example, use '123%3Aabc' instead of 123:abc
405                     userpassinfo = proxyinfo[0].rsplit(":", 1)
406                     if len(userpassinfo) == 2:
407                         proxy_username = userpassinfo[0]
408                         proxy_password = userpassinfo[1]
409                     elif len(userpassinfo) == 1:
410                         proxy_username = userpassinfo[0]
411
412                 baseurl.setQueryParam ("proxy", host)
413                 baseurl.setQueryParam ("proxyport", port)
414                 if proxy_username:
415                     baseurl.setQueryParam ("proxyuser", proxy_username)
416                 if proxy_password:
417                     baseurl.setQueryParam ("proxypass", proxy_password)
418             else:
419                 baseurl.setQueryParam ("proxy", "_none_")
420
421             self.repos.append(repo)
422
423             repo_info.addBaseUrl(baseurl)
424
425             if repo.priority is not None:
426                 repo_info.setPriority(repo.priority)
427
428             # this hack is used to change zypp credential file location
429             # the default one is $HOME/.zypp, which cause conflicts when
430             # installing some basic packages, and the location doesn't
431             # have any interface actually, so use a tricky way anyway
432             homedir = None
433             if 'HOME' in os.environ:
434                 homedir = os.environ['HOME']
435                 os.environ['HOME'] = '/'
436             else:
437                 os.environ['HOME'] = '/'
438
439             self.repo_manager.addRepository(repo_info)
440
441             # save back the $HOME env
442             if homedir:
443                 os.environ['HOME'] = homedir
444             else:
445                 del os.environ['HOME']
446
447             self.__build_repo_cache(name)
448
449         except RuntimeError as e:
450             raise CreatorError(str(e))
451
452         msger.verbose('repo: %s was added' % name)
453         return repo
454
455     def installHasFile(self, file):
456         return False
457
458     def preInstall(self, pkg):
459         self.pre_pkgs.append(pkg)
460
461     def checkPackage(self, pkg):
462         self.check_pkgs.append(pkg)
463
464     def _get_local_packages(self):
465         """Return a list of rpm path to be local installed.
466         This is the hook where subclasses may specify a set of rpms which
467         it requires to be installed locally.
468         This returns an empty list by default.
469         Note, subclasses should usually chain up to the base class
470         implementation of this hook.
471         """
472         cropts = configmgr.create
473         if cropts['local_pkgs_path']:
474             if os.path.isdir(cropts['local_pkgs_path']):
475                 return glob.glob(
476                         os.path.join(cropts['local_pkgs_path'], '*.rpm'))
477             elif os.path.splitext(cropts['local_pkgs_path'])[-1] == '.rpm':
478                 return [cropts['local_pkgs_path']]
479         return []
480     def __localinst_packages(self):
481         for rpm_path in self._get_local_packages():
482             self.installLocal(rpm_path)
483     def runInstall(self, checksize = 0):
484         os.environ["HOME"] = "/"
485         os.environ["LD_PRELOAD"] = ""
486         self.buildTransaction()
487         self.__localinst_packages()
488
489         todo = zypp.GetResolvablesToInsDel(self.Z.pool())
490         installed_pkgs = todo._toInstall
491         dlpkgs = []
492
493         for pitem in installed_pkgs:
494             if not zypp.isKindPattern(pitem) and \
495               not self.inDeselectPackages(pitem):
496                 item = zypp.asKindPackage(pitem)
497                 dlpkgs.append(item)
498
499                 if item.name() in self.check_pkgs:
500                     self.check_pkgs.remove(item.name())
501
502                 if not self.install_debuginfo or str(item.arch()) == "noarch":
503                     continue
504
505                 dipkg = self._zyppQueryPackage("%s-debuginfo" % item.name())
506                 if dipkg:
507                     ditem = zypp.asKindPackage(dipkg)
508                     dlpkgs.append(ditem)
509                 else:
510                     msger.warning("No debuginfo rpm found for: %s" \
511                                   % item.name())
512
513         if self.check_pkgs:
514             raise CreatorError('Packages absent in image: %s' % ','.join(self.check_pkgs))
515
516         # record all pkg and the content
517         localpkgs = list(self.localpkgs.keys())
518         for pkg in dlpkgs:
519             license = ''
520             if pkg.name() in localpkgs:
521                 hdr = rpmmisc.readRpmHeader(self.ts, self.localpkgs[pkg.name()])
522                 pkg_long_name = misc.RPM_FMT % {
523                                     'name': hdr['name'],
524                                     'arch': hdr['arch'],
525                                     'version': hdr['version'],
526                                     'release': hdr['release']
527                                 }
528                 license = hdr['license']
529
530             else:
531                 pkg_long_name = misc.RPM_FMT % {
532                                     'name': pkg.name(),
533                                     'arch': pkg.arch(),
534                                     'version': pkg.edition().version(),
535                                     'release': pkg.edition().release()
536                                 }
537
538                 license = pkg.license()
539
540             if license in list(self.__pkgs_license.keys()):
541                 self.__pkgs_license[license].append(pkg_long_name)
542             else:
543                 self.__pkgs_license[license] = [pkg_long_name]
544
545         total_count = len(dlpkgs)
546         cached_count = 0
547         download_total_size = sum([int(x.downloadSize()) for x in dlpkgs])
548         localpkgs = list(self.localpkgs.keys())
549
550         msger.info("Checking packages cached ...")
551         for po in dlpkgs:
552             # Check if it is cached locally
553             if po.name() in localpkgs:
554                 cached_count += 1
555             else:
556                 local = self.getLocalPkgPath(po)
557                 name = str(po.repoInfo().name())
558                 try:
559                     repo = filter(lambda r: r.name == name, self.repos)[0]
560                 except IndexError:
561                     repo = None
562                 nocache = repo.nocache if repo else False
563
564                 if os.path.exists(local):
565                     if nocache or self.checkPkg(local) !=0:
566                         os.unlink(local)
567                     else:
568                         download_total_size -= int(po.downloadSize())
569                         cached_count += 1
570         cache_avail_size = misc.get_filesystem_avail(self.cachedir)
571         if cache_avail_size < download_total_size:
572             raise CreatorError("No enough space used for downloading.")
573
574         # record the total size of installed pkgs
575         install_total_size = sum([int(x.installSize()) for x in dlpkgs])
576         # check needed size before actually download and install
577
578         # FIXME: for multiple partitions for loop type, check fails
579         #        skip the check temporarily
580         #if checksize and install_total_size > checksize:
581         #    raise CreatorError("No enough space used for installing, "
582         #                       "please resize partition size in ks file")
583
584         download_count =  total_count - cached_count
585         msger.info("Packages: %d Total, %d Cached, %d Missed" \
586                    % (total_count, cached_count, download_count))
587
588         try:
589             if download_count > 0:
590                 msger.info("Downloading packages ...")
591             self.downloadPkgs(dlpkgs, download_count)
592         except CreatorError as e:
593             raise CreatorError("Package download failed: %s" %(e,))
594
595         try:
596             self.installPkgs(dlpkgs)
597         except (RepoError, RpmError):
598             raise
599         except Exception as e:
600             raise CreatorError("Package installation failed: %s" % (e,))
601
602     def getVcsInfo(self):
603         if self.__pkgs_vcsinfo:
604             return
605
606         if not self.ts:
607             self.__initialize_transaction()
608
609         mi = self.ts.dbMatch()
610         for hdr in mi:
611             lname = misc.RPM_FMT % {
612                         'name': hdr['name'],
613                         'arch': hdr['arch'],
614                         'version': hdr['version'],
615                         'release': hdr['release']
616                     }
617             try:
618                 self.__pkgs_vcsinfo[lname] = hdr['VCS']
619             except ValueError:
620                 # if rpm not support VCS, set to None
621                 self.__pkgs_vcsinfo[lname] = None
622
623         return self.__pkgs_vcsinfo
624
625     def getAllContent(self):
626         if self.__pkgs_content:
627             return self.__pkgs_content
628
629         if not self.ts:
630             self.__initialize_transaction()
631
632         mi = self.ts.dbMatch()
633         for hdr in mi:
634             lname = misc.RPM_FMT % {
635                         'name': hdr['name'],
636                         'arch': hdr['arch'],
637                         'version': hdr['version'],
638                         'release': hdr['release']
639                     }
640             self.__pkgs_content[lname] = hdr['FILENAMES']
641
642         return self.__pkgs_content
643
644     def getPkgsLicense(self):
645         return self.__pkgs_license
646
647     def getFilelist(self, pkgname):
648         if not pkgname:
649             return None
650
651         if not self.ts:
652             self.__initialize_transaction()
653
654         mi = self.ts.dbMatch('name', pkgname)
655         for header in mi:
656             return header['FILENAMES']
657
658     def __initialize_repo_manager(self):
659         if self.repo_manager:
660             return
661
662         # Clean up repo metadata
663         shutil.rmtree(self.cachedir + "/etc", ignore_errors = True)
664         shutil.rmtree(self.cachedir + "/solv", ignore_errors = True)
665         shutil.rmtree(self.cachedir + "/raw", ignore_errors = True)
666
667         zypp.KeyRing.setDefaultAccept( zypp.KeyRing.ACCEPT_UNSIGNED_FILE
668                                      | zypp.KeyRing.ACCEPT_VERIFICATION_FAILED
669                                      | zypp.KeyRing.ACCEPT_UNKNOWNKEY
670                                      | zypp.KeyRing.TRUST_KEY_TEMPORARILY
671                                      )
672
673         self.repo_manager_options = \
674                 zypp.RepoManagerOptions(zypp.Pathname(self.instroot))
675
676         self.repo_manager_options.knownReposPath = \
677                 zypp.Pathname(self.cachedir + "/etc/zypp/repos.d")
678
679         self.repo_manager_options.repoCachePath = \
680                 zypp.Pathname(self.cachedir)
681
682         self.repo_manager_options.repoRawCachePath = \
683                 zypp.Pathname(self.cachedir + "/raw")
684
685         self.repo_manager_options.repoSolvCachePath = \
686                 zypp.Pathname(self.cachedir + "/solv")
687
688         self.repo_manager_options.repoPackagesCachePath = \
689                 zypp.Pathname(self.cachedir + "/packages")
690
691         self.repo_manager = zypp.RepoManager(self.repo_manager_options)
692
693     def __build_repo_cache(self, name):
694         repo = self.repo_manager.getRepositoryInfo(name)
695         if self.repo_manager.isCached(repo) or not repo.enabled():
696             return
697
698         msger.info('Refreshing repository: %s ...' % name)
699         self.repo_manager.buildCache(repo, zypp.RepoManager.BuildIfNeeded)
700
701     def __initialize_zypp(self):
702         if self.Z:
703             return
704
705         zconfig = zypp.ZConfig_instance()
706
707         # Set system architecture
708         if self.target_arch:
709             zconfig.setSystemArchitecture(zypp.Arch(self.target_arch))
710
711         msger.info("zypp architecture is <%s>" % zconfig.systemArchitecture())
712
713         # repoPackagesCachePath is corrected by this
714         self.repo_manager = zypp.RepoManager(self.repo_manager_options)
715         repos = self.repo_manager.knownRepositories()
716         for repo in repos:
717             if not repo.enabled():
718                 continue
719             self.repo_manager.loadFromCache(repo)
720
721         self.Z = zypp.ZYppFactory_instance().getZYpp()
722         self.Z.initializeTarget(zypp.Pathname(self.instroot))
723         self.Z.target().load()
724
725     def buildTransaction(self):
726         if not self.Z.resolver().resolvePool():
727             probs = self.Z.resolver().problems()
728
729             for problem in probs:
730                 msger.warning("repo problem: %s, %s" \
731                               % (problem.description().decode("utf-8"),
732                                  problem.details().decode("utf-8")))
733
734             raise RepoError("found %d resolver problem, abort!" \
735                             % len(probs))
736
737     def getLocalPkgPath(self, po):
738         repoinfo = po.repoInfo()
739         cacheroot = repoinfo.packagesPath()
740         location = po.location()
741         rpmpath = str(location.filename())
742         pkgpath = "%s/%s" % (cacheroot, os.path.basename(rpmpath))
743         return pkgpath
744
745     def installLocal(self, pkg, po=None, updateonly=False):
746         if not self.ts:
747             self.__initialize_transaction()
748
749         solvfile = "%s/.solv" % (self.cachedir)
750
751         rc, out = runner.runtool([fs_related.find_binary_path("rpms2solv"),
752                                   pkg])
753         if rc == 0:
754             f = open(solvfile, "w+")
755             f.write(out)
756             f.close()
757
758             warnmsg = self.repo_manager.loadSolvFile(solvfile,
759                                                      os.path.basename(pkg))
760             if warnmsg:
761                 msger.warning(warnmsg)
762
763             os.unlink(solvfile)
764         else:
765             msger.warning('Can not get %s solv data.' % pkg)
766
767         hdr = rpmmisc.readRpmHeader(self.ts, pkg)
768         arch = zypp.Arch(hdr['arch'])
769         sysarch = zypp.Arch(self.target_arch)
770
771         if arch.compatible_with (sysarch):
772             pkgname = hdr['name']
773             self.localpkgs[pkgname] = pkg
774             self.selectPackage(pkgname)
775             msger.info("Marking %s to be installed" % (pkg))
776
777         else:
778             msger.warning("Cannot add package %s to transaction. "
779                           "Not a compatible architecture: %s" \
780                           % (pkg, hdr['arch']))
781
782     def downloadPkgs(self, package_objects, count):
783         localpkgs = list(self.localpkgs.keys())
784         progress_obj = TextProgress(count)
785
786         for po in package_objects:
787             if po.name() in localpkgs:
788                 continue
789
790             filename = self.getLocalPkgPath(po)
791             if os.path.exists(filename):
792                 if self.checkPkg(filename) == 0:
793                     continue
794
795             dirn = os.path.dirname(filename)
796             if not os.path.exists(dirn):
797                 os.makedirs(dirn)
798
799             url = self.get_url(po)
800             proxies = self.get_proxies(po)
801
802             try:
803                 filename = myurlgrab(url.full, filename, proxies, progress_obj)
804             except CreatorError:
805                 self.close()
806                 raise
807
808     def preinstallPkgs(self):
809         if not self.ts_pre:
810             self.__initialize_transaction()
811
812         self.ts_pre.order()
813         cb = rpmmisc.RPMInstallCallback(self.ts_pre)
814         cb.headmsg = "Preinstall"
815         installlogfile = "%s/__catched_stderr.buf" % (self.instroot)
816
817         # start to catch stderr output from librpm
818         msger.enable_logstderr(installlogfile)
819
820         errors = self.ts_pre.run(cb.callback, '')
821         # stop catch
822         msger.disable_logstderr()
823         self.ts_pre.closeDB()
824         self.ts_pre = None
825
826         if errors is not None:
827             if len(errors) == 0:
828                 msger.warning('scriptlet or other non-fatal errors occurred '
829                               'during transaction.')
830
831             else:
832                 for e in errors:
833                     msger.warning(e[0])
834                 raise RepoError('Could not run transaction.')
835     def show_unresolved_dependencies_msg(self, unresolved_dependencies):
836         for pkg, need, needflags, sense, key in unresolved_dependencies:
837
838             package = '-'.join(pkg)
839
840             if needflags == rpm.RPMSENSE_LESS:
841                 deppkg = ' < '.join(need)
842             elif needflags == rpm.RPMSENSE_EQUAL:
843                 deppkg = ' = '.join(need)
844             elif needflags == rpm.RPMSENSE_GREATER:
845                 deppkg = ' > '.join(need)
846             else:
847                 deppkg = '-'.join(need)
848
849             if sense == rpm.RPMDEP_SENSE_REQUIRES:
850                 msger.warning("[%s] Requires [%s], which is not provided" \
851                               % (package, deppkg))
852
853             elif sense == rpm.RPMDEP_SENSE_CONFLICTS:
854                 msger.warning("[%s] Conflicts with [%s]" % (package, deppkg))
855
856     def installPkgs(self, package_objects):
857         if not self.ts:
858             self.__initialize_transaction()
859
860         # clean rpm lock
861         self._cleanupRpmdbLocks(self.instroot)
862         self._cleanupZyppJunk(self.instroot)
863         # Set filters
864         probfilter = 0
865         for flag in self.probFilterFlags:
866             probfilter |= flag
867         self.ts.setProbFilter(probfilter)
868         self.ts_pre.setProbFilter(probfilter)
869
870         localpkgs = list(self.localpkgs.keys())
871
872         for po in package_objects:
873             pkgname = po.name()
874             if pkgname in localpkgs:
875                 rpmpath = self.localpkgs[pkgname]
876             else:
877                 rpmpath = self.getLocalPkgPath(po)
878
879             if not os.path.exists(rpmpath):
880                 # Maybe it is a local repo
881                 rpmuri = self.get_url(po)
882                 if rpmuri.startswith("file:/"):
883                     rpmpath = rpmuri[5:]
884
885             if not os.path.exists(rpmpath):
886                 raise RpmError("Error: %s doesn't exist" % rpmpath)
887
888             h = rpmmisc.readRpmHeader(self.ts, rpmpath)
889
890             if pkgname in self.pre_pkgs:
891                 msger.verbose("pre-install package added: %s" % pkgname)
892                 self.ts_pre.addInstall(h, rpmpath, 'u')
893
894             self.ts.addInstall(h, rpmpath, 'u')
895
896         unresolved_dependencies = self.ts.check()
897         if not unresolved_dependencies:
898             if self.pre_pkgs:
899                 self.preinstallPkgs()
900
901             self.ts.order()
902             cb = rpmmisc.RPMInstallCallback(self.ts)
903             installlogfile = "%s/__catched_stderr.buf" % (self.instroot)
904
905             # start to catch stderr output from librpm
906             msger.enable_logstderr(installlogfile)
907
908             errors = self.ts.run(cb.callback, '')
909             # stop catch
910             msger.disable_logstderr()
911             self.ts.closeDB()
912             self.ts = None
913
914             if errors is not None:
915                 if len(errors) == 0:
916                     msger.warning('scriptlet or other non-fatal errors occurred '
917                                   'during transaction.')
918                     if self.strict_mode:
919                         raise CreatorError("mic failes to install some packages")
920                 else:
921                     for e in errors:
922                         msger.warning(e[0])
923                     raise RepoError('Could not run transaction.')
924
925         else:
926             self.show_unresolved_dependencies_msg(unresolved_dependencies)
927             raise RepoError("Unresolved dependencies, transaction failed.")
928
929     def __initialize_transaction(self):
930         if not self.ts:
931             self.ts = rpm.TransactionSet(self.instroot)
932             # Set to not verify DSA signatures.
933             self.ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES|rpm._RPMVSF_NODIGESTS)
934
935         if not self.ts_pre:
936             self.ts_pre = rpm.TransactionSet(self.instroot)
937             # Just unpack the files, don't run scripts
938             self.ts_pre.setFlags(rpm.RPMTRANS_FLAG_ALLFILES | rpm.RPMTRANS_FLAG_NOSCRIPTS)
939             # Set to not verify DSA signatures.
940             self.ts_pre.setVSFlags(rpm._RPMVSF_NOSIGNATURES|rpm._RPMVSF_NODIGESTS)
941
942     def checkPkg(self, pkg):
943         ret = 1
944         if not os.path.exists(pkg):
945             return ret
946         ret = rpmmisc.checkRpmIntegrity('rpm', pkg)
947         if ret != 0:
948             msger.warning("package %s is damaged: %s" \
949                           % (os.path.basename(pkg), pkg))
950
951         return ret
952
953     def _add_prob_flags(self, *flags):
954         for flag in flags:
955             if flag not in self.probFilterFlags:
956                 self.probFilterFlags.append(flag)
957
958     def get_proxies(self, pobj):
959         if not pobj:
960             return None
961
962         proxy = None
963         proxies = None
964         repoinfo = pobj.repoInfo()
965         reponame = "%s" % repoinfo.name()
966         repos = [r for r in self.repos if r.name == reponame]
967         repourl = str(repoinfo.baseUrls()[0])
968
969         if repos:
970             proxy = repos[0].proxy
971         if not proxy:
972             proxy = get_proxy_for(repourl)
973         if proxy:
974             proxies = {str(repourl.split(':')[0]): str(proxy)}
975
976         return proxies
977
978     def get_url(self, pobj):
979         if not pobj:
980             return None
981
982         name = str(pobj.repoInfo().name())
983         try:
984             repo = filter(lambda r: r.name == name, self.repos)[0]
985         except IndexError:
986             return None
987
988         location = pobj.location()
989         location = str(location.filename())
990         if location.startswith("./"):
991             location = location[2:]
992
993         return repo.baseurl[0].join(location)
994
995     def package_url(self, pkgname):
996
997         def cmpEVR(p1, p2):
998             ed1 = p1.edition()
999             ed2 = p2.edition()
1000             (e1, v1, r1) = list(map(str, [ed1.epoch(), ed1.version(), ed1.release()]))
1001             (e2, v2, r2) = list(map(str, [ed2.epoch(), ed2.version(), ed2.release()]))
1002             return rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
1003
1004         if not self.Z:
1005             self.__initialize_zypp()
1006
1007         q = zypp.PoolQuery()
1008         q.addKind(zypp.ResKind.package)
1009         q.setMatchExact()
1010         q.addAttribute(zypp.SolvAttr.name, pkgname)
1011         items = sorted(q.queryResults(self.Z.pool()),
1012                        cmp=lambda x,y: cmpEVR(zypp.asKindPackage(x), zypp.asKindPackage(y)),
1013                        reverse=True)
1014
1015         if items:
1016             item = zypp.asKindPackage(items[0])
1017             url = self.get_url(item)
1018             proxies = self.get_proxies(item)
1019             return (url, proxies)
1020
1021         return (None, None)