2 # yum.py : yum utilities
4 # Copyright 2007, Red Hat Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 2 of the License.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Library General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 import pykickstart.parser
34 from mic.utils.errors import *
35 from mic.utils.fs_related import *
36 from mic.pluginbase.backend_plugin import BackendPlugin
37 from mic.imager.baseimager import BaseImageCreator as ImageCreator
39 class MyYumRepository(yum.yumRepo.YumRepository):
40 def __init__(self, repoid):
41 yum.yumRepo.YumRepository.__init__(self, repoid)
42 self.sslverify = False
45 self.sslverify = False
46 yum.yumRepo.YumRepository._setupGrab(self)
51 class Yum(BackendPlugin, yum.YumBase):
52 def __init__(self, creator = None, recording_pkgs=None):
53 if not isinstance(creator, ImageCreator):
54 raise CreatorError("Invalid argument: creator")
55 yum.YumBase.__init__(self)
57 self.creator = creator
59 if self.creator.target_arch:
60 if rpmUtils.arch.arches.has_key(self.creator.target_arch):
61 self.arch.setup_arch(self.creator.target_arch)
63 raise CreatorError("Invalid target arch: %s" % self.creator.target_arch)
65 self.__recording_pkgs = recording_pkgs
66 self.__pkgs_content = {}
68 def doFileLogSetup(self, uid, logfile):
69 # don't do the file log for the livecd as it can lead to open fds
70 # being left and an inability to clean up after ourself
75 os.unlink(self.conf.installroot + "/yum.conf")
79 yum.YumBase.close(self)
83 if not os.path.exists("/etc/fedora-release") and not os.path.exists("/etc/meego-release"):
84 for i in range(3, os.sysconf("SC_OPEN_MAX")):
93 def _writeConf(self, confpath, installroot):
95 conf += "installroot=%s\n" % installroot
96 conf += "cachedir=/var/cache/yum\n"
99 conf += "failovermethod=priority\n"
100 conf += "http_caching=packages\n"
101 conf += "sslverify=0\n"
103 f = file(confpath, "w+")
107 os.chmod(confpath, 0644)
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*"):
115 def setup(self, confpath, installroot):
116 self._writeConf(confpath, installroot)
117 self._cleanupRpmdbLocks(installroot)
118 self.doConfigSetup(fn = confpath, root = installroot)
125 def selectPackage(self, pkg):
126 """Select a given package. Can be specified with name.arch or name*"""
128 self.install(pattern = pkg)
130 except yum.Errors.InstallError, e:
132 except yum.Errors.RepoError, e:
133 raise CreatorError("Unable to download from repo : %s" % (e,))
134 except yum.Errors.YumBaseError, e:
135 raise CreatorError("Unable to install: %s" % (e,))
137 def deselectPackage(self, pkg):
138 """Deselect package. Can be specified as name.arch or name*"""
139 sp = pkg.rsplit(".", 2)
142 txmbrs = self.tsInfo.matchNaevr(name=sp[0], arch=sp[1])
145 exact, match, unmatch = yum.packages.parsePackages(self.pkgSack.returnPackages(), [pkg], casematch=1)
146 for p in exact + match:
151 self.tsInfo.remove(x.pkgtup)
152 # we also need to remove from the conditionals
153 # dict so that things don't get pulled back in as a result
154 # of them. yes, this is ugly. conditionals should die.
155 for req, pkgs in self.tsInfo.conditionals.iteritems():
158 self.tsInfo.conditionals[req] = pkgs
160 logging.warn("No such package %s to remove" %(pkg,))
162 def selectGroup(self, grp, include = pykickstart.parser.GROUP_DEFAULT):
164 yum.YumBase.selectGroup(self, grp)
165 if include == pykickstart.parser.GROUP_REQUIRED:
166 map(lambda p: self.deselectPackage(p), grp.default_packages.keys())
167 elif include == pykickstart.parser.GROUP_ALL:
168 map(lambda p: self.selectPackage(p), grp.optional_packages.keys())
170 except (yum.Errors.InstallError, yum.Errors.GroupsError), e:
172 except yum.Errors.RepoError, e:
173 raise CreatorError("Unable to download from repo : %s" % (e,))
174 except yum.Errors.YumBaseError, e:
175 raise CreatorError("Unable to install: %s" % (e,))
177 def __checkAndDownloadURL(self, u2opener, url, savepath):
180 f = u2opener.open(url)
183 except u2.HTTPError, httperror:
184 if httperror.code in (404, 503):
187 raise CreatorError(httperror)
188 except OSError, oserr:
192 raise CreatorError(oserr)
193 except IOError, oserr:
194 if hasattr(oserr, "reason") and oserr.reason.errno == 2:
197 raise CreatorError(oserr)
198 except u2.URLError, err:
199 raise CreatorError(err)
202 licf = open(savepath, "w")
209 def __pagerFile(self, savepath):
210 if os.path.splitext(savepath)[1].upper() in ('.HTM', '.HTML'):
211 pagers = ('w3m', 'links', 'lynx', 'less', 'more')
213 pagers = ('less', 'more')
218 subprocess.call([pager, savepath])
228 raw_input('press <ENTER> to continue...')
230 def checkRepositoryEULA(self, name, repo):
231 """ This function is to check the LICENSE file if provided. """
233 # when proxy needed, make urllib2 follow it
235 proxy_username = repo.proxy_username
236 proxy_password = repo.proxy_password
239 auth_handler = u2.HTTPBasicAuthHandler(u2.HTTPPasswordMgrWithDefaultRealm())
243 proxy_netloc = urlparse.urlsplit(proxy).netloc
245 proxy_url = 'http://%s:%s@%s' % (proxy_username, proxy_password, proxy_netloc)
247 proxy_url = 'http://%s@%s' % (proxy_username, proxy_netloc)
251 proxy_support = u2.ProxyHandler({'http': proxy_url,
253 handlers.append(proxy_support)
255 # download all remote files to one temp dir
257 repo_lic_dir = tempfile.mkdtemp(prefix = 'repolic')
259 for url in repo.baseurl:
260 if not url.endswith('/'):
262 tmphandlers = handlers
263 (scheme, host, path, parm, query, frag) = urlparse.urlparse(url)
264 if scheme not in ("http", "https", "ftp", "ftps", "file"):
265 raise CreatorError("Error: invalid url %s" % url)
268 user_pass, host = host.split('@', 1)
270 user, password = user_pass.split(':', 1)
271 except ValueError, e:
272 raise CreatorError('Bad URL: %s' % url)
273 print "adding HTTP auth: %s, %s" %(user, password)
274 auth_handler.add_password(None, host, user, password)
275 tmphandlers.append(auth_handler)
276 url = scheme + "://" + host + path + parm + query + frag
277 if len(tmphandlers) != 0:
278 u2opener = u2.build_opener(*tmphandlers)
280 repo_eula_url = urlparse.urljoin(url, "LICENSE.txt")
281 repo_eula_path = self.__checkAndDownloadURL(
284 os.path.join(repo_lic_dir, repo.id + '_LICENSE.txt'))
293 # show the license file
294 print 'For the software packages in this yum repo:'
295 print ' %s: %s' % (name, baseurl)
296 print 'There is an "End User License Agreement" file that need to be checked.'
297 print 'Please read the terms and conditions outlined in it and answer the followed qustions.'
298 raw_input('press <ENTER> to continue...')
300 self.__pagerFile(repo_eula_path)
302 # Asking for the "Accept/Decline"
305 input_accept = raw_input('Would you agree to the terms and conditions outlined in the above End User License Agreement? (Yes/No): ')
306 if input_accept.upper() in ('YES', 'Y'):
308 elif input_accept.upper() in ('NO', 'N'):
310 print 'Will not install pkgs from this repo.'
314 shutil.rmtree(repo_lic_dir)
317 # try to find support_info.html for extra infomation
318 repo_info_url = urlparse.urljoin(baseurl, "support_info.html")
319 repo_info_path = self.__checkAndDownloadURL(
322 os.path.join(repo_lic_dir, repo.id + '_support_info.html'))
324 print 'There is one more file in the repo for additional support information, please read it'
325 raw_input('press <ENTER> to continue...')
326 self.__pagerFile(repo_info_path)
329 shutil.rmtree(repo_lic_dir)
332 def addRepository(self, name, url = None, mirrorlist = None, proxy = None, proxy_username = None, proxy_password = None, inc = None, exc = None):
333 def _varSubstitute(option):
334 # takes a variable and substitutes like yum configs do
335 option = option.replace("$basearch", rpmUtils.arch.getBaseArch())
336 option = option.replace("$arch", rpmUtils.arch.getCanonArch())
339 repo = MyYumRepository(name)
340 repo.sslverify = False
344 repo.proxy_username = proxy_username
345 repo.proxy_password = proxy_password
348 repo.baseurl.append(_varSubstitute(url))
350 # check LICENSE files
351 if not self.checkRepositoryEULA(name, repo):
355 repo.mirrorlist = _varSubstitute(mirrorlist)
356 conf = yum.config.RepoConf()
357 for k, v in conf.iteritems():
358 if v or not hasattr(repo, k):
359 repo.setAttribute(k, v)
360 repo.basecachedir = self.conf.cachedir
361 repo.failovermethod = "priority"
362 repo.metadata_expire = 0
363 # Enable gpg check for verifying corrupt packages
367 repo.setCallback(TextProgress())
371 def installHasFile(self, file):
372 provides_pkg = self.whatProvides(file, None, None)
373 dlpkgs = map(lambda x: x.po, filter(lambda txmbr: txmbr.ts_state in ("i", "u"), self.tsInfo.getMembers()))
375 for q in provides_pkg:
380 def runInstall(self, checksize = 0):
381 os.environ["HOME"] = "/"
383 (res, resmsg) = self.buildTransaction()
384 except yum.Errors.RepoError, e:
385 raise CreatorError("Unable to download from repo : %s" %(e,))
387 raise CreatorError("Failed to build transaction : %s" % str.join("\n", resmsg))
389 dlpkgs = map(lambda x: x.po, filter(lambda txmbr: txmbr.ts_state in ("i", "u"), self.tsInfo.getMembers()))
391 # record the total size of installed pkgs
392 pkgs_total_size = sum(map(lambda x: int(x.size), dlpkgs))
394 # check needed size before actually download and install
395 if checksize and pkgs_total_size > checksize:
396 raise CreatorError("Size of specified root partition in kickstart file is too small to install all selected packages.")
398 if self.__recording_pkgs:
399 # record all pkg and the content
401 pkg_long_name = "%s-%s.%s.rpm" % (pkg.name, pkg.printVer(), pkg.arch)
402 self.__pkgs_content[pkg_long_name] = pkg.files
404 total_count = len(dlpkgs)
406 print "Checking packages cache and packages integrity..."
408 local = po.localPkg()
409 if not os.path.exists(local):
411 if not self.verifyPkg(local, po, False):
412 print "Package %s is damaged: %s" % (os.path.basename(local), local)
415 print "%d packages to be installed, %d packages gotten from cache, %d packages to be downloaded" % (total_count, cached_count, total_count - cached_count)
417 self.downloadPkgs(dlpkgs)
421 self.populateTs(keepold=0)
422 deps = self.ts.check()
424 """ This isn't fatal, Ubuntu has this issue but it is ok. """
426 logging.warn("Dependency check failed!")
429 raise CreatorError("ordering packages for installation failed!")
431 # FIXME: callback should be refactored a little in yum
432 sys.path.append('/usr/share/yum-cli')
434 cb = callback.RPMInstallCallback()
435 cb.tsInfo = self.tsInfo
437 ret = self.runTransaction(cb)
439 self._cleanupRpmdbLocks(self.conf.installroot)
441 except yum.Errors.RepoError, e:
442 raise CreatorError("Unable to download from repo : %s" % (e,))
443 except yum.Errors.YumBaseError, e:
444 raise CreatorError("Unable to install: %s" % (e,))
446 def getAllContent(self):
447 return self.__pkgs_content
449 mic_plugin = ["yum", Yum]