4 # Copyright (C) 2006, 2007 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Module dealing with command line parsing"""
30 from ganeti import utils
31 from ganeti import logger
32 from ganeti import errors
33 from ganeti import mcpu
34 from ganeti import constants
36 from optparse import (OptionParser, make_option, TitledHelpFormatter,
37 Option, OptionValueError, SUPPRESS_HELP)
39 __all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain", "SubmitOpCode",
40 "cli_option", "GenerateTable",
41 "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE",
42 "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT"]
44 DEBUG_OPT = make_option("-d", "--debug", default=False,
46 help="Turn debugging on")
48 NOHDR_OPT = make_option("--no-headers", default=False,
49 action="store_true", dest="no_headers",
50 help="Don't display column headers")
52 SEP_OPT = make_option("--separator", default=None,
53 action="store", dest="separator",
54 help="Separator between output fields"
55 " (defaults to one space)")
57 USEUNITS_OPT = make_option("--human-readable", default=False,
58 action="store_true", dest="human_readable",
59 help="Print sizes in human readable format")
61 FIELDS_OPT = make_option("-o", "--output", dest="output", action="store",
62 type="string", help="Select output fields",
65 FORCE_OPT = make_option("-f", "--force", dest="force", action="store_true",
66 default=False, help="Force the operation")
68 _LOCK_OPT = make_option("--lock-retries", default=None,
69 type="int", help=SUPPRESS_HELP)
73 """Macro-like function denoting a fixed number of arguments"""
77 def ARGS_ATLEAST(val):
78 """Macro-like function denoting a minimum number of arguments"""
83 ARGS_ONE = ARGS_FIXED(1)
84 ARGS_ANY = ARGS_ATLEAST(0)
87 def check_unit(option, opt, value):
89 return utils.ParseUnit(value)
90 except errors.UnitParseError, err:
91 raise OptionValueError("option %s: %s" % (opt, err))
94 class CliOption(Option):
95 TYPES = Option.TYPES + ("unit",)
96 TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
97 TYPE_CHECKER["unit"] = check_unit
100 # optparse.py sets make_option, so we do it for our own option class, too
101 cli_option = CliOption
104 def _ParseArgs(argv, commands):
105 """Parses the command line and return the function which must be
106 executed together with its arguments
109 argv: the command line
111 commands: dictionary with special contents, see the design doc for
118 binary = argv[0].split("/")[-1]
120 if len(argv) > 1 and argv[1] == "--version":
121 print "%s (ganeti) %s" % (binary, constants.RELEASE_VERSION)
122 # Quit right away. That way we don't have to care about this special
123 # argument. optparse.py does it the same.
126 if len(argv) < 2 or argv[1] not in commands.keys():
127 # let's do a nice thing
128 sortedcmds = commands.keys()
130 print ("Usage: %(bin)s {command} [options...] [argument...]"
131 "\n%(bin)s <command> --help to see details, or"
132 " man %(bin)s\n" % {"bin": binary})
133 # compute the max line length for cmd + usage
134 mlen = max([len(" %s %s" % (cmd, commands[cmd][3])) for cmd in commands])
135 mlen = min(60, mlen) # should not get here...
136 # and format a nice command list
138 for cmd in sortedcmds:
139 cmdstr = " %s %s" % (cmd, commands[cmd][3])
140 help_text = commands[cmd][4]
141 help_lines = textwrap.wrap(help_text, 79-3-mlen)
142 print "%-*s - %s" % (mlen, cmdstr,
144 for line in help_lines:
145 print "%-*s %s" % (mlen, "", line)
147 return None, None, None
149 func, nargs, parser_opts, usage, description = commands[cmd]
150 parser_opts.append(_LOCK_OPT)
151 parser = OptionParser(option_list=parser_opts,
152 description=description,
153 formatter=TitledHelpFormatter(),
154 usage="%%prog %s %s" % (cmd, usage))
155 parser.disable_interspersed_args()
156 options, args = parser.parse_args()
159 print >> sys.stderr, ("Error: Command %s expects no arguments" % cmd)
160 return None, None, None
161 elif nargs < 0 and len(args) != -nargs:
162 print >> sys.stderr, ("Error: Command %s expects %d argument(s)" %
164 return None, None, None
165 elif nargs >= 0 and len(args) < nargs:
166 print >> sys.stderr, ("Error: Command %s expects at least %d argument(s)" %
168 return None, None, None
170 return func, options, args
174 """Ask the user a yes/no question.
177 questionstring - the question to ask.
180 True or False depending on answer (No for False is default).
184 f = file("/dev/tty", "r+")
189 f.write(textwrap.fill(text))
192 line = f.readline(16).strip().lower()
193 answer = line in ('y', 'yes')
199 def SubmitOpCode(op):
200 """Function to submit an opcode.
202 This is just a simple wrapper over the construction of the processor
203 instance. It should be extended to better handle feedback and
204 interaction functions.
207 proc = mcpu.Processor()
208 return proc.ExecOpCode(op, logger.ToStdout)
211 def GenericMain(commands):
212 """Generic main function for all the gnt-* commands.
214 Argument: a dictionary with a special structure, see the design doc
215 for command line handling.
218 # save the program name and the entire command line for later logging
220 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
221 if len(sys.argv) >= 2:
222 binary += " " + sys.argv[1]
223 old_cmdline = " ".join(sys.argv[2:])
227 binary = "<unknown program>"
230 func, options, args = _ParseArgs(sys.argv, commands)
231 if func is None: # parse error
234 options._ask_user = _AskUser
236 logger.SetupLogging(debug=options.debug, program=binary)
239 utils.Lock('cmd', max_retries=options.lock_retries, debug=options.debug)
240 except errors.LockError, err:
241 logger.ToStderr(str(err))
245 logger.Info("run with arguments '%s'" % old_cmdline)
247 logger.Info("run with no arguments")
251 result = func(options, args)
252 except errors.ConfigurationError, err:
253 logger.Error("Corrupt configuration file: %s" % err)
254 logger.ToStderr("Aborting.")
256 except errors.HooksAbort, err:
257 logger.ToStderr("Failure: hooks execution failed:")
258 for node, script, out in err.args[0]:
260 logger.ToStderr(" node: %s, script: %s, output: %s" %
263 logger.ToStderr(" node: %s, script: %s (no output)" %
266 except errors.HooksFailure, err:
267 logger.ToStderr("Failure: hooks general failure: %s" % str(err))
269 except errors.OpPrereqError, err:
270 logger.ToStderr("Failure: prerequisites not met for this"
271 " operation:\n%s" % str(err))
273 except errors.OpExecError, err:
274 logger.ToStderr("Failure: command execution error:\n%s" % str(err))
283 def GenerateTable(headers, fields, separator, data,
284 numfields=None, unitfields=None):
285 """Prints a table with headers and different fields.
288 headers: Dict of header titles or None if no headers should be shown
289 fields: List of fields to show
290 separator: String used to separate fields or None for spaces
291 data: Data to be printed
292 numfields: List of fields to be aligned to right
293 unitfields: List of fields to be formatted as units
296 if numfields is None:
298 if unitfields is None:
303 if separator is not None:
304 format_fields.append("%s")
305 elif field in numfields:
306 format_fields.append("%*s")
308 format_fields.append("%-*s")
310 if separator is None:
311 mlens = [0 for name in fields]
312 format = ' '.join(format_fields)
314 format = separator.replace("%", "%%").join(format_fields)
317 for idx, val in enumerate(row):
318 if fields[idx] in unitfields:
324 val = row[idx] = utils.FormatUnit(val)
325 if separator is None:
326 mlens[idx] = max(mlens[idx], len(val))
331 for idx, name in enumerate(fields):
333 if separator is None:
334 mlens[idx] = max(mlens[idx], len(hdr))
335 args.append(mlens[idx])
337 result.append(format % tuple(args))
341 for idx in xrange(len(fields)):
342 if separator is None:
343 args.append(mlens[idx])
344 args.append(line[idx])
345 result.append(format % tuple(args))