This file is indexed.

/usr/share/ganeti/cfgshell is in ganeti 2.9.3-1.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
#! /usr/bin/python
#

# Copyright (C) 2006, 2007 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Tool to do manual changes to the config file.

"""

# functions in this module need to have a given name structure, so:
# pylint: disable=C0103


import optparse
import cmd

try:
  import readline
  _wd = readline.get_completer_delims()
  _wd = _wd.replace("-", "")
  readline.set_completer_delims(_wd)
  del _wd
except ImportError:
  pass

from ganeti import errors
from ganeti import config
from ganeti import objects


class ConfigShell(cmd.Cmd):
  """Command tool for editing the config file.

  Note that although we don't do saves after remove, the current
  ConfigWriter code does that; so we can't prevent someone from
  actually breaking the config with this tool. It's the users'
  responsibility to know what they're doing.

  """
  # all do_/complete_* functions follow the same API
  # pylint: disable=W0613
  prompt = "(/) "

  def __init__(self, cfg_file=None):
    """Constructor for the ConfigShell object.

    The optional cfg_file argument will be used to load a config file
    at startup.

    """
    cmd.Cmd.__init__(self)
    self.cfg = None
    self.parents = []
    self.path = []
    if cfg_file:
      self.do_load(cfg_file)
      self.postcmd(False, "")

  def emptyline(self):
    """Empty line handling.

    Note that the default will re-run the last command. We don't want
    that, and just ignore the empty line.

    """
    return False

  @staticmethod
  def _get_entries(obj):
    """Computes the list of subdirs and files in the given object.

    This, depending on the passed object entry, look at each logical
    child of the object and decides if it's a container or a simple
    object. Based on this, it computes the list of subdir and files.

    """
    dirs = []
    entries = []
    if isinstance(obj, objects.ConfigObject):
      for name in obj.GetAllSlots():
        child = getattr(obj, name, None)
        if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
          dirs.append(name)
        else:
          entries.append(name)
    elif isinstance(obj, (list, tuple)):
      for idx, child in enumerate(obj):
        if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
          dirs.append(str(idx))
        else:
          entries.append(str(idx))
    elif isinstance(obj, dict):
      dirs = obj.keys()

    return dirs, entries

  def precmd(self, line):
    """Precmd hook to prevent commands in invalid states.

    This will prevent everything except load and quit when no
    configuration is loaded.

    """
    if line.startswith("load") or line == "EOF" or line == "quit":
      return line
    if not self.parents or self.cfg is None:
      print "No config data loaded"
      return ""
    return line

  def postcmd(self, stop, line):
    """Postcmd hook to update the prompt.

    We show the current location in the prompt and this function is
    used to update it; this is only needed after cd and load, but we
    update it anyway.

    """
    if self.cfg is None:
      self.prompt = "(#no config) "
    else:
      self.prompt = "(/%s) " % ("/".join(self.path),)
    return stop

  def do_load(self, line):
    """Load function.

    Syntax: load [/path/to/config/file]

    This will load a new configuration, discarding any existing data
    (if any). If no argument has been passed, it will use the default
    config file location.

    """
    if line:
      arg = line
    else:
      arg = None
    try:
      self.cfg = config.ConfigWriter(cfg_file=arg, offline=True)
      self.parents = [self.cfg._config_data] # pylint: disable=W0212
      self.path = []
    except errors.ConfigurationError, err:
      print "Error: %s" % str(err)
    return False

  def do_ls(self, line):
    """List the current entry.

    This will show directories with a slash appended and files
    normally.

    """
    dirs, entries = self._get_entries(self.parents[-1])
    for i in dirs:
      print i + "/"
    for i in entries:
      print i
    return False

  def complete_cd(self, text, line, begidx, endidx):
    """Completion function for the cd command.

    """
    pointer = self.parents[-1]
    dirs, _ = self._get_entries(pointer)
    matches = [str(name) for name in dirs if name.startswith(text)]
    return matches

  def do_cd(self, line):
    """Changes the current path.

    Valid arguments: either .., /, "" (no argument) or a child of the current
    object.

    """
    if line == "..":
      if self.path:
        self.path.pop()
        self.parents.pop()
        return False
      else:
        print "Already at top level"
        return False
    elif len(line) == 0 or line == "/":
      self.parents = self.parents[0:1]
      self.path = []
      return False

    pointer = self.parents[-1]
    dirs, _ = self._get_entries(pointer)

    if line not in dirs:
      print "No such child"
      return False
    if isinstance(pointer, (dict, list, tuple)):
      if isinstance(pointer, (list, tuple)):
        line = int(line)
      new_obj = pointer[line]
    else:
      new_obj = getattr(pointer, line)
    self.parents.append(new_obj)
    self.path.append(str(line))
    return False

  def do_pwd(self, line):
    """Shows the current path.

    This duplicates the prompt functionality, but it's reasonable to
    have.

    """
    print "/" + "/".join(self.path)
    return False

  def complete_cat(self, text, line, begidx, endidx):
    """Completion for the cat command.

    """
    pointer = self.parents[-1]
    _, entries = self._get_entries(pointer)
    matches = [name for name in entries if name.startswith(text)]
    return matches

  def do_cat(self, line):
    """Shows the contents of the given file.

    This will display the contents of the given file, which must be a
    child of the current path (as shows by `ls`).

    """
    pointer = self.parents[-1]
    _, entries = self._get_entries(pointer)
    if line not in entries:
      print "No such entry"
      return False

    if isinstance(pointer, (dict, list, tuple)):
      if isinstance(pointer, (list, tuple)):
        line = int(line)
      val = pointer[line]
    else:
      val = getattr(pointer, line)
    print val
    return False

  def do_verify(self, line):
    """Verify the configuration.

    This verifies the contents of the configuration file (and not the
    in-memory data, as every modify operation automatically saves the
    file).

    """
    vdata = self.cfg.VerifyConfig()
    if vdata:
      print "Validation failed. Errors:"
      for text in vdata:
        print text
    return False

  def do_save(self, line):
    """Saves the configuration data.

    Note that is redundant (all modify operations automatically save
    the data), but it is good to use it as in the future that could
    change.

    """
    if self.cfg.VerifyConfig():
      print "Config data does not validate, refusing to save."
      return False
    self.cfg._WriteConfig() # pylint: disable=W0212

  def do_rm(self, line):
    """Removes an instance or a node.

    This function works only on instances or nodes. You must be in
    either `/nodes` or `/instances` and give a valid argument.

    """
    pointer = self.parents[-1]
    data = self.cfg._config_data  # pylint: disable=W0212
    if pointer not in (data.instances, data.nodes):
      print "Can only delete instances and nodes"
      return False
    if pointer == data.instances:
      if line in data.instances:
        self.cfg.RemoveInstance(line)
      else:
        print "Invalid instance name"
    else:
      if line in data.nodes:
        self.cfg.RemoveNode(line)
      else:
        print "Invalid node name"

  @staticmethod
  def do_EOF(line):
    """Exit the application.

    """
    print
    return True

  @staticmethod
  def do_quit(line):
    """Exit the application.

    """
    print
    return True


class Error(Exception):
  """Generic exception"""
  pass


def ParseOptions():
  """Parses the command line options.

  In case of command line errors, it will show the usage and exit the
  program.

  @return: a tuple (options, args), as returned by OptionParser.parse_args

  """
  parser = optparse.OptionParser()

  options, args = parser.parse_args()

  return options, args


def main():
  """Application entry point.

  """
  _, args = ParseOptions()
  if args:
    cfg_file = args[0]
  else:
    cfg_file = None
  shell = ConfigShell(cfg_file=cfg_file)
  shell.cmdloop()


if __name__ == "__main__":
  main()