Statistics
| Branch: | Tag: | Revision:

root / tools / cfgshell @ 5fcdc80d

History | View | Annotate | Download (8.8 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

    
27
import os
28
import sys
29
import optparse
30
import time
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.cfg._OpenConfig()
156
      self.parents = [self.cfg._config_data]
157
      self.path = []
158
    except errors.ConfigurationError, err:
159
      print "Error: %s" % str(err)
160
    return False
161

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
313
  def do_EOF(self, line):
314
    print
315
    return True
316

    
317
  def do_quit(self, line):
318
    """Exit the application.
319

    
320
    """
321
    print
322
    return True
323

    
324

    
325
class Error(Exception):
326
  """Generic exception"""
327
  pass
328

    
329

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

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

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

    
340
  parser = optparse.OptionParser()
341

    
342
  options, args = parser.parse_args()
343

    
344
  return options, args
345

    
346

    
347
def main():
348
  """Application entry point.
349

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

    
360

    
361
if __name__ == "__main__":
362
  main()