3 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
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
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
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.
18 from __future__ import with_statement
28 from mic.utils import errors, proxy, misc
29 from mic.utils.rpmmisc import readRpmHeader, RPMInstallCallback
30 from mic.chroot import cleanup_mounts, setup_chrootenv, cleanup_chrootenv
31 from mic.conf import configmgr
33 PATH_BOOTSTRAP = "/usr/sbin:/usr/bin:/sbin:/bin"
36 rpm.RPMTRANS_FLAG_ALLFILES,
37 rpm.RPMTRANS_FLAG_NOSCRIPTS,
38 rpm.RPMTRANS_FLAG_NOTRIGGERS,
42 rpm._RPMVSF_NOSIGNATURES,
47 rpm.RPMPROB_FILTER_OLDPACKAGE,
48 rpm.RPMPROB_FILTER_REPLACEPKG,
49 rpm.RPMPROB_FILTER_IGNOREARCH
52 class MiniBackend(object):
53 def __init__(self, rootdir, arch=None, repomd=None):
55 self.rootdir = os.path.abspath(rootdir)
63 self.scriptlets = False
73 self._ts = rpm.TransactionSet(self.rootdir)
74 self._ts.setFlags(reduce(lambda x, y: x|y, RPMTRANS_FLAGS))
75 self._ts.setVSFlags(reduce(lambda x, y: x|y, RPMVSF_FLAGS))
76 self._ts.setProbFilter(reduce(lambda x, y: x|y, RPMPROB_FLAGS))
85 ts = property(fget = lambda self: self.get_ts(),
86 fdel = lambda self: self.del_ts(),
87 doc="TransactionSet object")
89 def selectPackage(self, pkg):
90 if not pkg in self.dlpkgs:
91 self.dlpkgs.append(pkg)
93 def _get_local_packages(self, pkg):
94 """Return local mic-bootstrap rpm path."""
95 cropts = configmgr.create
96 if cropts['local_pkgs_path']:
97 if os.path.isdir(cropts['local_pkgs_path']):
99 os.path.join(cropts['local_pkgs_path'], pkg + '*.rpm'))
101 raise errors.BootstrapError("Many %s packages in folder, put only one %s package in it." % (pkg, pkg))
102 elif len(pkglist) == 1:
103 return ''.join(pkglist)
104 elif os.path.splitext(cropts['local_pkgs_path'])[-1] == '.rpm':
105 if cropts['local_pkgs_path'].find(pkg) >= 0:
106 return cropts['local_pkgs_path']
108 def runInstall(self):
113 if not self.scriptlets:
116 for pkg in self.preins.keys():
117 prog, script = self.preins[pkg]
118 self.run_pkg_script(pkg, prog, script, '0')
119 for pkg in self.postins.keys():
120 prog, script = self.postins[pkg]
121 self.run_pkg_script(pkg, prog, script, '1')
123 def downloadPkgs(self):
125 for pkg in self.dlpkgs:
126 localpth = self._get_local_packages(pkg)
128 localpth = misc.get_package(pkg, self.repomd, self.arch)
130 self.localpkgs[pkg] = localpth
131 elif pkg in self.optionals:
139 raise errors.BootstrapError("Can't get rpm binary: %s" %
142 def installPkgs(self):
143 for pkg in self.localpkgs.keys():
144 rpmpath = self.localpkgs[pkg]
146 hdr = readRpmHeader(self.ts, rpmpath)
148 # save prein and postin scripts
149 self.preins[pkg] = (hdr['PREINPROG'], hdr['PREIN'])
150 self.postins[pkg] = (hdr['POSTINPROG'], hdr['POSTIN'])
152 # mark pkg as install
153 self.ts.addInstall(hdr, rpmpath, 'u')
157 cb = RPMInstallCallback(self.ts)
158 errs = self.ts.run(cb.callback, '')
160 # ts.run() exit codes are, hmm, "creative": None means all ok, empty
161 # list means some errors happened in the transaction and non-empty
162 # list that there were errors preventing the ts from starting...
164 raise errors.BootstrapError("Transaction couldn't start: %s" % '\n'.join(errs))
166 def run_pkg_script(self, pkg, prog, script, arg):
167 mychroot = lambda: os.chroot(self.rootdir)
173 prog = "/usr/bin/lua"
175 tmpdir = os.path.join(self.rootdir, "tmp")
176 if not os.path.exists(tmpdir):
178 tmpfd, tmpfp = tempfile.mkstemp(dir=tmpdir, prefix="%s.pre-" % pkg)
179 script = script.replace('\r', '')
180 os.write(tmpfd, script)
182 os.chmod(tmpfp, 0700)
185 script_fp = os.path.join('/tmp', os.path.basename(tmpfp))
186 subprocess.call([prog, script_fp, arg], preexec_fn=mychroot)
187 except (OSError, IOError), err:
188 msger.warning(str(err))
192 class Bootstrap(object):
193 def __init__(self, rootdir, distro, arch=None):
194 self.rootdir = misc.mkdtemp(dir=rootdir, prefix=distro)
204 def get_rootdir(self):
205 if os.path.exists(self.rootdir):
206 shutil.rmtree(self.rootdir, ignore_errors=True)
207 os.makedirs(self.rootdir)
210 def dirsetup(self, rootdir=None):
211 _path = lambda pth: os.path.join(rootdir, pth.lstrip('/'))
214 rootdir = self.rootdir
217 # make /tmp and /etc path
218 tmpdir = _path('/tmp')
219 if not os.path.exists(tmpdir):
221 etcdir = _path('/etc')
222 if not os.path.exists(etcdir):
226 tzdist = _path('/etc/%s-release' % self.distro)
227 if not os.path.exists(tzdist):
228 with open(tzdist, 'w') as wf:
229 wf.write("bootstrap")
233 def create(self, repomd, pkglist, optlist=()):
235 pkgmgr = MiniBackend(self.get_rootdir())
236 pkgmgr.arch = self.arch
237 pkgmgr.repomd = repomd
238 pkgmgr.optionals = list(optlist)
239 map(pkgmgr.selectPackage, pkglist + list(optlist))
241 except (OSError, IOError, errors.CreatorError), err:
242 raise errors.BootstrapError("%s" % err)
244 def run(self, cmd, chdir, rootdir=None, bindmounts=None):
249 def sync_timesetting(rootdir):
251 # sync time and zone info to bootstrap
252 if os.path.exists(rootdir + "/etc/localtime"):
253 os.unlink(rootdir + "/etc/localtime")
254 shutil.copyfile("/etc/localtime", rootdir + "/etc/localtime")
258 def sync_passwdfile(rootdir):
260 # sync passwd file to bootstrap, saving the user info
261 if os.path.exists(rootdir + "/etc/passwd"):
262 os.unlink(rootdir + "/etc/passwd")
263 shutil.copyfile("/etc/passwd", rootdir + "/etc/passwd")
267 def sync_hostfile(rootdir):
269 # sync host info to bootstrap
270 if os.path.exists(rootdir + "/etc/hosts"):
271 os.unlink(rootdir + "/etc/hosts")
272 shutil.copyfile("/etc/hosts", rootdir + "/etc/hosts")
276 def sync_signfile(rootdir,homedir):
277 if os.path.exists(homedir + "/.sign"):
278 signfile = rootdir + homedir + "/.sign"
280 # sync sign info to bootstrap
281 if os.path.exists(signfile):
282 if os.path.isdir(signfile):
283 shutil.rmtree(signfile)
287 shutil.copytree(homedir + "/.sign", signfile)
288 except OSError as exc:
289 if exc.errno == errno.ENOTDIR:
290 shutil.copy(homedir + "/.sign", signfile)
296 rootdir = self.rootdir
298 if isinstance(cmd, list):
304 env['PATH'] = "%s:%s" % (PATH_BOOTSTRAP, env['PATH'])
305 cropts = configmgr.create
307 lang = cropts["ks"].handler.lang.lang
314 proxy.set_proxy_environ()
315 gloablmounts = setup_chrootenv(rootdir, bindmounts)
316 sync_timesetting(rootdir)
317 sync_passwdfile(rootdir)
318 sync_hostfile(rootdir)
319 env['SUDO_USER'] = 'root'
320 if 'SUDO_USER' in env:
321 sync_signfile(rootdir, os.path.expanduser('~' + env['SUDO_USER']))
323 sync_signfile(rootdir, os.path.expanduser('~'))
324 retcode = subprocess.call(cmd, preexec_fn=mychroot, env=env, shell=shell)
325 except (OSError, IOError):
326 # add additional information to original exception
327 value, tb = sys.exc_info()[1:]
328 value = '%s: %s' % (value, ' '.join(cmd))
329 raise RuntimeError, value, tb
331 #if self.logfile and os.path.isfile(self.logfile):
332 # msger.log(file(self.logfile).read())
333 cleanup_chrootenv(rootdir, bindmounts, gloablmounts)
334 proxy.unset_proxy_environ()
340 cleanup_mounts(self.rootdir)
342 shutil.rmtree(self.rootdir, ignore_errors=True)