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