Revision 4f3d5b76

b/Makefile.am
11 11

  
12 12
ACLOCAL_AMFLAGS = -I autotools
13 13
DOCBOOK_WRAPPER = $(top_srcdir)/autotools/docbook-wrapper
14
BUILD_BASH_COMPLETION = $(top_srcdir)/autotools/build-bash-completion
14 15
REPLACE_VARS_SED = autotools/replace_vars.sed
15 16

  
16 17
hypervisordir = $(pkgpythondir)/hypervisor
......
148 149

  
149 150
noinst_DATA = $(manhtml) doc/html
150 151

  
151
dist_sbin_SCRIPTS = \
152
	daemons/ganeti-noded \
153
	daemons/ganeti-watcher \
154
	daemons/ganeti-masterd \
155
	daemons/ganeti-confd \
156
	daemons/ganeti-rapi \
152
gnt_scripts = \
157 153
	scripts/gnt-backup \
158 154
	scripts/gnt-cluster \
159 155
	scripts/gnt-debug \
......
162 158
	scripts/gnt-node \
163 159
	scripts/gnt-os
164 160

  
161
dist_sbin_SCRIPTS = \
162
	daemons/ganeti-noded \
163
	daemons/ganeti-watcher \
164
	daemons/ganeti-masterd \
165
	daemons/ganeti-confd \
166
	daemons/ganeti-rapi \
167
	$(gnt_scripts)
168

  
165 169
dist_tools_SCRIPTS = \
166 170
	tools/burnin \
167 171
	tools/cfgshell \
......
179 183
	$(docrst) \
180 184
	doc/conf.py \
181 185
	doc/html \
182
	doc/examples/bash_completion.in \
183 186
	doc/examples/ganeti.initd.in \
184 187
	doc/examples/ganeti.cron.in \
185 188
	doc/examples/dumb-allocator \
......
266 269
		$(REPLACE_VARS_SED)
267 270
	sed -f $(REPLACE_VARS_SED) < $< > $@
268 271

  
272
doc/examples/bash_completion: $(BUILD_BASH_COMPLETION) lib/_autoconf.py \
273
	lib/cli.py $(gnt_scripts) tools/burnin
274
	TMPDIR=`mktemp -d ./buildtmpXXXXXX` && \
275
	cp -r scripts lib tools $$TMPDIR && \
276
	( \
277
		CDIR=`pwd` && \
278
		cd $$TMPDIR && \
279
		mv lib ganeti && \
280
		PYTHONPATH=. $$CDIR/$(BUILD_BASH_COMPLETION) > $$CDIR/$@; \
281
	); \
282
	rm -rf $$TMPDIR
283

  
269 284
doc/%.png: doc/%.dot
270 285
	@test -n "$(DOT)" || { echo 'dot' not found during configure; exit 1; }
271 286
	$(DOT) -Tpng -o $@ $<
......
332 347
	  echo "SOCAT_PATH = '$(SOCAT_PATH)'"; \
333 348
	  echo "LVM_STRIPECOUNT = $(LVM_STRIPECOUNT)"; \
334 349
	  echo "TOOLSDIR = '$(toolsdir)'"; \
350
	  echo "GNT_SCRIPTS = [$(foreach i,$(notdir $(gnt_scripts)),'$(i)',)]"; \
335 351
	} > $@
336 352

  
337 353
$(REPLACE_VARS_SED): Makefile stamp-directories
b/autotools/build-bash-completion
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2009 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
import imp
23
import optparse
24
import os
25
import sys
26
import re
27
from cStringIO import StringIO
28

  
29
from ganeti import constants
30
from ganeti import cli
31
from ganeti import utils
32

  
33
# _autoconf shouldn't be imported from anywhere except constants.py, but we're
34
# making an exception here because this script is only used at build time.
35
from ganeti import _autoconf
36

  
37

  
38
class ShellWriter:
39
  """Helper class to write scripts with indentation.
40

  
41
  """
42
  INDENT_STR = "  "
43

  
44
  def __init__(self, fh):
45
    self._fh = fh
46
    self._indent = 0
47

  
48
  def IncIndent(self):
49
    """Increase indentation level by 1.
50

  
51
    """
52
    self._indent += 1
53

  
54
  def DecIndent(self):
55
    """Decrease indentation level by 1.
56

  
57
    """
58
    assert self._indent > 0
59
    self._indent -= 1
60

  
61
  def Write(self, txt, *args):
62
    """Write line to output file.
63

  
64
    """
65
    self._fh.write(self._indent * self.INDENT_STR)
66

  
67
    if args:
68
      self._fh.write(txt % args)
69
    else:
70
      self._fh.write(txt)
71

  
72
    self._fh.write("\n")
73

  
74

  
75
def WritePreamble(sw):
76
  """Writes the script preamble.
77

  
78
  Helper functions should be written here.
79

  
80
  """
81
  sw.Write("# This script is automatically generated at build time.")
82
  sw.Write("# Do not modify manually.")
83

  
84
  sw.Write("_ganeti_nodes() {")
85
  sw.IncIndent()
86
  try:
87
    node_list_path = os.path.join(constants.DATA_DIR, "ssconf_node_list")
88
    sw.Write("cat %s", utils.ShellQuote(node_list_path))
89
  finally:
90
    sw.DecIndent()
91
  sw.Write("}")
92

  
93
  sw.Write("_ganeti_instances() {")
94
  sw.IncIndent()
95
  try:
96
    instance_list_path = os.path.join(constants.DATA_DIR,
97
                                      "ssconf_instance_list")
98
    sw.Write("cat %s", utils.ShellQuote(instance_list_path))
99
  finally:
100
    sw.DecIndent()
101
  sw.Write("}")
102

  
103
  sw.Write("_ganeti_jobs() {")
104
  sw.IncIndent()
105
  try:
106
    # FIXME: this is really going into the internals of the job queue
107
    sw.Write("local jlist=$( cd %s && echo job-*; )",
108
             utils.ShellQuote(constants.QUEUE_DIR))
109
    sw.Write("echo ${jlist//job-/}")
110
  finally:
111
    sw.DecIndent()
112
  sw.Write("}")
113

  
114
  sw.Write("_ganeti_os() {")
115
  sw.IncIndent()
116
  try:
117
    # FIXME: Make querying the master for all OSes cheap
118
    for path in constants.OS_SEARCH_PATH:
119
      sw.Write("( cd %s && echo *; )", utils.ShellQuote(path))
120
  finally:
121
    sw.DecIndent()
122
  sw.Write("}")
123

  
124
  # Params: <offset> <options with values> <options without values>
125
  # Result variable: $first_arg_idx
126
  sw.Write("_ganeti_find_first_arg() {")
127
  sw.IncIndent()
128
  try:
129
    sw.Write("local w i")
130

  
131
    sw.Write("first_arg_idx=")
132
    sw.Write("for (( i=$1; i < COMP_CWORD; ++i )); do")
133
    sw.IncIndent()
134
    try:
135
      sw.Write("w=${COMP_WORDS[$i]}")
136

  
137
      # Skip option value
138
      sw.Write("""if [[ -n "$2" && "$w" == @($2) ]]; then let ++i""")
139

  
140
      # Skip
141
      sw.Write("""elif [[ -n "$3" && "$w" == @($3) ]]; then :""")
142

  
143
      # Ah, we found the first argument
144
      sw.Write("else first_arg_idx=$i; break;")
145
      sw.Write("fi")
146
    finally:
147
      sw.DecIndent()
148
    sw.Write("done")
149
  finally:
150
    sw.DecIndent()
151
  sw.Write("}")
152

  
153
  # Params: <list of options separated by space>
154
  # Input variable: $first_arg_idx
155
  # Result variables: $arg_idx, $choices
156
  sw.Write("_ganeti_list_options() {")
157
  sw.IncIndent()
158
  try:
159
    sw.Write("""if [[ -z "$first_arg_idx" ]]; then""")
160
    sw.IncIndent()
161
    try:
162
      sw.Write("arg_idx=0")
163
      # Show options only if the current word starts with a dash
164
      sw.Write("""if [[ "$cur" == -* ]]; then""")
165
      sw.IncIndent()
166
      try:
167
        sw.Write("choices=$1")
168
      finally:
169
        sw.DecIndent()
170
      sw.Write("fi")
171
      sw.Write("return")
172
    finally:
173
      sw.DecIndent()
174
    sw.Write("fi")
175

  
176
    # Calculate position of current argument
177
    sw.Write("arg_idx=$(( COMP_CWORD - first_arg_idx ))")
178
    sw.Write("choices=")
179
  finally:
180
    sw.DecIndent()
181
  sw.Write("}")
182

  
183

  
184
def WriteCompReply(sw, args):
185
  sw.Write("""COMPREPLY=( $(compgen %s -- "$cur") )""", args)
186
  sw.Write("return")
187

  
188

  
189
class CompletionWriter:
190
  """Command completion writer class.
191

  
192
  """
193
  def __init__(self, arg_offset, opts, args):
194
    self.arg_offset = arg_offset
195
    self.opts = opts
196
    self.args = args
197

  
198
    for opt in opts:
199
      opt.all_names = sorted(opt._short_opts + opt._long_opts)
200

  
201
  def _FindFirstArgument(self, sw):
202
    ignore = []
203
    skip_one = []
204

  
205
    for opt in self.opts:
206
      if opt.takes_value():
207
        # Ignore value
208
        for i in opt.all_names:
209
          ignore.append("%s=*" % utils.ShellQuote(i))
210
          skip_one.append(utils.ShellQuote(i))
211
      else:
212
        ignore.extend([utils.ShellQuote(i) for i in opt.all_names])
213

  
214
    ignore = sorted(utils.UniqueSequence(ignore))
215
    skip_one = sorted(utils.UniqueSequence(skip_one))
216

  
217
    if ignore or skip_one:
218
      # Try to locate first argument
219
      sw.Write("_ganeti_find_first_arg %s %s %s",
220
               self.arg_offset + 1,
221
               utils.ShellQuote("|".join(skip_one)),
222
               utils.ShellQuote("|".join(ignore)))
223
    else:
224
      # When there are no options the first argument is always at position
225
      # offset + 1
226
      sw.Write("first_arg_idx=%s", self.arg_offset + 1)
227

  
228
  def _CompleteOptionValues(self, sw):
229
    # Group by values
230
    # "values" -> [optname1, optname2, ...]
231
    values = {}
232

  
233
    for opt in self.opts:
234
      if not opt.takes_value():
235
        continue
236

  
237
      # Only static choices implemented so far (e.g. no node list)
238
      suggest = getattr(opt, "completion_suggest", None)
239

  
240
      if not suggest:
241
        suggest = opt.choices
242

  
243
      if suggest:
244
        suggest_text = " ".join(sorted(suggest))
245
      else:
246
        suggest_text = ""
247

  
248
      values.setdefault(suggest_text, []).extend(opt.all_names)
249

  
250
    # Don't write any code if there are no option values
251
    if not values:
252
      return
253

  
254
    sw.Write("if [[ $COMP_CWORD -gt %s ]]; then", self.arg_offset + 1)
255
    sw.IncIndent()
256
    try:
257
      sw.Write("""case "$prev" in""")
258
      for (choices, names) in values.iteritems():
259
        # TODO: Implement completion for --foo=bar form
260
        sw.Write("%s)", "|".join([utils.ShellQuote(i) for i in names]))
261
        sw.IncIndent()
262
        try:
263
          WriteCompReply(sw, "-W %s" % utils.ShellQuote(choices))
264
        finally:
265
          sw.DecIndent()
266
        sw.Write(";;")
267
      sw.Write("""esac""")
268
    finally:
269
      sw.DecIndent()
270
    sw.Write("""fi""")
271

  
272
  def _CompleteArguments(self, sw):
273
    if not (self.opts or self.args):
274
      return
275

  
276
    all_option_names = []
277
    for opt in self.opts:
278
      all_option_names.extend(opt.all_names)
279
    all_option_names.sort()
280

  
281
    # List options if no argument has been specified yet
282
    sw.Write("_ganeti_list_options %s",
283
             utils.ShellQuote(" ".join(all_option_names)))
284

  
285
    if self.args:
286
      last_idx = len(self.args) - 1
287
      last_arg_end = 0
288
      varlen_arg_idx = None
289
      wrote_arg = False
290

  
291
      # Write some debug comments
292
      for idx, arg in enumerate(self.args):
293
        sw.Write("# %s: %r", idx, arg)
294

  
295
      sw.Write("compgenargs=")
296

  
297
      for idx, arg in enumerate(self.args):
298
        assert arg.min is not None and arg.min >= 0
299
        assert not (idx < last_idx and arg.max is None)
300

  
301
        if arg.min != arg.max or arg.max is None:
302
          if varlen_arg_idx is not None:
303
            raise Exception("Only one argument can have a variable length")
304
          varlen_arg_idx = idx
305

  
306
        compgenargs = []
307

  
308
        if isinstance(arg, cli.ArgUnknown):
309
          choices = ""
310
        elif isinstance(arg, cli.ArgSuggest):
311
          choices = utils.ShellQuote(" ".join(arg.choices))
312
        elif isinstance(arg, cli.ArgInstance):
313
          choices = "$(_ganeti_instances)"
314
        elif isinstance(arg, cli.ArgNode):
315
          choices = "$(_ganeti_nodes)"
316
        elif isinstance(arg, cli.ArgJobId):
317
          choices = "$(_ganeti_jobs)"
318
        elif isinstance(arg, cli.ArgFile):
319
          choices = ""
320
          compgenargs.append("-f")
321
        elif isinstance(arg, cli.ArgCommand):
322
          choices = ""
323
          compgenargs.append("-c")
324
        else:
325
          raise Exception("Unknown argument type %r" % arg)
326

  
327
        if arg.min == 1 and arg.max == 1:
328
          cmpcode = """"$arg_idx" == %d""" % (last_arg_end)
329
        elif arg.min == arg.max:
330
          cmpcode = (""""$arg_idx" -ge %d && "$arg_idx" -lt %d""" %
331
                     (last_arg_end, last_arg_end + arg.max))
332
        elif arg.max is None:
333
          cmpcode = """"$arg_idx" -ge %d""" % (last_arg_end)
334
        else:
335
          raise Exception("Unable to generate argument position condition")
336

  
337
        last_arg_end += arg.min
338

  
339
        if choices or compgenargs:
340
          if wrote_arg:
341
            condcmd = "elif"
342
          else:
343
            condcmd = "if"
344

  
345
          sw.Write("""%s [[ %s ]]; then""", condcmd, cmpcode)
346
          sw.IncIndent()
347
          try:
348
            if choices:
349
              sw.Write("""choices="$choices "%s""", choices)
350
            if compgenargs:
351
              sw.Write("compgenargs=%s", utils.ShellQuote(" ".join(compgenargs)))
352
          finally:
353
            sw.DecIndent()
354

  
355
          wrote_arg = True
356

  
357
      if wrote_arg:
358
        sw.Write("fi")
359

  
360
    if self.args:
361
      WriteCompReply(sw, """-W "$choices" $compgenargs""")
362
    else:
363
      # $compgenargs exists only if there are arguments
364
      WriteCompReply(sw, '-W "$choices"')
365

  
366
  def WriteTo(self, sw):
367
    self._FindFirstArgument(sw)
368
    self._CompleteOptionValues(sw)
369
    self._CompleteArguments(sw)
370

  
371

  
372
def WriteCompletion(sw, scriptname, funcname,
373
                    commands=None,
374
                    opts=None, args=None):
375
  """Writes the completion code for one command.
376

  
377
  @type sw: ShellWriter
378
  @param sw: Script writer
379
  @type scriptname: string
380
  @param scriptname: Name of command line program
381
  @type funcname: string
382
  @param funcname: Shell function name
383
  @type commands: list
384
  @param commands: List of all subcommands in this program
385

  
386
  """
387
  sw.Write("%s() {", funcname)
388
  sw.IncIndent()
389
  try:
390
    sw.Write('local cur="$2" prev="$3"')
391
    sw.Write("local i first_arg_idx choices compgenargs arg_idx")
392

  
393
    sw.Write("COMPREPLY=()")
394

  
395
    if opts is not None and args is not None:
396
      assert not commands
397
      CompletionWriter(0, opts, args).WriteTo(sw)
398

  
399
    else:
400
      sw.Write("""if [[ "$COMP_CWORD" == 1 ]]; then""")
401
      sw.IncIndent()
402
      try:
403
        # Complete the command name
404
        WriteCompReply(sw,
405
                       ("-W %s" %
406
                        utils.ShellQuote(" ".join(sorted(commands.keys())))))
407
      finally:
408
        sw.DecIndent()
409
      sw.Write("fi")
410

  
411
      # We're doing options and arguments to commands
412
      sw.Write("""case "${COMP_WORDS[1]}" in""")
413
      for cmd, (_, argdef, optdef, _, _) in commands.iteritems():
414
        if not (argdef or optdef):
415
          continue
416

  
417
        # TODO: Group by arguments and options
418
        sw.Write("%s)", utils.ShellQuote(cmd))
419
        sw.IncIndent()
420
        try:
421
          CompletionWriter(1, optdef, argdef).WriteTo(sw)
422
        finally:
423
          sw.DecIndent()
424

  
425
        sw.Write(";;")
426
      sw.Write("esac")
427
  finally:
428
    sw.DecIndent()
429
  sw.Write("}")
430

  
431
  sw.Write("complete -F %s -o filenames %s",
432
           utils.ShellQuote(funcname),
433
           utils.ShellQuote(scriptname))
434

  
435

  
436
def GetFunctionName(name):
437
  return "_" + re.sub(r"[^a-z0-9]+", "_", name.lower())
438

  
439

  
440
def LoadModule(filename):
441
  """Loads an external module by filename.
442

  
443
  """
444
  (name, ext) = os.path.splitext(filename)
445

  
446
  fh = open(filename, "U")
447
  try:
448
    return imp.load_module(name, fh, filename, (ext, "U", imp.PY_SOURCE))
449
  finally:
450
    fh.close()
451

  
452

  
453
def GetCommands(filename, module):
454
  """Returns the commands defined in a module.
455

  
456
  Aliases are also added as commands.
457

  
458
  """
459
  try:
460
    commands = getattr(module, "commands")
461
  except AttributeError, err:
462
    raise Exception("Script %s doesn't have 'commands' attribute" %
463
                    filename)
464

  
465
  # Use aliases
466
  aliases = getattr(module, "aliases", {})
467
  if aliases:
468
    commands = commands.copy()
469
    for name, target in aliases.iteritems():
470
      commands[name] = commands[target]
471

  
472
  return commands
473

  
474

  
475
def main():
476
  buf = StringIO()
477
  sw = ShellWriter(buf)
478

  
479
  WritePreamble(sw)
480

  
481
  # gnt-* scripts
482
  for scriptname in _autoconf.GNT_SCRIPTS:
483
    filename = "scripts/%s" % scriptname
484

  
485
    WriteCompletion(sw, scriptname,
486
                    GetFunctionName(scriptname),
487
                    commands=GetCommands(filename, LoadModule(filename)))
488

  
489
  # Burnin script
490
  burnin = LoadModule("tools/burnin")
491
  WriteCompletion(sw, "%s/burnin" % constants.TOOLSDIR, "_ganeti_burnin",
492
                  opts=burnin.OPTIONS, args=burnin.ARGUMENTS)
493

  
494
  print buf.getvalue()
495

  
496

  
497
if __name__ == "__main__":
498
  main()
/dev/null
1
_gnt_backup()
2
{
3
  local cur prev base_cmd cmds ilist nlist
4
  COMPREPLY=()
5
  cur="$2"
6
  prev="$3"
7
  #
8
  #  The basic options we'll complete.
9
  #
10
  cmds="export import list remove"
11

  
12
  # default completion is empty
13
  COMPREPLY=()
14

  
15
  if [[ ! -f "@LOCALSTATEDIR@/lib/ganeti/ssconf_cluster_name" ]]; then
16
    # cluster not initialized
17
    return 0
18
  fi
19

  
20
  ilist=$(< "@LOCALSTATEDIR@/lib/ganeti/ssconf_instance_list")
21
  nlist=$(< "@LOCALSTATEDIR@/lib/ganeti/ssconf_node_list")
22

  
23
  case "$COMP_CWORD" in
24
    1)
25
      # complete the command name
26
      COMPREPLY=( $(compgen -W "$cmds" -- ${cur}) )
27
      ;;
28
    *)
29
      # we're doing options to commands
30
      base_cmd="${COMP_WORDS[1]}"
31
      case "${base_cmd}" in
32
        export)
33
          case "$COMP_CWORD" in
34
            2)
35
              # options or instances
36
              COMPREPLY=( $(compgen -W "--no-shutdown -n $ilist" -- ${cur}) )
37
              ;;
38
            3)
39
              # if previous was option, we allow instance
40
              case "$prev" in
41
                -*)
42
                  COMPREPLY=( $(compgen -W "$ilist" -- ${cur}) )
43
                  ;;
44
              esac
45
          esac
46
          ;;
47
        import)
48
          case "$prev" in
49
            -t)
50
              COMPREPLY=( $(compgen -W "diskless file plain drbd" -- ${cur}) )
51
              ;;
52
            --src-node)
53
              COMPREPLY=( $(compgen -W "$nlist" -- ${cur}) )
54
              ;;
55
            --file-driver)
56
              COMPREPLY=( $(compgen -W "loop blktap" -- ${cur}) )
57
              ;;
58
            -*)
59
              # arguments to other options, we don't have completion yet
60
              ;;
61
            *)
62
              COMPREPLY=( $(compgen -W "-t -n -B -H -s --disks --net \
63
                              --no-nics --no-start --no-ip-check -I \
64
                              --src-node --src-dir --file-driver \
65
                              --file-storage-dir" -- ${cur}) )
66
              ;;
67
          esac
68
          ;;
69
        remove)
70
          if [[ "$COMP_CWORD" -eq 2 ]]; then
71
            COMPREPLY=( $(compgen -W "$ilist" -- ${cur}) )
72
          fi
73
          ;;
74
      esac
75
  esac
76

  
77
  return 0
78
}
79

  
80
complete -F _gnt_backup gnt-backup
81

  
82
_gnt_cluster()
83
{
84
  local cur prev cmds
85
  cur="$2"
86
  prev="$3"
87
  #
88
  #  The basic options we'll complete.
89
  #
90
  if [[ -e "@LOCALSTATEDIR@/lib/ganeti/ssconf_cluster_name" ]]; then
91
    cmds="add-tags command copyfile destroy getmaster info list-tags \
92
          masterfailover modify queue redist-conf remove-tags rename \
93
          repair-disk-sizes search-tags verify verify-disks version"
94
  else
95
    cmds="init"
96
  fi
97

  
98
  # default completion is empty
99
  COMPREPLY=()
100
  case "$COMP_CWORD" in
101
    1)
102
      # complete the command name
103
      COMPREPLY=($(compgen -W "$cmds" -- ${cur}))
104
      ;;
105
    2)
106
      # complete arguments to the command
107
      case "$prev" in
108
        "queue")
109
          COMPREPLY=( $(compgen -W "drain undrain info" -- ${cur}) )
110
          ;;
111
        "copyfile")
112
          COMPREPLY=( $(compgen -f -- ${cur}) )
113
          ;;
114
        "command")
115
          COMPREPLY=( $(compgen -c -- ${cur}) )
116
          ;;
117
	*)
118
          ;;
119
      esac
120
  esac
121

  
122
  return 0
123
}
124

  
125
complete -F _gnt_cluster gnt-cluster
126

  
127
_gnt_debug()
128
{
129
  local cur prev cmds
130
  cur="$2"
131
  prev="$3"
132

  
133
  cmds="allocator delay submit-job"
134

  
135
  # default completion is empty
136
  COMPREPLY=()
137

  
138
  if [[ ! -f "@LOCALSTATEDIR@/lib/ganeti/ssconf_cluster_name" ]]; then
139
    # cluster not initialized
140
    return 0
141
  fi
142

  
143
  case "$COMP_CWORD" in
144
    1)
145
      # complete the command name
146
      COMPREPLY=( $(compgen -W "$cmds" -- ${cur}) )
147
      ;;
148
    *)
149
      # we're doing options to commands
150
      base_cmd="${COMP_WORDS[1]}"
151
      case "${base_cmd}" in
152
        delay)
153
          if [[ "$prev" != -* ]]; then
154
            COMPREPLY=( $(compgen -W "--no-master -n" -- ${cur}) )
155
          fi
156
          ;;
157
        submit-job)
158
          if [[ "$COMP_CWORD" -eq 2 ]]; then
159
            COMPREPLY=( $(compgen -f -- ${cur}) )
160
          fi
161
          ;;
162
      esac
163
  esac
164

  
165
  return 0
166
}
167

  
168
complete -F _gnt_debug gnt-debug
169

  
170
_gnt_instance()
171
{
172
  local cur prev base_cmd cmds ilist nlist
173
  COMPREPLY=()
174
  cur="$2"
175
  prev="$3"
176
  #
177
  #  The basic options we'll complete.
178
  #
179
  cmds="activate-disks add add-tags batch-create console deactivate-disks \
180
        failover grow-disk info list list-tags migrate modify reboot \
181
        reinstall remove remove-tags rename replace-disks shutdown startup"
182

  
183
  # default completion is empty
184
  COMPREPLY=()
185

  
186
  if [[ ! -f "@LOCALSTATEDIR@/lib/ganeti/ssconf_cluster_name" ]]; then
187
    # cluster not initialized
188
    return 0
189
  fi
190

  
191
  ilist=$(< "@LOCALSTATEDIR@/lib/ganeti/ssconf_instance_list")
192
  nlist=$(< "@LOCALSTATEDIR@/lib/ganeti/ssconf_node_list")
193

  
194
  case "$COMP_CWORD" in
195
    1)
196
      # complete the command name
197
      COMPREPLY=( $(compgen -W "$cmds" -- ${cur}) )
198
      ;;
199
    *)
200
      # we're doing options to commands
201
      base_cmd="${COMP_WORDS[1]}"
202
      case "${base_cmd}" in
203
        # first, rules for multiple commands
204
        activate-disks|console|deactivate-disks|list-tags|rename|remove)
205
          # commands with only one instance argument, nothing else
206
          if [[ "$COMP_CWORD" -eq 2 ]]; then
207
            COMPREPLY=( $(compgen -W "$ilist" -- ${cur}) )
208
          fi
209
          ;;
210
        info)
211
          # commands with more than one instance
212
          COMPREPLY=( $(compgen -W "$ilist" -- ${cur}) )
213
          ;;
214
        add-tags|grow-disk|reinstall|remove-tags|replace-disks)
215
          # not very well handled
216
          COMPREPLY=( $(compgen -W "$ilist" -- ${cur}) )
217
          ;;
218
        startup|start|shutdown|stop|reboot)
219
          COMPREPLY=( $(compgen -W "--force-multiple --node --primary \
220
                          --secondary --all --submit $ilist" -- ${cur}) )
221
          ;;
222
        # individual commands
223
        add)
224
          case "$prev" in
225
            -t)
226
              COMPREPLY=( $(compgen -W "diskless file plain drbd" -- ${cur}) )
227
              ;;
228
            --file-driver)
229
              COMPREPLY=( $(compgen -W "loop blktap" -- ${cur}) )
230
              ;;
231
            -*)
232
              # arguments to other options, we don't have completion yet
233
              ;;
234
            *)
235
              COMPREPLY=( $(compgen -W "-t -n -o -B -H -s --disks --net \
236
                            --no-nics --no-start --no-ip-check -I \
237
                            --file-driver --file-storage-dir --submit" \
238
                -- ${cur}) )
239
              ;;
240
          esac
241
          ;;
242
        batch-create)
243
          # this only takes one file name
244
          COMPREPLY=( $(compgen -A file -- ${cur}) )
245
          ;;
246
        failover)
247
          case "$COMP_CWORD" in
248
            2)
249
              # options or instances
250
              COMPREPLY=( $(compgen -W "--ignore-failures $ilist" -- ${cur}) )
251
              ;;
252
            3)
253
              # if previous was option, we allow instance
254
              case "$prev" in
255
                -*)
256
                  COMPREPLY=( $(compgen -W "$ilist" -- ${cur}) )
257
                  ;;
258
              esac
259
          esac
260
          ;;
261
        list)
262
          COMPREPLY=( $(compgen -W "--no-headers --separator --units -o \
263
                          --sync $ilist" -- ${cur}) )
264
          ;;
265
        modify)
266
          COMPREPLY=( $(compgen -W "-H -B --disk --net $ilist" -- ${cur}) )
267
          ;;
268
        migrate)
269
          case "$COMP_CWORD" in
270
            2)
271
              # options or instances
272
              COMPREPLY=( $(compgen -W "--non-live --cleanup $ilist" -- \
273
                ${cur}) )
274
              ;;
275
            3)
276
              # if previous was option, we allow instance
277
              case "$prev" in
278
                -*)
279
                  COMPREPLY=( $(compgen -W "$ilist" -- ${cur}) )
280
                  ;;
281
              esac
282
          esac
283
      esac
284
  esac
285

  
286
  return 0
287
}
288

  
289
complete -F _gnt_instance gnt-instance
290

  
291
_gnt_job()
292
{
293
  local cur prev cmds
294
  cur="$2"
295
  prev="$3"
296

  
297
  cmds="archive autoarchive cancel info list"
298

  
299
  # default completion is empty
300
  COMPREPLY=()
301

  
302
  if [[ ! -f "@LOCALSTATEDIR@/lib/ganeti/ssconf_cluster_name" ]]; then
303
    # cluster not initialized
304
    return 0
305
  fi
306

  
307
  case "$COMP_CWORD" in
308
    1)
309
      # complete the command name
310
      COMPREPLY=( $(compgen -W "$cmds" -- ${cur}) )
311
      ;;
312
    *)
313
      # we're doing options to commands
314
      base_cmd="${COMP_WORDS[1]}"
315
      case "${base_cmd}" in
316
        archive|cancel|info)
317
          # FIXME: this is really going into the internals of the job queue
318
          jlist=$( cd @LOCALSTATEDIR@/lib/ganeti/queue; echo job-*)
319
          jlist=${jlist//job-/}
320
          COMPREPLY=( $(compgen -W "$jlist" -- ${cur}) )
321
          ;;
322
        list)
323
          COMPREPLY=( $(compgen -W "--no-headers --separator -o" -- ${cur}) )
324
          ;;
325

  
326
      esac
327
  esac
328

  
329
  return 0
330
}
331

  
332
complete -F _gnt_job gnt-job
333

  
334
_gnt_os()
335
{
336
  local cur prev cmds
337
  cur="$2"
338
  prev="$3"
339

  
340
  cmds="list diagnose"
341

  
342
  # default completion is empty
343
  COMPREPLY=()
344

  
345
  if [[ ! -f "@LOCALSTATEDIR@/lib/ganeti/ssconf_cluster_name" ]]; then
346
    # cluster not initialized
347
    return 0
348
  fi
349

  
350
  case "$COMP_CWORD" in
351
    1)
352
      # complete the command name
353
      COMPREPLY=( $(compgen -W "$cmds" -- ${cur}) )
354
      ;;
355
    *)
356
      # we're doing options to commands
357
      base_cmd="${COMP_WORDS[1]}"
358
      case "${base_cmd}" in
359
        list)
360
          if [[ "$COMP_CWORD" -eq 2 ]]; then
361
            COMPREPLY=( $(compgen -W "--no-headers" -- ${cur}) )
362
          fi
363
          ;;
364
      esac
365
  esac
366

  
367
  return 0
368
}
369

  
370
complete -F _gnt_os gnt-os
371

  
372
_gnt_node()
373
{
374
  local cur prev cmds base_cmd
375
  cur="$2"
376
  prev="$3"
377

  
378
  cmds="add add-tags evacuate failover info list list-tags migrate modify \
379
          remove remove-tags volumes"
380

  
381
  # default completion is empty
382
  COMPREPLY=()
383

  
384
  if [[ ! -f "@LOCALSTATEDIR@/lib/ganeti/ssconf_cluster_name" ]]; then
385
    # cluster not initialized
386
    return 0
387
  fi
388

  
389
  nlist=$(< "@LOCALSTATEDIR@/lib/ganeti/ssconf_node_list")
390

  
391
  case "$COMP_CWORD" in
392
    1)
393
      # complete the command name
394
      COMPREPLY=( $(compgen -W "$cmds" -- ${cur}) )
395
      ;;
396
    *)
397
      # we're doing options to commands
398
      base_cmd="${COMP_WORDS[1]}"
399
      case "${base_cmd}" in
400
        # first rules for multiple commands
401
        list-tags|remove)
402
          # commands with only one instance argument, nothing else
403
          if [[ "$COMP_CWORD" -eq 2 ]]; then
404
            COMPREPLY=( $(compgen -W "$nlist" -- ${cur}) )
405
          fi
406
          ;;
407
        add-tags|info|remove-tags|volumes)
408
          COMPREPLY=( $(compgen -W "$nlist" -- ${cur}) )
409
          ;;
410
        # individual commands
411
        add)
412
          # options or instances
413
          COMPREPLY=( $(compgen -W "-s --readd --no-ssh-key-check" -- ${cur}) )
414
          ;;
415
        evacuate)
416
          case "$COMP_CWORD" in
417
            2)
418
              # options or instances
419
              COMPREPLY=( $(compgen -W "-n -I $nlist" -- ${cur}) )
420
              ;;
421
            3)
422
              # if previous was option, we allow node
423
              case "$prev" in
424
                -*)
425
                  COMPREPLY=( $(compgen -W "$nlist" -- ${cur}) )
426
                  ;;
427
              esac
428
          esac
429
          ;;
430
        failover)
431
          case "$COMP_CWORD" in
432
            2)
433
              # options or instances
434
              COMPREPLY=( $(compgen -W "--ignore-failures $nlist" -- ${cur}) )
435
              ;;
436
            3)
437
              # if previous was option, we allow node
438
              case "$prev" in
439
                -*)
440
                  COMPREPLY=( $(compgen -W "$nlist" -- ${cur}) )
441
                  ;;
442
              esac
443
          esac
444
          ;;
445
        list)
446
          COMPREPLY=( $(compgen -W "--no-headers --separator --units -o \
447
                          --sync $nlist" -- ${cur}) )
448
          ;;
449
        modify)
450
          # TODO: after a non-option, don't allow options
451
          if [[ "$COMP_CWORD" -eq 2 || "$prev" != -* ]]; then
452
            COMPREPLY=( $(compgen -W "-C -O -D $nlist" -- ${cur}) )
453
          elif [[ "$prev" == -* ]]; then
454
            COMPREPLY=( $(compgen -W "yes no" -- ${cur}) )
455
          fi
456
          ;;
457
        migrate)
458
          case "$COMP_CWORD" in
459
            2)
460
              # options or nodes
461
              COMPREPLY=( $(compgen -W "--non-live $nlist" -- ${cur}) )
462
              ;;
463
            3)
464
              # if previous was option, we allow node
465
              case "$prev" in
466
                -*)
467
                  COMPREPLY=( $(compgen -W "$nlist" -- ${cur}) )
468
                  ;;
469
              esac
470
          esac
471
      esac
472
  esac
473

  
474
  return 0
475
}
476

  
477
complete -F _gnt_node gnt-node
478

  
479
# other tools
480

  
481
_gnt_tool_burnin()
482
{
483
  local cur prev
484
  cur="$2"
485
  prev="$3"
486

  
487
  # default completion is empty
488
  COMPREPLY=()
489

  
490
  if [[ ! -f "@LOCALSTATEDIR@/lib/ganeti/ssconf_cluster_name" ]]; then
491
    # cluster not initialized
492
    return 0
493
  fi
494

  
495
  nlist=$(< "@LOCALSTATEDIR@/lib/ganeti/ssconf_node_list")
496

  
497
  case "$prev" in
498
    -t)
499
      COMPREPLY=( $(compgen -W "diskless file plain drbd" -- ${cur}) )
500
      ;;
501
    --rename)
502
      # this needs an unused host name, so we don't complete it
503
      ;;
504
    -n|--nodes)
505
      # nodes from the cluster, comma separated
506
      # FIXME: make completion work for comma-separated values
507
      COMPREPLY=( $(compgen -W "$nlist" -- ${cur}) )
508
      ;;
509
    -o|--os)
510
      # the os list
511
      COMPREPLY=( $(compgen -W "$(gnt-os list --no-headers)" -- ${cur}) )
512
      ;;
513
    --disk-size|--disk-growth)
514
      # these take a number or unit, we can't really autocomplete, but
515
      # we show a couple of examples
516
      COMPREPLY=( $(compgen -W "128M 512M 1G 4G 1G,256M 4G,1G,1G 10G" -- \
517
        ${cur}) )
518
      ;;
519
    --mem-size)
520
      # this takes a number or unit, we can't really autocomplete, but
521
      # we show a couple of examples
522
      COMPREPLY=( $(compgen -W "128M 256M 512M 1G 4G 8G 12G 16G" -- ${cur}) )
523
      ;;
524
    --net-timeout)
525
      # this takes a number in seconds; again, we can't really complete
526
      COMPREPLY=( $(compgen -W "15 60 300 900" -- ${cur}) )
527
      ;;
528
    *)
529
      # all other, we just list the whole options
530
      COMPREPLY=( $(compgen -W "-o --disk-size --disk-growth --mem-size \
531
                      -v --verbose --no-replace1 --no-replace2 --no-failover \
532
                      --no-migrate --no-importexport --no-startstop \
533
                      --no-reinstall --no-reboot --no-activate-disks \
534
                      --no-add-disks --no-add-nics --no-nics \
535
                      --rename -t -n --nodes -I --iallocator -p --parallel \
536
                      --net-timeout -C --http-check -K --keep-instances" \
537
        -- ${cur}) )
538
  esac
539

  
540
  return 0
541
}
542

  
543
complete -F _gnt_tool_burnin @PREFIX@/lib/ganeti/tools/burnin

Also available in: Unified diff