Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ dcb93971

History | View | Annotate | Download (8.3 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
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.
10
#
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.
15
#
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
19
# 02110-1301, USA.
20

    
21

    
22
"""Module dealing with command line parsing"""
23

    
24

    
25
import sys
26
import textwrap
27
import os.path
28
import copy
29

    
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
35

    
36
from optparse import (OptionParser, make_option, TitledHelpFormatter,
37
                      Option, OptionValueError, SUPPRESS_HELP)
38

    
39
__all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain", "SubmitOpCode",
40
           "cli_option",
41
           "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE",
42
           "USEUNITS_OPT", "FIELDS_OPT"]
43

    
44
DEBUG_OPT = make_option("-d", "--debug", default=False,
45
                        action="store_true",
46
                        help="Turn debugging on")
47

    
48
NOHDR_OPT = make_option("--no-headers", default=False,
49
                        action="store_true", dest="no_headers",
50
                        help="Don't display column headers")
51

    
52
SEP_OPT = make_option("--separator", default=" ",
53
                      action="store", dest="separator",
54
                      help="Separator between output fields"
55
                      " (defaults to one space)")
56

    
57
USEUNITS_OPT = make_option("--human-readable", default=False,
58
                           action="store_true", dest="human_readable",
59
                           help="Print sizes in human readable format")
60

    
61
FIELDS_OPT = make_option("-o", "--output", dest="output", action="store",
62
                         type="string", help="Select output fields",
63
                         metavar="FIELDS")
64

    
65
_LOCK_OPT = make_option("--lock-retries", default=None,
66
                        type="int", help=SUPPRESS_HELP)
67

    
68

    
69
def ARGS_FIXED(val):
70
  """Macro-like function denoting a fixed number of arguments"""
71
  return -val
72

    
73

    
74
def ARGS_ATLEAST(val):
75
  """Macro-like function denoting a minimum number of arguments"""
76
  return val
77

    
78

    
79
ARGS_NONE = None
80
ARGS_ONE = ARGS_FIXED(1)
81
ARGS_ANY = ARGS_ATLEAST(0)
82

    
83

    
84
def check_unit(option, opt, value):
85
  try:
86
    return utils.ParseUnit(value)
87
  except errors.UnitParseError, err:
88
    raise OptionValueError, ("option %s: %s" % (opt, err))
89

    
90

    
91
class CliOption(Option):
92
  TYPES = Option.TYPES + ("unit",)
93
  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
94
  TYPE_CHECKER["unit"] = check_unit
95

    
96

    
97
# optparse.py sets make_option, so we do it for our own option class, too
98
cli_option = CliOption
99

    
100

    
101
def _ParseArgs(argv, commands):
102
  """Parses the command line and return the function which must be
103
  executed together with its arguments
104

105
  Arguments:
106
    argv: the command line
107

108
    commands: dictionary with special contents, see the design doc for
109
    cmdline handling
110
  """
111
  if len(argv) == 0:
112
    binary = "<command>"
113
  else:
114
    binary = argv[0].split("/")[-1]
115

    
116
  if len(argv) > 1 and argv[1] == "--version":
117
    print "%s (ganeti) %s" % (binary, constants.RELEASE_VERSION)
118
    # Quit right away. That way we don't have to care about this special
119
    # argument. optparse.py does it the same.
120
    sys.exit(0)
121

    
122
  if len(argv) < 2 or argv[1] not in commands.keys():
123
    # let's do a nice thing
124
    sortedcmds = commands.keys()
125
    sortedcmds.sort()
126
    print ("Usage: %(bin)s {command} [options...] [argument...]"
127
           "\n%(bin)s <command> --help to see details, or"
128
           " man %(bin)s\n" % {"bin": binary})
129
    # compute the max line length for cmd + usage
130
    mlen = max([len(" %s %s" % (cmd, commands[cmd][3])) for cmd in commands])
131
    mlen = min(60, mlen) # should not get here...
132
    # and format a nice command list
133
    print "Commands:"
134
    for cmd in sortedcmds:
135
      cmdstr = " %s %s" % (cmd, commands[cmd][3])
136
      help_text = commands[cmd][4]
137
      help_lines = textwrap.wrap(help_text, 79-3-mlen)
138
      print "%-*s - %s" % (mlen, cmdstr,
139
                                          help_lines.pop(0))
140
      for line in help_lines:
141
        print "%-*s   %s" % (mlen, "", line)
142
    print
143
    return None, None, None
144
  cmd = argv.pop(1)
145
  func, nargs, parser_opts, usage, description = commands[cmd]
146
  parser_opts.append(_LOCK_OPT)
147
  parser = OptionParser(option_list=parser_opts,
148
                        description=description,
149
                        formatter=TitledHelpFormatter(),
150
                        usage="%%prog %s %s" % (cmd, usage))
151
  parser.disable_interspersed_args()
152
  options, args = parser.parse_args()
153
  if nargs is None:
154
    if len(args) != 0:
155
      print >> sys.stderr, ("Error: Command %s expects no arguments" % cmd)
156
      return None, None, None
157
  elif nargs < 0 and len(args) != -nargs:
158
    print >> sys.stderr, ("Error: Command %s expects %d argument(s)" %
159
                         (cmd, -nargs))
160
    return None, None, None
161
  elif nargs >= 0 and len(args) < nargs:
162
    print >> sys.stderr, ("Error: Command %s expects at least %d argument(s)" %
163
                         (cmd, nargs))
164
    return None, None, None
165

    
166
  return func, options, args
167

    
168

    
169
def _AskUser(text):
170
  """Ask the user a yes/no question.
171

172
  Args:
173
    questionstring - the question to ask.
174

175
  Returns:
176
    True or False depending on answer (No for False is default).
177

178
  """
179
  try:
180
    f = file("/dev/tty", "r+")
181
  except IOError:
182
    return False
183
  answer = False
184
  try:
185
    f.write(textwrap.fill(text))
186
    f.write('\n')
187
    f.write("y/[n]: ")
188
    line = f.readline(16).strip().lower()
189
    answer = line in ('y', 'yes')
190
  finally:
191
    f.close()
192
  return answer
193

    
194

    
195
def SubmitOpCode(op):
196
  """Function to submit an opcode.
197

198
  This is just a simple wrapper over the construction of the processor
199
  instance. It should be extended to better handle feedback and
200
  interaction functions.
201

202
  """
203
  proc = mcpu.Processor()
204
  return proc.ExecOpCode(op, logger.ToStdout)
205

    
206

    
207
def GenericMain(commands):
208
  """Generic main function for all the gnt-* commands.
209

210
  Argument: a dictionary with a special structure, see the design doc
211
  for command line handling.
212

213
  """
214
  # save the program name and the entire command line for later logging
215
  if sys.argv:
216
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
217
    if len(sys.argv) >= 2:
218
      binary += " " + sys.argv[1]
219
      old_cmdline = " ".join(sys.argv[2:])
220
    else:
221
      old_cmdline = ""
222
  else:
223
    binary = "<unknown program>"
224
    old_cmdline = ""
225

    
226
  func, options, args = _ParseArgs(sys.argv, commands)
227
  if func is None: # parse error
228
    return 1
229

    
230
  options._ask_user = _AskUser
231

    
232
  logger.SetupLogging(debug=options.debug, program=binary)
233

    
234
  try:
235
    utils.Lock('cmd', max_retries=options.lock_retries, debug=options.debug)
236
  except errors.LockError, err:
237
    logger.ToStderr(str(err))
238
    return 1
239

    
240
  if old_cmdline:
241
    logger.Info("run with arguments '%s'" % old_cmdline)
242
  else:
243
    logger.Info("run with no arguments")
244

    
245
  try:
246
    try:
247
      result = func(options, args)
248
    except errors.ConfigurationError, err:
249
      logger.Error("Corrupt configuration file: %s" % err)
250
      logger.ToStderr("Aborting.")
251
      result = 2
252
    except errors.HooksAbort, err:
253
      logger.ToStderr("Failure: hooks execution failed:")
254
      for node, script, out in err.args[0]:
255
        if out:
256
          logger.ToStderr("  node: %s, script: %s, output: %s" %
257
                          (node, script, out))
258
        else:
259
          logger.ToStderr("  node: %s, script: %s (no output)" %
260
                          (node, script))
261
      result = 1
262
    except errors.HooksFailure, err:
263
      logger.ToStderr("Failure: hooks general failure: %s" % str(err))
264
      result = 1
265
    except errors.OpPrereqError, err:
266
      logger.ToStderr("Failure: prerequisites not met for this"
267
                      " operation:\n%s" % str(err))
268
      result = 1
269
    except errors.OpExecError, err:
270
      logger.ToStderr("Failure: command execution error:\n%s" % str(err))
271
      result = 1
272
  finally:
273
    utils.Unlock('cmd')
274
    utils.LockCleanup()
275

    
276
  return result