Use cpio gzip in mic.
[tools/mic.git] / tools / mic
1 #!/usr/bin/env python
2
3 #Copyright (c) 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 # pylint: disable-msg=E0611, E1101, R0201
19 # E0611: no name in module, some attributes are set during running, so ignore it
20 # E1101: %s %r has no %r member, some attributes are set during running,
21 #        so ignore it
22 # R0201: Method could be a function
23
24 """
25  This mudule is entry for mic.
26  It defines a class named MicCmd inheriting Cmdln, and supplies interfaces like
27  'create, chroot, convert' and also some parameters for command 'mic'.
28 """
29 import os
30 import signal
31 import sys
32 import errno
33
34 from argparse import ArgumentParser, SUPPRESS
35
36 from mic import msger, __version__ as VERSION
37 from mic.utils import misc, errors
38 from mic.conf import configmgr
39 from mic.plugin import pluginmgr
40 from mic.helpformat import MICHelpFormatter, subparser
41     
42
43 @subparser
44 def chroot_parser(parser):
45     """chroot into an image
46
47     Examples:
48         mic chroot platform.img
49         mic chroot platform.img ls
50     """
51     parser.add_argument('imagefile', help='Path of image file')
52     parser.add_argument('-s', '--saveto', action = 'store', dest = 'saveto', default = None,
53                         help = "Save the unpacked image to specified dir")
54     parser.add_argument('-c', '--cmd', dest = 'cmd', default = None,
55                         help = "command which will be executed in chroot environment")
56     parser.set_defaults(alias="ch")
57     return parser
58
59 @subparser
60 def create_parser(parser):
61     """create an image
62     Examples:
63       $ mic -d -v create auto handset_blackbay.ks
64       $ mic -d -v cr loop handset_blackbay.ks --logfile=mic.log
65     """
66
67     parent_parser = ArgumentParser(add_help=False)
68     parent_parser.add_argument('ksfile', help='Path of ksfile')
69     parent_parser.add_argument('--logfile', dest='logfile', default=None,
70                                help='Path of logfile')
71     parent_parser.add_argument('-c', '--config', dest='config', default=None,
72                                help='Specify config file for mic')
73     parent_parser.add_argument('-k', '--cachedir', action='store',
74                                dest='cachedir', default=None,
75                                help='Cache directory to store the downloaded')
76     parent_parser.add_argument('-o', '--outdir', action='store', dest='outdir',
77                                default=None, help='Output directory')
78     parent_parser.add_argument('-A', '--arch', dest='arch', default=None,
79                                help='Specify repo architecture')
80     parent_parser.add_argument('--release', dest='release', default=None, metavar='RID',
81                                help='Generate a release of RID with all necessary'
82                                ' files, when @BUILD_ID@ is contained in '
83                                'kickstart file, it will be replaced by RID')
84     parent_parser.add_argument("--record-pkgs", dest="record_pkgs", default=None,
85                                help='Record the info of installed packages, '
86                                'multiple values can be specified which '
87                                'joined by ",", valid values: "name", '
88                                '"content", "license", "vcs"')
89     parent_parser.add_argument('--pkgmgr', dest='pkgmgr', default=None,
90                                help='Specify backend package manager')
91     parent_parser.add_argument('--local-pkgs-path', dest='local_pkgs_path', default=None,
92                                help='Path for local pkgs(rpms) to be installed')
93     parent_parser.add_argument('--runtime', dest='runtime', default=None,
94                                help='Specify runtime mode, avaiable: bootstrap')
95     # --taring-to is alias to --pack-to
96     parent_parser.add_argument('--taring-to', dest='pack_to', default=None,
97                                help=SUPPRESS)
98     parent_parser.add_argument('--pack-to', dest='pack_to', default=None,
99                                help='Pack the images together into the specified'
100                                     ' achive, extension supported: .zip, .tar, '
101                                     '.tar.gz, .tar.bz2, etc. by default, .tar '
102                                     'will be used')
103     parent_parser.add_argument('--copy-kernel', action='store_true', dest='copy_kernel',
104                                help='Copy kernel files from image /boot directory'
105                                     ' to the image output directory.')
106     parent_parser.add_argument('--install-pkgs', action='store', dest='install_pkgs', default=None,
107                                help='Specify what type of packages to be installed,'
108                                     ' valid: source, debuginfo, debugsource')
109     parent_parser.add_argument('--check-pkgs', action='store', dest='check_pkgs', default=[],
110                                help='Check if given packages would be installed, '
111                                     'packages should be separated by comma')
112     parent_parser.add_argument('--tmpfs', action='store_true', dest='enabletmpfs',
113                                help='Setup tmpdir as tmpfs to accelerate, experimental'
114                                     ' feature, use it if you have more than 4G memory')
115     parent_parser.add_argument('--repourl', action='append', dest='repourl', default=[],
116                                help=SUPPRESS)
117     parent_parser.add_argument('-R', '--repo', action='append',
118                                dest='repo', default=[],
119                                help=SUPPRESS)
120     parent_parser.add_argument('--ignore-ksrepo', action='store_true',
121                                dest='ignore_ksrepo', default=False,
122                                help=SUPPRESS)
123     parent_parser.add_argument('--strict-mode', action='store_true',
124                                dest='strict_mode', default=False,
125                                help='Abort creation of image, if there are some errors'
126                                     ' during rpm installation. ')
127      
128     parent_parser.add_argument('-d', '--debug', action='store_true',
129                                help='debug output')
130     parent_parser.add_argument('-v', '--verbose', action='store_true',
131                                 help='verbose output')
132     parent_parser.add_argument('-i', '--interactive', action='store_true',
133                                 dest='interactive', default=True,
134                                help='interactive output')
135
136     parser.set_defaults(alias="cr")
137
138     subparsers  = parser.add_subparsers(title='Subcommands', dest='subcommand')
139     auto_parser = subparsers.add_parser('auto', parents=[parent_parser], help='auto detect image type from magic header')
140
141     fs_parser   = subparsers.add_parser('fs', parents=[parent_parser],
142                                         help='create fs image')
143     fs_parser.add_argument("--include-src", dest = "include_src",action = "store_true",
144                            default = False, help = "Generate a image with source rpms included")
145
146     loop_parser = subparsers.add_parser('loop', parents=[parent_parser], help='create loop image')
147
148     loop_parser.add_argument("--compress-disk-image", dest="compress_image",
149                              choices=("gz", "bz2"), default=None,
150                              help="Same with --compress-image")
151     # alias to compress-image for compatibility
152     loop_parser.add_argument("--compress-image", dest="compress_image",
153                              choices=("gz", "bz2"), default=None,
154                              help="Compress all loop images with 'gz' or 'bz2'")
155     loop_parser.add_argument("--shrink", action='store_true', default=False,
156                   help="Whether to shrink loop images to minimal size")
157                   
158     qcow_parser = subparsers.add_parser('qcow', parents=[parent_parser], help='create qcow image')
159
160     raw_parser = subparsers.add_parser('raw', parents=[parent_parser], help='create raw image')
161
162     raw_parser.add_argument("--compress-disk-image", dest="compress_image",
163                             choices=("gz", "bz2"), default=None,
164                             help="Same with --compress-image")
165     raw_parser.add_argument("--compress-image", dest="compress_image",
166                             choices=("gz", "bz2"), default = None,
167                             help="Compress all raw images before package")
168     raw_parser.add_argument("--generate-bmap", action="store_true", default = None,
169                             help="also generate the block map file")
170     raw_parser.add_argument("--fstab-entry", dest="fstab_entry", choices=("name", "uuid"), default="uuid",
171                             help="Set fstab entry, 'name' means using device names, "
172                                  "'uuid' means using filesystem uuid")
173     return parser
174
175 def main(argv):
176     """Script entry point."""
177     
178     def print_version():
179         """log name, verion, hostname"""
180         
181         name = 'mic'
182         msger.raw("%s %s (%s)" % (name,
183                                   VERSION,
184                                   misc.get_hostname_distro_str()))
185                                   
186     def has_parameter(arg, arglist):
187         """
188         Helper function.
189         Check if argument requires parameter by analyzing
190         its action. Parameter is required only for 'store' and 'append' actions
191         """
192         if arg.startswith('-'):
193             for args in arglist:
194                 if arg in (args['short'], args['long']):
195                     if args.get('action') in (None, 'store', 'append'):
196                         return True
197                     return False
198
199     def sigterm_handler(signal, frame):
200         raise errors.Abort('\nSIGTERM catched, program aborted.')
201
202     # Add SIGTERM handler for exit gracefully
203     signal.signal(signal.SIGTERM, sigterm_handler)
204
205     # Create top level parser
206     epilog = "Try 'mic SUBCOMMAND --help' for help on a specific subcommand."
207     description = "mic - the Image Creation tool"
208     parser = ArgumentParser(description=description, epilog=epilog,
209                             formatter_class=MICHelpFormatter)
210
211     # List of global arguments
212     # The main purpose of this structure is to contain arguments
213     # of add_argument. This is used to do aliasing properly
214     # (see code under the comment 'replace aliases with real commands')
215     global_args = [{'short': '-V', 'long': '--version', 'action': 'version',
216                     'version': '%(prog)s ' + VERSION},
217                    {'short': '-d', 'long': '--debug', 'action': 'store_true',
218                     'help': 'debug output'},
219                    {'short': '-v', 'long': '--verbose', 'action': 'store_true',
220                     'help': 'verbose output'},
221                    {'short': '-i', 'long': '--interactive', 'action': 'store_true',
222                     'dest': 'interactive', 'default': 'True', 'help': 'interactive output'}, 
223                    {'short': '', 'long': '--non-interactive', 'action': 'store_false',
224                     'dest': 'interactive', 'default': 'True', 'help': 'non-interactive output'}, 
225                   ]
226
227     for args in global_args:
228         parser_kwargs = {}
229         for key in ('action', 'help', 'version', 'default', 'dest'):
230             if key in args:
231                 parser_kwargs[key] = args[key]
232         
233         if args['short'] is '':
234             parser.add_argument(args['long'], **parser_kwargs)
235         else:
236             parser.add_argument(args['short'], args['long'], **parser_kwargs)
237
238     # hacked by the request of cmdln lovers
239     parser.format_usage = parser.format_help
240
241     # Create parsers for subcommands
242     subparsers = parser.add_subparsers(title='subcommands')
243
244     # collect aliases
245     aliases = {}
246     for name, obj in globals().iteritems():
247         if name.endswith('_parser') and callable(obj):
248             aliases[obj(subparsers).get_default('alias')] = name.split('_')[0]
249
250     # replace aliases with real commands
251     for i, arg in enumerate(argv[1:]):
252         if not arg.startswith('-'):
253             # argv[i] is previous argument to arg
254             if not has_parameter(argv[i], global_args) and arg in aliases:
255                 argv[i+1] = aliases[arg]
256                 break
257
258     # Parse arguments
259     args = parser.parse_args(argv[1:])
260
261     if args.interactive:
262         msger.enable_interactive()
263     else:
264         msger.disable_interactive()
265
266     if args.verbose:
267         msger.set_loglevel('VERBOSE')
268
269     if args.debug:
270         try:
271             import rpm
272             rpm.setVerbosity(rpm.RPMLOG_DEBUG)
273         except ImportError:
274             pass
275
276         msger.set_loglevel('DEBUG')
277
278     print_version()
279
280     # Import target module and call 'main' from it
281     module = __import__("mic.%s" % args.module, fromlist=[args.module])
282     return module.main(parser, args, argv[1:])
283
284     
285 if __name__ == "__main__":
286     try:
287         sys.exit(main(sys.argv))
288     except KeyboardInterrupt:
289         msger.error('\n^C catched, program aborted.')
290     except IOError as ioerr:
291         # catch 'no space left' exception, etc
292         if ioerr.errno == errno.ENOSPC:
293             msger.error('\nNo space left on device')
294         raise
295     except errors.Usage as usage:
296         msger.error(str(usage))
297     except errors.Abort as  msg:
298         msger.info(str(msg))
299     except errors.CreatorError as err:
300         if msger.get_loglevel() == 'DEBUG':
301             import traceback
302             msger.error(traceback.format_exc())
303         else:
304             msger.error(str(err))