3 # Copyright (c) 2007 Red Hat Inc.
4 # Copyright (c) 2010, 2011 Intel, Inc.
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the Free
8 # Software Foundation; version 2 of the License
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc., 59
17 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 from string import Template
28 from mic.kickstart import ksparser
29 from mic.utils import misc, rpmmisc
30 from mic.utils.grabber import TextProgress
31 from mic.utils.proxy import get_proxy_for
32 from mic.utils.errors import CreatorError
33 from mic.utils.safeurl import SafeURL
36 YUMCONF_TEMP = """[main]
37 installroot=$installroot
38 cachedir=/var/cache/yum
39 persistdir=/var/lib/yum
42 failovermethod=priority
47 class MyYumRepository(yum.yumRepo.YumRepository):
48 def __init__(self, repoid, nocache):
49 super(MyYumRepository, self).__init__(repoid)
50 self.nocache = nocache
56 super(MyYumRepository, self).dirSetup()
57 # relocate package dir
58 pkgdir = os.path.join(self.basecachedir, 'packages', self.id)
59 self.setAttribute('_dir_setup_pkgdir', pkgdir)
60 self._dirSetupMkdir_p(self.pkgdir)
62 def _getFile(self, url=None,
75 if not self.sslverify:
78 m2c_connection = M2Crypto.SSL.Connection.clientPostConnectionCheck
79 M2Crypto.SSL.Connection.clientPostConnectionCheck = None
80 except ImportError, err:
81 raise CreatorError("%s, please try to install python-m2crypto" % str(err))
85 proxy = get_proxy_for(url)
87 proxy = get_proxy_for(self.urls[0])
90 self.proxy = str(proxy)
92 size = int(size) if size else None
93 rvalue = super(MyYumRepository, self)._getFile(url,
105 if m2c_connection and \
106 not M2Crypto.SSL.Connection.clientPostConnectionCheck:
107 M2Crypto.SSL.Connection.clientPostConnectionCheck = m2c_connection
111 from mic.pluginbase import BackendPlugin
112 class Yum(BackendPlugin, yum.YumBase):
115 def __init__(self, target_arch, instroot, cachedir, strict_mode = False):
116 yum.YumBase.__init__(self)
118 self.cachedir = cachedir
119 self.instroot = instroot
120 self.target_arch = target_arch
121 self.strict_mode = strict_mode
124 if not rpmUtils.arch.arches.has_key(self.target_arch):
125 rpmUtils.arch.arches["armv7hl"] = "noarch"
126 rpmUtils.arch.arches["armv7tnhl"] = "armv7nhl"
127 rpmUtils.arch.arches["armv7tnhl"] = "armv7thl"
128 rpmUtils.arch.arches["armv7thl"] = "armv7hl"
129 rpmUtils.arch.arches["armv7nhl"] = "armv7hl"
130 self.arch.setup_arch(self.target_arch)
132 self.__pkgs_license = {}
133 self.__pkgs_content = {}
134 self.__pkgs_vcsinfo = {}
137 self.install_debuginfo = False
139 def doFileLogSetup(self, uid, logfile):
140 # don't do the file log for the livecd as it can lead to open fds
141 # being left and an inability to clean up after ourself
146 os.unlink(self.confpath)
147 os.unlink(self.conf.installroot + "/yum.conf")
155 yum.YumBase.close(self)
161 def _writeConf(self, confpath, installroot):
162 conf = Template(YUMCONF_TEMP).safe_substitute(installroot=installroot)
164 f = file(confpath, "w+")
168 os.chmod(confpath, 0644)
170 def _cleanupRpmdbLocks(self, installroot):
171 # cleans up temporary files left by bdb so that differing
172 # versions of rpm don't cause problems
173 for f in glob.glob(installroot + "/var/lib/rpm/__db*"):
178 (fn, self.confpath) = tempfile.mkstemp(dir=self.cachedir,
181 self._writeConf(self.confpath, self.instroot)
182 self._cleanupRpmdbLocks(self.instroot)
184 self.doConfigSetup(fn = self.confpath, root = self.instroot)
191 def preInstall(self, pkg):
192 # FIXME: handle pre-install package
195 def checkPackage(self, pkg):
196 self.check_pkgs.append(pkg)
198 def selectPackage(self, pkg):
199 """Select a given package.
200 Can be specified with name.arch or name*
204 self.install(pattern = pkg)
206 except yum.Errors.InstallError:
207 return "No package(s) available to install"
208 except yum.Errors.RepoError, e:
209 raise CreatorError("Unable to download from repo : %s" % (e,))
210 except yum.Errors.YumBaseError, e:
211 raise CreatorError("Unable to install: %s" % (e,))
213 def deselectPackage(self, pkg):
214 """Deselect package. Can be specified as name.arch or name*
217 sp = pkg.rsplit(".", 2)
220 txmbrs = self.tsInfo.matchNaevr(name=sp[0], arch=sp[1])
223 exact, match, unmatch = yum.packages.parsePackages(
224 self.pkgSack.returnPackages(),
227 for p in exact + match:
232 self.tsInfo.remove(x.pkgtup)
233 # we also need to remove from the conditionals
234 # dict so that things don't get pulled back in as a result
235 # of them. yes, this is ugly. conditionals should die.
236 for req, pkgs in self.tsInfo.conditionals.iteritems():
239 self.tsInfo.conditionals[req] = pkgs
241 msger.warning("No such package %s to remove" %(pkg,))
243 def selectGroup(self, grp, include = ksparser.GROUP_DEFAULT):
245 yum.YumBase.selectGroup(self, grp)
246 if include == ksparser.GROUP_REQUIRED:
247 for p in grp.default_packages.keys():
248 self.deselectPackage(p)
250 elif include == ksparser.GROUP_ALL:
251 for p in grp.optional_packages.keys():
252 self.selectPackage(p)
255 except (yum.Errors.InstallError, yum.Errors.GroupsError), e:
257 except yum.Errors.RepoError, e:
258 raise CreatorError("Unable to download from repo : %s" % (e,))
259 except yum.Errors.YumBaseError, e:
260 raise CreatorError("Unable to install: %s" % (e,))
262 def addRepository(self, name, url = None, mirrorlist = None, proxy = None,
263 proxy_username = None, proxy_password = None,
264 inc = None, exc = None, ssl_verify=True, nocache=False,
265 cost = None, priority=None):
266 # TODO: Handle priority attribute for repos
267 def _varSubstitute(option):
268 # takes a variable and substitutes like yum configs do
269 option = option.replace("$basearch", rpmUtils.arch.getBaseArch())
270 option = option.replace("$arch", rpmUtils.arch.getCanonArch())
272 repo = MyYumRepository(name, nocache)
276 repo.proxy_username = proxy_username
277 repo.proxy_password = proxy_password
280 repo.baseurl.append(_varSubstitute(url.full))
283 repo.mirrorlist = _varSubstitute(mirrorlist)
285 conf = yum.config.RepoConf()
286 for k, v in conf.iteritems():
287 if v or not hasattr(repo, k):
288 repo.setAttribute(k, v)
290 repo.sslverify = ssl_verify
292 repo.basecachedir = self.cachedir
293 repo.base_persistdir = self.conf.persistdir
294 repo.failovermethod = "priority"
295 repo.metadata_expire = 0
296 # Enable gpg check for verifying corrupt packages
304 msger.verbose('repo: %s was added' % name)
307 def installLocal(self, pkg, po=None, updateonly=False):
308 ts = rpmUtils.transaction.initReadOnlyTransaction()
310 hdr = rpmUtils.miscutils.hdrFromPackage(ts, pkg)
311 except rpmUtils.RpmUtilsError, e:
312 raise yum.Errors.MiscError, \
313 'Could not open local rpm file: %s: %s' % (pkg, e)
315 self.deselectPackage(hdr['name'])
316 yum.YumBase.installLocal(self, pkg, po, updateonly)
318 def installHasFile(self, file):
319 provides_pkg = self.whatProvides(file, None, None)
323 lambda txmbr: txmbr.ts_state in ("i", "u"),
324 self.tsInfo.getMembers()))
327 for q in provides_pkg:
333 def runInstall(self, checksize = 0):
334 os.environ["HOME"] = "/"
335 os.environ["LD_PRELOAD"] = ""
337 (res, resmsg) = self.buildTransaction()
338 except yum.Errors.RepoError, e:
339 raise CreatorError("Unable to download from repo : %s" %(e,))
342 raise CreatorError("Failed to build transaction : %s" \
343 % str.join("\n", resmsg))
348 lambda txmbr: txmbr.ts_state in ("i", "u"),
349 self.tsInfo.getMembers()))
351 # record all pkg and the content
353 pkg_long_name = misc.RPM_FMT % {
356 'version': pkg.version,
357 'release': pkg.release
359 self.__pkgs_content[pkg_long_name] = pkg.files
360 license = pkg.license
361 if license in self.__pkgs_license.keys():
362 self.__pkgs_license[license].append(pkg_long_name)
364 self.__pkgs_license[license] = [pkg_long_name]
366 if pkg.name in self.check_pkgs:
367 self.check_pkgs.remove(pkg.name)
370 raise CreatorError('Packages absent in image: %s' % ','.join(self.check_pkgs))
372 total_count = len(dlpkgs)
374 download_total_size = sum(map(lambda x: int(x.packagesize), dlpkgs))
376 msger.info("\nChecking packages cached ...")
378 local = po.localPkg()
379 repo = filter(lambda r: r.id == po.repoid, self.repos.listEnabled())[0]
380 if repo.nocache and os.path.exists(local):
382 if not os.path.exists(local):
384 if not self.verifyPkg(local, po, False):
385 msger.warning("Package %s is damaged: %s" \
386 % (os.path.basename(local), local))
388 download_total_size -= int(po.packagesize)
391 cache_avail_size = misc.get_filesystem_avail(self.cachedir)
392 if cache_avail_size < download_total_size:
393 raise CreatorError("No enough space used for downloading.")
395 # record the total size of installed pkgs
398 if hasattr(x, 'installedsize'):
399 pkgs_total_size += int(x.installedsize)
401 pkgs_total_size += int(x.size)
403 # check needed size before actually download and install
404 if checksize and pkgs_total_size > checksize:
405 raise CreatorError("No enough space used for installing, "
406 "please resize partition size in ks file")
408 msger.info("Packages: %d Total, %d Cached, %d Missed" \
409 % (total_count, cached_count, total_count - cached_count))
412 repos = self.repos.listEnabled()
414 repo.setCallback(TextProgress(total_count - cached_count))
416 self.downloadPkgs(dlpkgs)
420 self.populateTs(keepold=0)
422 deps = self.ts.check()
424 # This isn't fatal, Ubuntu has this issue but it is ok.
426 msger.warning("Dependency check failed!")
430 raise CreatorError("ordering packages for installation failed")
432 # FIXME: callback should be refactored a little in yum
433 cb = rpmmisc.RPMInstallCallback(self.ts)
434 cb.tsInfo = self.tsInfo
437 msger.warning('\nCaution, do NOT interrupt the installation, '
438 'else mic cannot finish the cleanup.')
440 installlogfile = "%s/__catched_stderr.buf" % (self.instroot)
441 msger.enable_logstderr(installlogfile)
442 transactionResult = self.runTransaction(cb)
443 if transactionResult.return_code != 0 and self.strict_mode:
444 raise CreatorError("mic failes to install some packages")
445 self._cleanupRpmdbLocks(self.conf.installroot)
447 except rpmUtils.RpmUtilsError, e:
448 raise CreatorError("mic does NOT support delta rpm: %s" % e)
449 except yum.Errors.RepoError, e:
450 raise CreatorError("Unable to download from repo : %s" % e)
451 except yum.Errors.YumBaseError, e:
452 raise CreatorError("Unable to install: %s" % e)
454 msger.disable_logstderr()
456 def getVcsInfo(self):
457 if self.__pkgs_vcsinfo:
458 return self.__pkgs_vcsinfo
460 self.__initialize_transaction()
461 mi = self.ts.dbMatch()
463 lname = misc.RPM_FMT % {
466 'version': hdr['version'],
467 'release': hdr['release']
470 self.__pkgs_vcsinfo[lname] = hdr['VCS']
472 self.__pkgs_vcsinfo[lname] = None
474 return self.__pkgs_vcsinfo
476 def getAllContent(self):
477 return self.__pkgs_content
479 def getPkgsLicense(self):
480 return self.__pkgs_license
482 def getFilelist(self, pkgname):
486 pkg = filter(lambda txmbr: txmbr.po.name == pkgname, self.tsInfo.getMembers())
489 return pkg[0].po.filelist
491 def package_url(self, pkgname):
492 pkgs = self.pkgSack.searchNevra(name=pkgname)
497 url = SafeURL(repo.baseurl[0]).join(pkg.remote_path)
501 proxy = get_proxy_for(url)
503 proxies = {str(url.split(':')[0]): str(proxy)}
507 return (url, proxies)