headers for new modules and comments for TBD issues
[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, sys
20 import pickle
21 import shutil
22 import subprocess
23 import rpm
24
25 from mic import msger
26 from mic import chroot
27 from mic.plugin import pluginmgr
28 from mic.utils import proxy
29 from mic.utils import rpmmisc
30 from mic.utils import errors
31
32 minibase_pkgs = [ "kernel", "rpm", "setup", "filesystem", "basesystem",
33                   "tzdata", "libgcc", "ncurses-base", "ncurses", "glibc",
34                   "glibc-common", "ncurses-libs", "nss-softokn-freebl",
35                   "bash", "zlib", "info", "cpio", "coreutils", "zypper" ]
36
37 required_pkgs = [ "pam", "passwd", "meego-release", "nss", "genisoimage",
38                   "bzip2", "gzip", "perl", "make", "file", "psmisc", "wget",
39                   "syslinux-extlinux", "btrfs-progs", "satsolver-tools",
40                   "isomd5sum", "mtd-utils", "mtd-utils-ubi", "libzypp",
41                   "python-zypp", "grep", "mic" ]
42
43
44 def query_package_rpmdb(root='/', tag='name', pattern=None):
45     name = pattern
46     version = None
47     ts = rpm.TransactionSet(root)
48     mi = ts.dbMatch(tag, pattern)
49     for hdr in mi:
50         version = hdr['version']
51     return (name, version)
52
53 def query_package_metadat(root='/', tag='name', pattern=None):
54     name = pattern
55     version = None
56     try:
57         with open(root + '/.metadata', 'r') as f:
58             metadata = pickle.load(f)
59         f.close()
60     except:
61         raise errors.BootstrapError("Load %s/.metadata error" % root)
62     else:
63         for pkg in metadata.keys():
64             (n, v, r, e, a) = rpmmisc.splitFilename(pkg)
65             if n == pattern:
66                 version = v
67     return (name, version)
68
69 class Bootstrap(object):
70     def __init__(self, homedir='/var/mic/bootstrap', **kwargs):
71         self._pkgmgr = None
72         self._rootdir = None
73         self._bootstraps = []
74         self.homedir = homedir
75
76         if not os.path.exists(self.homedir):
77             os.makedirs(self.homedir)
78
79         self.__dict__.update(**kwargs)
80
81     def _setRootdir(self, name):
82         self._rootdir = os.path.join(self.homedir, name)
83
84     def _getRootdir(self):
85         if not os.path.exists(self._rootdir):
86             raise errors.BootstrapError("Root dir: %s not exist" % self._rootdir)
87         return self._rootdir
88
89     rootdir = property(fget = lambda self: self._getRootdir(),
90                        fset = lambda self, name: self._setRootdir(name),
91                        doc = 'root directory')
92
93     def _setPkgmgr(self, name):
94         backend_plugins = pluginmgr.get_plugins('backend')
95         for (key, cls) in backend_plugins.iteritems():
96             if key == name:
97                 self._pkgmgr = cls
98         if not self._pkgmgr:
99             raise errors.BootstrapError("Backend: %s can't be loaded correctly" % name)
100
101     pkgmgr = property(fget = lambda self: self._pkgmgr,
102                       fset = lambda self, name: self._setPkgmgr(name),
103                       doc = 'package manager')
104
105     @property
106     def bootstraps(self):
107         if self._bootstraps:
108             return self._bootstraps
109         for dir in os.listdir(self.homedir):
110             metadata_fp = os.path.join(self.homedir, dir, '.metadata')
111             if os.path.exists(metadata_fp) \
112                 and 0 != os.path.getsize(metadata_fp):
113                 self._bootstraps.append(dir)
114         return self._bootstraps
115
116     def run(self, name, cmd, chdir='/', bindmounts=None):
117         self.rootdir = name
118         def mychroot():
119             os.chroot(self.rootdir)
120             os.chdir(chdir)
121
122         if isinstance(cmd, list):
123             cmd = ' '.join(cmd)
124
125         lvl = msger.get_loglevel()
126         msger.set_loglevel('quiet')
127         globalmounts = chroot.setup_chrootenv(self.rootdir, bindmounts)
128         try:
129             proxy.set_proxy_environ()
130             subprocess.call(cmd, preexec_fn=mychroot, shell=True)
131             proxy.unset_proxy_environ()
132         except:
133             raise errors.BootstrapError("Run in bootstrap fail")
134         finally:
135             chroot.cleanup_chrootenv(self.rootdir, bindmounts, globalmounts)
136
137         msger.set_loglevel(lvl)
138
139     def list(self, **kwargs):
140         bslist = []
141         for binst in self.bootstraps:
142             (mver, kver, rver) = self.status(binst)
143             bsinfo = {'name':binst, 'meego':mver, 'kernel':kver, 'rpm': rver}
144             bslist.append(bsinfo)
145
146         return bslist
147
148     def status(self, name):
149         self.rootdir = name
150         if os.path.exists(self.rootdir + '/.metadata'):
151             query_package = query_package_metadat
152         else:
153             query_package = query_package_rpmdb
154
155         name, mver = query_package(self.rootdir, 'name', 'meego-release')
156         msger.debug("MeeGo Release: %s" % mver)
157
158         name, kver = query_package(self.rootdir, 'name', 'kernel')
159         msger.debug("Kernel Version: %s" % kver)
160
161         name, rver = query_package(self.rootdir, 'name', 'rpm')
162         msger.debug("RPM Version: %s" % rver)
163
164         return (mver, kver, rver)
165
166     def create(self, name, repolist, **kwargs):
167         self.name = name
168         self.pkgmgr = 'zypp'
169         self.arch = 'i686'
170         self.rootdir = name
171         self.cachedir = '/var/tmp/mic/cache' # TBD from conf, do NOT hardcode
172
173         if 'arch' in kwargs:
174             self.arch = kwargs['arch']
175         if 'cachedir' in kwargs:
176             self.cachedir = kwargs['cachedir']
177
178         if os.path.exists(self._rootdir):
179             metadata_fp = os.path.join(self._rootdir, '.metadata')
180             if os.path.exists(metadata_fp) and \
181                0 != os.path.getsize(metadata_fp):
182                 msger.warning("bootstrap already exists") # TBD more details
183                 return
184             else:
185                 shutil.rmtree(self._rootdir)
186
187         if not os.path.exists(self._rootdir):
188             os.makedirs(self._rootdir)
189
190         pkg_manager = self.pkgmgr(self.arch, self.rootdir, self.cachedir)
191         pkg_manager.setup()
192
193         for repo in repolist:
194             if 'proxy' in repo.keys():
195                 pkg_manager.addRepository(repo['name'], repo['baseurl'], proxy = repo['proxy'])
196             else:
197                 pkg_manager.addRepository(repo['name'], repo['baseurl'])
198
199         rpm.addMacro("_dbpath", "/var/lib/rpm")
200         rpm.addMacro("__file_context_path", "%{nil}")
201
202         for pkg in minibase_pkgs:
203             pkg_manager.selectPackage(pkg)
204         for pkg in required_pkgs:
205             pkg_manager.selectPackage(pkg)
206
207         try:
208             pkg_manager.runInstall(512 * 1024L * 1024L)
209         except:
210             raise errors.BootstrapError("Create bootstrap fail")
211         else:
212             metadata = pkg_manager.getAllContent()
213             metadata_fp = os.path.join(self.rootdir, '.metadata')
214             with open(metadata_fp, 'w') as f:
215                 pickle.dump(metadata, f)
216             f.close()
217         finally:
218             pkg_manager.closeRpmDB()
219             pkg_manager.close()
220
221         # Copy bootstrap repo files
222         srcdir = "%s/etc/zypp/repos.d/" % self.cachedir
223         destdir= "%s/etc/zypp/repos.d/" % os.path.abspath(self.rootdir)
224         shutil.rmtree(destdir, ignore_errors = True)
225         shutil.copytree(srcdir, destdir)
226
227         msger.info("Bootstrap created.")
228
229     def rebuild(self):
230         pass
231
232     def update(self, name):
233         self.rootdir = name
234         chrootdir = self.rootdir
235
236         def mychroot():
237             os.chroot(chrootdir)
238
239         shutil.copyfile("/etc/resolv.conf", chrootdir + "/etc/resolv.conf")
240         try:
241             subprocess.call("zypper -n --no-gpg-checks update", preexec_fn=mychroot, shell=True)
242         except OSError, err:
243             raise errors.BootstrapError("Bootstrap: %s Update fail" % chrootdir)
244
245     def cleanup(self, name):
246         self.rootdir = name
247         try:
248             chroot.cleanup_mounts(self.rootdir)
249             shutil.rmtree(self.rootdir, ignore_errors=True)
250         except:
251             raise errors.BootstrapError("Bootstrap: %s clean up fail " % self.rootdir)