raise when creating bootstrap failed
[tools/mic.git] / mic / bootstrap.py
1 #!/usr/bin/python -tt
2 #
3 # Copyright (c) 2009, 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 from __future__ import with_statement
19 import os
20 import sys
21 import tempfile
22 import shutil
23 import subprocess
24 import rpm
25 from mic import msger
26 from mic.utils import errors, proxy, misc
27 from mic.utils.rpmmisc import readRpmHeader, RPMInstallCallback
28 from mic.chroot import cleanup_mounts, setup_chrootenv, cleanup_chrootenv
29
30 PATH_BOOTSTRAP = "/usr/sbin:/usr/bin:/sbin:/bin"
31
32 RPMTRANS_FLAGS = [
33                    rpm.RPMTRANS_FLAG_ALLFILES,
34                    rpm.RPMTRANS_FLAG_NOSCRIPTS,
35                    rpm.RPMTRANS_FLAG_NOTRIGGERS,
36                  ]
37
38 RPMVSF_FLAGS = [
39                  rpm._RPMVSF_NOSIGNATURES,
40                  rpm._RPMVSF_NODIGESTS
41                ]
42
43 RPMPROB_FLAGS = [
44                   rpm.RPMPROB_FILTER_OLDPACKAGE,
45                   rpm.RPMPROB_FILTER_REPLACEPKG,
46                   rpm.RPMPROB_FILTER_IGNOREARCH
47                 ]
48
49 class MiniBackend(object):
50     def __init__(self, rootdir, arch=None, repomd=None):
51         self._ts = None
52         self.rootdir = os.path.abspath(rootdir)
53         self.arch = arch
54         self.repomd = repomd
55         self.dlpkgs = []
56         self.localpkgs = {}
57         self.optionals = []
58         self.preins = {}
59         self.postins = {}
60
61     def __del__(self):
62         try:
63             del self.ts
64         except:
65             pass
66
67     def get_ts(self):
68         if not self._ts:
69             self._ts = rpm.TransactionSet(self.rootdir)
70             self._ts.setFlags(reduce(lambda x, y: x|y, RPMTRANS_FLAGS))
71             self._ts.setVSFlags(reduce(lambda x, y: x|y, RPMVSF_FLAGS))
72             self._ts.setProbFilter(reduce(lambda x, y: x|y, RPMPROB_FLAGS))
73
74         return self._ts
75
76     def del_ts(self):
77         if self._ts:
78             self._ts.closeDB()
79             self._ts = None
80
81     ts = property(fget = lambda self: self.get_ts(),
82                   fdel = lambda self: self.del_ts(),
83                   doc="TransactionSet object")
84
85     def selectPackage(self, pkg):
86         if not pkg in self.dlpkgs:
87             self.dlpkgs.append(pkg)
88
89     def runInstall(self):
90         # FIXME: check space
91         self.downloadPkgs()
92         self.installPkgs()
93
94         if self.arch.startswith("arm"):
95             misc.setup_qemu_emulator(self.rootdir, self.arch)
96
97         for pkg in self.preins.keys():
98             prog, script = self.preins[pkg]
99             self.run_pkg_script(pkg, prog, script, '0')
100         for pkg in self.postins.keys():
101             prog, script = self.postins[pkg]
102             self.run_pkg_script(pkg, prog, script, '1')
103
104     def downloadPkgs(self):
105         nonexist = []
106         for pkg in self.dlpkgs:
107             try:
108                 localpth = misc.get_package(pkg, self.repomd, self.arch)
109                 if localpth:
110                     self.localpkgs[pkg] = localpth
111                 elif pkg in self.optionals:
112                     # skip optional rpm
113                     continue
114                 else:
115                     # mark nonexist rpm
116                     nonexist.append(pkg)
117             except:
118                 raise
119
120         if nonexist:
121             raise errors.BootstrapError("Can't get rpm binary: %s" %
122                                         ','.join(nonexist))
123
124     def installPkgs(self):
125         for pkg in self.localpkgs.keys():
126             rpmpath = self.localpkgs[pkg]
127
128             hdr = readRpmHeader(self.ts, rpmpath)
129
130             # save prein and postin scripts
131             self.preins[pkg] = (hdr['PREINPROG'], hdr['PREIN'])
132             self.postins[pkg] = (hdr['POSTINPROG'], hdr['POSTIN'])
133
134             # mark pkg as install
135             self.ts.addInstall(hdr, rpmpath, 'u')
136
137         # run transaction
138         self.ts.order()
139         cb = RPMInstallCallback(self.ts)
140         self.ts.run(cb.callback, '')
141
142     def run_pkg_script(self, pkg, prog, script, arg):
143         mychroot = lambda: os.chroot(self.rootdir)
144
145         if not script:
146             return
147
148         if prog == "<lua>":
149              prog = "/usr/bin/lua"
150
151         tmpdir = os.path.join(self.rootdir, "tmp")
152         if not os.path.exists(tmpdir):
153             os.makedirs(tmpdir)
154         tmpfd, tmpfp = tempfile.mkstemp(dir=tmpdir, prefix="%s.pre-" % pkg)
155         script = script.replace('\r', '')
156         os.write(tmpfd, script)
157         os.close(tmpfd)
158         os.chmod(tmpfp, 0700)
159
160         try:
161             script_fp = os.path.join('/tmp', os.path.basename(tmpfp))
162             subprocess.call([prog, script_fp, arg], preexec_fn=mychroot)
163         except (OSError, IOError), err:
164             msger.warning(str(err))
165         finally:
166             os.unlink(tmpfp)
167
168 class Bootstrap(object):
169     def __init__(self, rootdir, distro, arch=None):
170         self.rootdir = misc.mkdtemp(dir=rootdir, prefix=distro)
171         self.distro = distro
172         self.arch = arch
173         self.logfile = None
174         self.pkgslist = []
175         self.repomd = None
176
177     def __del__(self):
178         self.cleanup()
179
180     def get_rootdir(self):
181         if os.path.exists(self.rootdir):
182             shutil.rmtree(self.rootdir, ignore_errors=True)
183         os.makedirs(self.rootdir)
184         return self.rootdir
185
186     def dirsetup(self, rootdir=None):
187         _path = lambda pth: os.path.join(rootdir, pth.lstrip('/'))
188
189         if not rootdir:
190             rootdir = self.rootdir
191
192         try:
193             # make /tmp and /etc path
194             tmpdir = _path('/tmp')
195             if not os.path.exists(tmpdir):
196                 os.makedirs(tmpdir)
197             etcdir = _path('/etc')
198             if not os.path.exists(etcdir):
199                 os.makedirs(etcdir)
200
201             # touch distro file
202             tzdist = _path('/etc/%s-release' % self.distro)
203             if not os.path.exists(tzdist):
204                 with open(tzdist, 'w') as wf:
205                     wf.write("bootstrap")
206         except:
207             pass
208
209     def create(self, repomd, pkglist, optlist=[]):
210         try:
211             pkgmgr = MiniBackend(self.get_rootdir())
212             pkgmgr.arch = self.arch
213             pkgmgr.repomd = repomd
214             pkgmgr.optionals = optlist
215             map(pkgmgr.selectPackage, pkglist + optlist)
216             pkgmgr.runInstall()
217
218         except (OSError, IOError, errors.CreatorError), err:
219             raise errors.BootstrapError("%s" % err)
220
221         except:
222             raise
223
224     def run(self, cmd, chdir, rootdir=None, bindmounts=None):
225         def mychroot():
226             os.chroot(rootdir)
227             os.chdir(chdir)
228
229         if not rootdir:
230             rootdir = self.rootdir
231
232         if isinstance(cmd, list):
233             shell = False
234         else:
235             shell = True
236
237         env = os.environ
238         env['PATH'] = "%s:%s" % (PATH_BOOTSTRAP, env['PATH'])
239
240         retcode = 0
241         gloablmounts = None
242         try:
243             proxy.set_proxy_environ()
244             gloablmounts = setup_chrootenv(rootdir, bindmounts)
245             retcode = subprocess.call(cmd, preexec_fn=mychroot, env=env, shell=shell)
246         except (OSError, IOError), err:
247             raise RuntimeError(err)
248         finally:
249             if self.logfile:
250                 msger.log(file(self.logfile).read())
251             cleanup_chrootenv(rootdir, bindmounts, gloablmounts)
252             proxy.unset_proxy_environ()
253         return retcode
254
255     def cleanup(self):
256         try:
257             # clean mounts
258             cleanup_mounts(self.rootdir)
259             # remove rootdir
260             shutil.rmtree(self.rootdir, ignore_errors=True)
261         except:
262             pass