Statistics
| Branch: | Tag: | Revision:

root / tools / cfgshell @ a8083063

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 = self.cluster_name = 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:/%s) " % (self.cluster_name, "/".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
      self.cluster_name = self.cfg.GetClusterName()
159
    except errors.ConfigurationError, err:
160
      print "Error: %s" % str(err)
161
    return False
162

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

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

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

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

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

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

    
189
    Valid arguments: either .. or a child of the current 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

    
201
    pointer = self.parents[-1]
202
    dirs, entries = self._get_entries(pointer)
203

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

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

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

    
223
    """
224
    print "/" + "/".join(self.path)
225
    return False
226

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
309
  def do_EOF(self, line):
310
    print
311
    return True
312

    
313
  def do_quit(self, line):
314
    """Exit the application.
315

    
316
    """
317
    print
318
    return True
319

    
320
class Error(Exception):
321
  """Generic exception"""
322
  pass
323

    
324

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

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

    
331
  Returns:
332
    (options, args), as returned by OptionParser.parse_args
333
  """
334

    
335
  parser = optparse.OptionParser()
336

    
337
  options, args = parser.parse_args()
338

    
339
  return options, args
340

    
341

    
342
def main():
343
  """Application entry point.
344

    
345
  This is just a wrapper over BootStrap, to handle our own exceptions.
346
  """
347
  options, args = ParseOptions()
348
  if args:
349
    cfg_file = args[0]
350
  else:
351
    cfg_file = None
352
  shell = ConfigShell(cfg_file=cfg_file)
353
  shell.cmdloop()
354

    
355

    
356
if __name__ == "__main__":
357
  main()