Statistics
| Branch: | Tag: | Revision:

root / tools / cfgshell @ 8b3fd458

History | View | Annotate | Download (8.9 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-msg=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
  prompt = "(/) "
57

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

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

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

    
73
  def emptyline(self):
74
    """Empty line handling.
75

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

    
79
    """
80
    return False
81

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

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

    
90
    """
91
    dirs = []
92
    entries = []
93
    if isinstance(obj, objects.ConfigObject):
94
      for name in obj.__slots__:
95
        child = getattr(obj, name, None)
96
        if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
97
          dirs.append(name)
98
        else:
99
          entries.append(name)
100
    elif isinstance(obj, (list, tuple)):
101
      for idx, child in enumerate(obj):
102
        if isinstance(child, (list, dict, tuple, objects.ConfigObject)):
103
          dirs.append(str(idx))
104
        else:
105
          entries.append(str(idx))
106
    elif isinstance(obj, dict):
107
      dirs = obj.keys()
108

    
109
    return dirs, entries
110

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

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

    
117
    """
118
    if line.startswith("load") or line == 'EOF' or line == "quit":
119
      return line
120
    if not self.parents or self.cfg is None:
121
      print "No config data loaded"
122
      return ""
123
    return line
124

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

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

    
132
    """
133
    if self.cfg is None:
134
      self.prompt = "(#no config) "
135
    else:
136
      self.prompt = "(/%s) " % ("/".join(self.path),)
137
    return stop
138

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

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

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

    
148
    """
149
    if line:
150
      arg = line
151
    else:
152
      arg = None
153
    try:
154
      self.cfg = config.ConfigWriter(cfg_file=arg, offline=True)
155
      self.parents = [self.cfg._config_data]
156
      self.path = []
157
    except errors.ConfigurationError, err:
158
      print "Error: %s" % str(err)
159
    return False
160

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

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

    
167
    """
168
    dirs, entries = self._get_entries(self.parents[-1])
169
    for i in dirs:
170
      print i + "/"
171
    for i in entries:
172
      print i
173
    return False
174

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

    
178
    """
179
    pointer = self.parents[-1]
180
    dirs, entries = self._get_entries(pointer)
181
    matches = [str(name) for name in dirs if name.startswith(text)]
182
    return matches
183

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

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

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

    
204
    pointer = self.parents[-1]
205
    dirs, entries = self._get_entries(pointer)
206

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

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

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

    
226
    """
227
    print "/" + "/".join(self.path)
228
    return False
229

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

    
233
    """
234
    pointer = self.parents[-1]
235
    dirs, entries = self._get_entries(pointer)
236
    matches = [name for name in entries if name.startswith(text)]
237
    return matches
238

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

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

    
245
    """
246
    pointer = self.parents[-1]
247
    dirs, entries = self._get_entries(pointer)
248
    if line not in entries:
249
      print "No such entry"
250
      return False
251

    
252
    if isinstance(pointer, (dict, list, tuple)):
253
      if isinstance(pointer, (list, tuple)):
254
        line = int(line)
255
      val = pointer[line]
256
    else:
257
      val = getattr(pointer, line)
258
    print val
259
    return False
260

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

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

    
268
    """
269
    vdata = self.cfg.VerifyConfig()
270
    if vdata:
271
      print "Validation failed. Errors:"
272
      for text in vdata:
273
        print text
274
    return False
275

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

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

    
283
    """
284
    if self.cfg.VerifyConfig():
285
      print "Config data does not validate, refusing to save."
286
      return False
287
    self.cfg._WriteConfig()
288

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

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

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

    
312
  def do_EOF(self, line):
313
    """Exit the application.
314

    
315
    """
316
    print
317
    return True
318

    
319
  def do_quit(self, line):
320
    """Exit the application.
321

    
322
    """
323
    print
324
    return True
325

    
326

    
327
class Error(Exception):
328
  """Generic exception"""
329
  pass
330

    
331

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

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

    
338
  Returns:
339
    (options, args), as returned by OptionParser.parse_args
340
  """
341

    
342
  parser = optparse.OptionParser()
343

    
344
  options, args = parser.parse_args()
345

    
346
  return options, args
347

    
348

    
349
def main():
350
  """Application entry point.
351

    
352
  This is just a wrapper over BootStrap, to handle our own exceptions.
353
  """
354
  options, args = ParseOptions()
355
  if args:
356
    cfg_file = args[0]
357
  else:
358
    cfg_file = None
359
  shell = ConfigShell(cfg_file=cfg_file)
360
  shell.cmdloop()
361

    
362

    
363
if __name__ == "__main__":
364
  main()