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