Statistics
| Branch: | Tag: | Revision:

root / tools / cfgshell @ 36c70d4d

History | View | Annotate | Download (9.1 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
"""Tool to do manual changes to the config file.
23

    
24
"""
25

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

    
29

    
30
import optparse
31
import cmd
32

    
33
try:
34
  import readline
35
  _wd = readline.get_completer_delims()
36
  _wd = _wd.replace("-", "")
37
  readline.set_completer_delims(_wd)
38
  del _wd
39
except ImportError:
40
  pass
41

    
42
from ganeti import errors
43
from ganeti import config
44
from ganeti import objects
45

    
46

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

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

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

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

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

    
66
    """
67
    cmd.Cmd.__init__(self)
68
    self.cfg = None
69
    self.parents = []
70
    self.path = []
71
    if cfg_file:
72
      self.do_load(cfg_file)
73
      self.postcmd(False, "")
74

    
75
  def emptyline(self):
76
    """Empty line handling.
77

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

    
81
    """
82
    return False
83

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

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

    
92
    """
93
    dirs = []
94
    entries = []
95
    if isinstance(obj, objects.ConfigObject):
96
      # pylint: disable=W0212
97
      # yes, we're using a protected member
98
      for name in obj._all_slots():
99
        child = getattr(obj, name, None)
100
        if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
101
          dirs.append(name)
102
        else:
103
          entries.append(name)
104
    elif isinstance(obj, (list, tuple)):
105
      for idx, child in enumerate(obj):
106
        if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
107
          dirs.append(str(idx))
108
        else:
109
          entries.append(str(idx))
110
    elif isinstance(obj, dict):
111
      dirs = obj.keys()
112

    
113
    return dirs, entries
114

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

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

    
121
    """
122
    if line.startswith("load") or line == 'EOF' or line == "quit":
123
      return line
124
    if not self.parents or self.cfg is None:
125
      print "No config data loaded"
126
      return ""
127
    return line
128

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

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

    
136
    """
137
    if self.cfg is None:
138
      self.prompt = "(#no config) "
139
    else:
140
      self.prompt = "(/%s) " % ("/".join(self.path),)
141
    return stop
142

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

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

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

    
152
    """
153
    if line:
154
      arg = line
155
    else:
156
      arg = None
157
    try:
158
      self.cfg = config.ConfigWriter(cfg_file=arg, offline=True)
159
      self.parents = [self.cfg._config_data] # pylint: disable=W0212
160
      self.path = []
161
    except errors.ConfigurationError, err:
162
      print "Error: %s" % str(err)
163
    return False
164

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

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

    
171
    """
172
    dirs, entries = self._get_entries(self.parents[-1])
173
    for i in dirs:
174
      print i + "/"
175
    for i in entries:
176
      print i
177
    return False
178

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

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

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

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

    
194
    """
195
    if line == "..":
196
      if self.path:
197
        self.path.pop()
198
        self.parents.pop()
199
        return False
200
      else:
201
        print "Already at top level"
202
        return False
203
    elif len(line) == 0 or line == "/":
204
      self.parents = self.parents[0:1]
205
      self.path = []
206
      return False
207

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

    
211
    if line not in dirs:
212
      print "No such child"
213
      return False
214
    if isinstance(pointer, (dict, list, tuple)):
215
      if isinstance(pointer, (list, tuple)):
216
        line = int(line)
217
      new_obj = pointer[line]
218
    else:
219
      new_obj = getattr(pointer, line)
220
    self.parents.append(new_obj)
221
    self.path.append(str(line))
222
    return False
223

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

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

    
230
    """
231
    print "/" + "/".join(self.path)
232
    return False
233

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

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

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

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

    
249
    """
250
    pointer = self.parents[-1]
251
    _, entries = self._get_entries(pointer)
252
    if line not in entries:
253
      print "No such entry"
254
      return False
255

    
256
    if isinstance(pointer, (dict, list, tuple)):
257
      if isinstance(pointer, (list, tuple)):
258
        line = int(line)
259
      val = pointer[line]
260
    else:
261
      val = getattr(pointer, line)
262
    print val
263
    return False
264

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

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

    
272
    """
273
    vdata = self.cfg.VerifyConfig()
274
    if vdata:
275
      print "Validation failed. Errors:"
276
      for text in vdata:
277
        print text
278
    return False
279

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

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

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

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

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

    
299
    """
300
    pointer = self.parents[-1]
301
    data = self.cfg._config_data  # pylint: disable=W0212
302
    if pointer not in (data.instances, data.nodes):
303
      print "Can only delete instances and nodes"
304
      return False
305
    if pointer == data.instances:
306
      if line in data.instances:
307
        self.cfg.RemoveInstance(line)
308
      else:
309
        print "Invalid instance name"
310
    else:
311
      if line in data.nodes:
312
        self.cfg.RemoveNode(line)
313
      else:
314
        print "Invalid node name"
315

    
316
  @staticmethod
317
  def do_EOF(line):
318
    """Exit the application.
319

    
320
    """
321
    print
322
    return True
323

    
324
  @staticmethod
325
  def do_quit(line):
326
    """Exit the application.
327

    
328
    """
329
    print
330
    return True
331

    
332

    
333
class Error(Exception):
334
  """Generic exception"""
335
  pass
336

    
337

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

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

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

    
346
  """
347
  parser = optparse.OptionParser()
348

    
349
  options, args = parser.parse_args()
350

    
351
  return options, args
352

    
353

    
354
def main():
355
  """Application entry point.
356

    
357
  """
358
  _, args = ParseOptions()
359
  if args:
360
    cfg_file = args[0]
361
  else:
362
    cfg_file = None
363
  shell = ConfigShell(cfg_file=cfg_file)
364
  shell.cmdloop()
365

    
366

    
367
if __name__ == "__main__":
368
  main()