root / tools / cfgshell @ d99dd9c7
History | View | Annotate | Download (9 kB)
1 | a8083063 | Iustin Pop | #!/usr/bin/python |
---|---|---|---|
2 | a8083063 | Iustin Pop | # |
3 | a8083063 | Iustin Pop | |
4 | a8083063 | Iustin Pop | # Copyright (C) 2006, 2007 Google Inc. |
5 | a8083063 | Iustin Pop | # |
6 | a8083063 | Iustin Pop | # This program is free software; you can redistribute it and/or modify |
7 | a8083063 | Iustin Pop | # it under the terms of the GNU General Public License as published by |
8 | a8083063 | Iustin Pop | # the Free Software Foundation; either version 2 of the License, or |
9 | a8083063 | Iustin Pop | # (at your option) any later version. |
10 | a8083063 | Iustin Pop | # |
11 | a8083063 | Iustin Pop | # This program is distributed in the hope that it will be useful, but |
12 | a8083063 | Iustin Pop | # WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | a8083063 | Iustin Pop | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | a8083063 | Iustin Pop | # General Public License for more details. |
15 | a8083063 | Iustin Pop | # |
16 | a8083063 | Iustin Pop | # You should have received a copy of the GNU General Public License |
17 | a8083063 | Iustin Pop | # along with this program; if not, write to the Free Software |
18 | a8083063 | Iustin Pop | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
19 | a8083063 | Iustin Pop | # 02110-1301, USA. |
20 | a8083063 | Iustin Pop | |
21 | a8083063 | Iustin Pop | |
22 | a8083063 | Iustin Pop | """Tool to do manual changes to the config file. |
23 | a8083063 | Iustin Pop | |
24 | a8083063 | Iustin Pop | """ |
25 | a8083063 | Iustin Pop | |
26 | 3ecf6786 | Iustin Pop | # functions in this module need to have a given name structure, so: |
27 | b459a848 | Andrea Spadaccini | # pylint: disable=C0103 |
28 | 3ecf6786 | Iustin Pop | |
29 | a8083063 | Iustin Pop | |
30 | a8083063 | Iustin Pop | import optparse |
31 | a8083063 | Iustin Pop | import cmd |
32 | a8083063 | Iustin Pop | |
33 | a8083063 | Iustin Pop | try: |
34 | a8083063 | Iustin Pop | import readline |
35 | a8083063 | Iustin Pop | _wd = readline.get_completer_delims() |
36 | a8083063 | Iustin Pop | _wd = _wd.replace("-", "") |
37 | a8083063 | Iustin Pop | readline.set_completer_delims(_wd) |
38 | a8083063 | Iustin Pop | del _wd |
39 | a8083063 | Iustin Pop | except ImportError: |
40 | a8083063 | Iustin Pop | pass |
41 | a8083063 | Iustin Pop | |
42 | a8083063 | Iustin Pop | from ganeti import errors |
43 | a8083063 | Iustin Pop | from ganeti import config |
44 | a8083063 | Iustin Pop | from ganeti import objects |
45 | a8083063 | Iustin Pop | |
46 | a8083063 | Iustin Pop | |
47 | a8083063 | Iustin Pop | class ConfigShell(cmd.Cmd): |
48 | a8083063 | Iustin Pop | """Command tool for editing the config file. |
49 | a8083063 | Iustin Pop | |
50 | a8083063 | Iustin Pop | Note that although we don't do saves after remove, the current |
51 | a8083063 | Iustin Pop | ConfigWriter code does that; so we can't prevent someone from |
52 | a8083063 | Iustin Pop | actually breaking the config with this tool. It's the users' |
53 | a8083063 | Iustin Pop | responsibility to know what they're doing. |
54 | a8083063 | Iustin Pop | |
55 | a8083063 | Iustin Pop | """ |
56 | 2d54e29c | Iustin Pop | # all do_/complete_* functions follow the same API |
57 | b459a848 | Andrea Spadaccini | # pylint: disable=W0613 |
58 | a8083063 | Iustin Pop | prompt = "(/) " |
59 | a8083063 | Iustin Pop | |
60 | a8083063 | Iustin Pop | def __init__(self, cfg_file=None): |
61 | a8083063 | Iustin Pop | """Constructor for the ConfigShell object. |
62 | a8083063 | Iustin Pop | |
63 | a8083063 | Iustin Pop | The optional cfg_file argument will be used to load a config file |
64 | a8083063 | Iustin Pop | at startup. |
65 | a8083063 | Iustin Pop | |
66 | a8083063 | Iustin Pop | """ |
67 | a8083063 | Iustin Pop | cmd.Cmd.__init__(self) |
68 | 5fcdc80d | Iustin Pop | self.cfg = None |
69 | a8083063 | Iustin Pop | self.parents = [] |
70 | a8083063 | Iustin Pop | self.path = [] |
71 | a8083063 | Iustin Pop | if cfg_file: |
72 | a8083063 | Iustin Pop | self.do_load(cfg_file) |
73 | a8083063 | Iustin Pop | self.postcmd(False, "") |
74 | a8083063 | Iustin Pop | |
75 | a8083063 | Iustin Pop | def emptyline(self): |
76 | a8083063 | Iustin Pop | """Empty line handling. |
77 | a8083063 | Iustin Pop | |
78 | a8083063 | Iustin Pop | Note that the default will re-run the last command. We don't want |
79 | a8083063 | Iustin Pop | that, and just ignore the empty line. |
80 | a8083063 | Iustin Pop | |
81 | a8083063 | Iustin Pop | """ |
82 | a8083063 | Iustin Pop | return False |
83 | a8083063 | Iustin Pop | |
84 | a8083063 | Iustin Pop | @staticmethod |
85 | a8083063 | Iustin Pop | def _get_entries(obj): |
86 | a8083063 | Iustin Pop | """Computes the list of subdirs and files in the given object. |
87 | a8083063 | Iustin Pop | |
88 | a8083063 | Iustin Pop | This, depending on the passed object entry, look at each logical |
89 | a8083063 | Iustin Pop | child of the object and decides if it's a container or a simple |
90 | a8083063 | Iustin Pop | object. Based on this, it computes the list of subdir and files. |
91 | a8083063 | Iustin Pop | |
92 | a8083063 | Iustin Pop | """ |
93 | a8083063 | Iustin Pop | dirs = [] |
94 | a8083063 | Iustin Pop | entries = [] |
95 | a8083063 | Iustin Pop | if isinstance(obj, objects.ConfigObject): |
96 | 32683096 | René Nussbaumer | for name in obj.GetAllSlots(): |
97 | a8083063 | Iustin Pop | child = getattr(obj, name, None) |
98 | a8083063 | Iustin Pop | if isinstance(child, (list, dict, tuple, objects.ConfigObject)): |
99 | a8083063 | Iustin Pop | dirs.append(name) |
100 | a8083063 | Iustin Pop | else: |
101 | a8083063 | Iustin Pop | entries.append(name) |
102 | a8083063 | Iustin Pop | elif isinstance(obj, (list, tuple)): |
103 | a8083063 | Iustin Pop | for idx, child in enumerate(obj): |
104 | a8083063 | Iustin Pop | if isinstance(child, (list, dict, tuple, objects.ConfigObject)): |
105 | a8083063 | Iustin Pop | dirs.append(str(idx)) |
106 | a8083063 | Iustin Pop | else: |
107 | a8083063 | Iustin Pop | entries.append(str(idx)) |
108 | a8083063 | Iustin Pop | elif isinstance(obj, dict): |
109 | a8083063 | Iustin Pop | dirs = obj.keys() |
110 | a8083063 | Iustin Pop | |
111 | a8083063 | Iustin Pop | return dirs, entries |
112 | a8083063 | Iustin Pop | |
113 | a8083063 | Iustin Pop | def precmd(self, line): |
114 | a8083063 | Iustin Pop | """Precmd hook to prevent commands in invalid states. |
115 | a8083063 | Iustin Pop | |
116 | a8083063 | Iustin Pop | This will prevent everything except load and quit when no |
117 | a8083063 | Iustin Pop | configuration is loaded. |
118 | a8083063 | Iustin Pop | |
119 | a8083063 | Iustin Pop | """ |
120 | 370f2768 | Michael Hanselmann | if line.startswith("load") or line == "EOF" or line == "quit": |
121 | a8083063 | Iustin Pop | return line |
122 | a8083063 | Iustin Pop | if not self.parents or self.cfg is None: |
123 | a8083063 | Iustin Pop | print "No config data loaded" |
124 | a8083063 | Iustin Pop | return "" |
125 | a8083063 | Iustin Pop | return line |
126 | a8083063 | Iustin Pop | |
127 | a8083063 | Iustin Pop | def postcmd(self, stop, line): |
128 | a8083063 | Iustin Pop | """Postcmd hook to update the prompt. |
129 | a8083063 | Iustin Pop | |
130 | a8083063 | Iustin Pop | We show the current location in the prompt and this function is |
131 | a8083063 | Iustin Pop | used to update it; this is only needed after cd and load, but we |
132 | a8083063 | Iustin Pop | update it anyway. |
133 | a8083063 | Iustin Pop | |
134 | a8083063 | Iustin Pop | """ |
135 | a8083063 | Iustin Pop | if self.cfg is None: |
136 | a8083063 | Iustin Pop | self.prompt = "(#no config) " |
137 | a8083063 | Iustin Pop | else: |
138 | 5fcdc80d | Iustin Pop | self.prompt = "(/%s) " % ("/".join(self.path),) |
139 | a8083063 | Iustin Pop | return stop |
140 | a8083063 | Iustin Pop | |
141 | a8083063 | Iustin Pop | def do_load(self, line): |
142 | a8083063 | Iustin Pop | """Load function. |
143 | a8083063 | Iustin Pop | |
144 | a8083063 | Iustin Pop | Syntax: load [/path/to/config/file] |
145 | a8083063 | Iustin Pop | |
146 | a8083063 | Iustin Pop | This will load a new configuration, discarding any existing data |
147 | a8083063 | Iustin Pop | (if any). If no argument has been passed, it will use the default |
148 | a8083063 | Iustin Pop | config file location. |
149 | a8083063 | Iustin Pop | |
150 | a8083063 | Iustin Pop | """ |
151 | a8083063 | Iustin Pop | if line: |
152 | a8083063 | Iustin Pop | arg = line |
153 | a8083063 | Iustin Pop | else: |
154 | a8083063 | Iustin Pop | arg = None |
155 | a8083063 | Iustin Pop | try: |
156 | a8083063 | Iustin Pop | self.cfg = config.ConfigWriter(cfg_file=arg, offline=True) |
157 | b459a848 | Andrea Spadaccini | self.parents = [self.cfg._config_data] # pylint: disable=W0212 |
158 | a8083063 | Iustin Pop | self.path = [] |
159 | a8083063 | Iustin Pop | except errors.ConfigurationError, err: |
160 | a8083063 | Iustin Pop | print "Error: %s" % str(err) |
161 | a8083063 | Iustin Pop | return False |
162 | a8083063 | Iustin Pop | |
163 | a8083063 | Iustin Pop | def do_ls(self, line): |
164 | a8083063 | Iustin Pop | """List the current entry. |
165 | a8083063 | Iustin Pop | |
166 | a8083063 | Iustin Pop | This will show directories with a slash appended and files |
167 | a8083063 | Iustin Pop | normally. |
168 | a8083063 | Iustin Pop | |
169 | a8083063 | Iustin Pop | """ |
170 | a8083063 | Iustin Pop | dirs, entries = self._get_entries(self.parents[-1]) |
171 | a8083063 | Iustin Pop | for i in dirs: |
172 | a8083063 | Iustin Pop | print i + "/" |
173 | a8083063 | Iustin Pop | for i in entries: |
174 | a8083063 | Iustin Pop | print i |
175 | a8083063 | Iustin Pop | return False |
176 | a8083063 | Iustin Pop | |
177 | a8083063 | Iustin Pop | def complete_cd(self, text, line, begidx, endidx): |
178 | a8083063 | Iustin Pop | """Completion function for the cd command. |
179 | a8083063 | Iustin Pop | |
180 | a8083063 | Iustin Pop | """ |
181 | a8083063 | Iustin Pop | pointer = self.parents[-1] |
182 | f4ad2ef0 | Iustin Pop | dirs, _ = self._get_entries(pointer) |
183 | a8083063 | Iustin Pop | matches = [str(name) for name in dirs if name.startswith(text)] |
184 | a8083063 | Iustin Pop | return matches |
185 | a8083063 | Iustin Pop | |
186 | a8083063 | Iustin Pop | def do_cd(self, line): |
187 | a8083063 | Iustin Pop | """Changes the current path. |
188 | a8083063 | Iustin Pop | |
189 | 033b7b64 | Michael Hanselmann | Valid arguments: either .., /, "" (no argument) or a child of the current |
190 | 033b7b64 | Michael Hanselmann | object. |
191 | a8083063 | Iustin Pop | |
192 | a8083063 | Iustin Pop | """ |
193 | a8083063 | Iustin Pop | if line == "..": |
194 | a8083063 | Iustin Pop | if self.path: |
195 | a8083063 | Iustin Pop | self.path.pop() |
196 | a8083063 | Iustin Pop | self.parents.pop() |
197 | a8083063 | Iustin Pop | return False |
198 | a8083063 | Iustin Pop | else: |
199 | a8083063 | Iustin Pop | print "Already at top level" |
200 | a8083063 | Iustin Pop | return False |
201 | 033b7b64 | Michael Hanselmann | elif len(line) == 0 or line == "/": |
202 | 033b7b64 | Michael Hanselmann | self.parents = self.parents[0:1] |
203 | 033b7b64 | Michael Hanselmann | self.path = [] |
204 | 033b7b64 | Michael Hanselmann | return False |
205 | a8083063 | Iustin Pop | |
206 | a8083063 | Iustin Pop | pointer = self.parents[-1] |
207 | f4ad2ef0 | Iustin Pop | dirs, _ = self._get_entries(pointer) |
208 | a8083063 | Iustin Pop | |
209 | a8083063 | Iustin Pop | if line not in dirs: |
210 | a8083063 | Iustin Pop | print "No such child" |
211 | a8083063 | Iustin Pop | return False |
212 | a8083063 | Iustin Pop | if isinstance(pointer, (dict, list, tuple)): |
213 | a8083063 | Iustin Pop | if isinstance(pointer, (list, tuple)): |
214 | a8083063 | Iustin Pop | line = int(line) |
215 | a8083063 | Iustin Pop | new_obj = pointer[line] |
216 | a8083063 | Iustin Pop | else: |
217 | a8083063 | Iustin Pop | new_obj = getattr(pointer, line) |
218 | a8083063 | Iustin Pop | self.parents.append(new_obj) |
219 | a8083063 | Iustin Pop | self.path.append(str(line)) |
220 | a8083063 | Iustin Pop | return False |
221 | a8083063 | Iustin Pop | |
222 | a8083063 | Iustin Pop | def do_pwd(self, line): |
223 | a8083063 | Iustin Pop | """Shows the current path. |
224 | a8083063 | Iustin Pop | |
225 | a8083063 | Iustin Pop | This duplicates the prompt functionality, but it's reasonable to |
226 | a8083063 | Iustin Pop | have. |
227 | a8083063 | Iustin Pop | |
228 | a8083063 | Iustin Pop | """ |
229 | a8083063 | Iustin Pop | print "/" + "/".join(self.path) |
230 | a8083063 | Iustin Pop | return False |
231 | a8083063 | Iustin Pop | |
232 | a8083063 | Iustin Pop | def complete_cat(self, text, line, begidx, endidx): |
233 | a8083063 | Iustin Pop | """Completion for the cat command. |
234 | a8083063 | Iustin Pop | |
235 | a8083063 | Iustin Pop | """ |
236 | a8083063 | Iustin Pop | pointer = self.parents[-1] |
237 | f4ad2ef0 | Iustin Pop | _, entries = self._get_entries(pointer) |
238 | a8083063 | Iustin Pop | matches = [name for name in entries if name.startswith(text)] |
239 | a8083063 | Iustin Pop | return matches |
240 | a8083063 | Iustin Pop | |
241 | a8083063 | Iustin Pop | def do_cat(self, line): |
242 | a8083063 | Iustin Pop | """Shows the contents of the given file. |
243 | a8083063 | Iustin Pop | |
244 | a8083063 | Iustin Pop | This will display the contents of the given file, which must be a |
245 | a8083063 | Iustin Pop | child of the current path (as shows by `ls`). |
246 | a8083063 | Iustin Pop | |
247 | a8083063 | Iustin Pop | """ |
248 | a8083063 | Iustin Pop | pointer = self.parents[-1] |
249 | f4ad2ef0 | Iustin Pop | _, entries = self._get_entries(pointer) |
250 | a8083063 | Iustin Pop | if line not in entries: |
251 | a8083063 | Iustin Pop | print "No such entry" |
252 | a8083063 | Iustin Pop | return False |
253 | a8083063 | Iustin Pop | |
254 | a8083063 | Iustin Pop | if isinstance(pointer, (dict, list, tuple)): |
255 | a8083063 | Iustin Pop | if isinstance(pointer, (list, tuple)): |
256 | a8083063 | Iustin Pop | line = int(line) |
257 | a8083063 | Iustin Pop | val = pointer[line] |
258 | a8083063 | Iustin Pop | else: |
259 | a8083063 | Iustin Pop | val = getattr(pointer, line) |
260 | a8083063 | Iustin Pop | print val |
261 | a8083063 | Iustin Pop | return False |
262 | a8083063 | Iustin Pop | |
263 | a8083063 | Iustin Pop | def do_verify(self, line): |
264 | a8083063 | Iustin Pop | """Verify the configuration. |
265 | a8083063 | Iustin Pop | |
266 | a8083063 | Iustin Pop | This verifies the contents of the configuration file (and not the |
267 | a8083063 | Iustin Pop | in-memory data, as every modify operation automatically saves the |
268 | a8083063 | Iustin Pop | file). |
269 | a8083063 | Iustin Pop | |
270 | a8083063 | Iustin Pop | """ |
271 | a8083063 | Iustin Pop | vdata = self.cfg.VerifyConfig() |
272 | a8083063 | Iustin Pop | if vdata: |
273 | a8083063 | Iustin Pop | print "Validation failed. Errors:" |
274 | a8083063 | Iustin Pop | for text in vdata: |
275 | a8083063 | Iustin Pop | print text |
276 | a8083063 | Iustin Pop | return False |
277 | a8083063 | Iustin Pop | |
278 | a8083063 | Iustin Pop | def do_save(self, line): |
279 | a8083063 | Iustin Pop | """Saves the configuration data. |
280 | a8083063 | Iustin Pop | |
281 | a8083063 | Iustin Pop | Note that is redundant (all modify operations automatically save |
282 | a8083063 | Iustin Pop | the data), but it is good to use it as in the future that could |
283 | a8083063 | Iustin Pop | change. |
284 | a8083063 | Iustin Pop | |
285 | a8083063 | Iustin Pop | """ |
286 | a8083063 | Iustin Pop | if self.cfg.VerifyConfig(): |
287 | a8083063 | Iustin Pop | print "Config data does not validate, refusing to save." |
288 | a8083063 | Iustin Pop | return False |
289 | b459a848 | Andrea Spadaccini | self.cfg._WriteConfig() # pylint: disable=W0212 |
290 | a8083063 | Iustin Pop | |
291 | a8083063 | Iustin Pop | def do_rm(self, line): |
292 | a8083063 | Iustin Pop | """Removes an instance or a node. |
293 | a8083063 | Iustin Pop | |
294 | a8083063 | Iustin Pop | This function works only on instances or nodes. You must be in |
295 | a8083063 | Iustin Pop | either `/nodes` or `/instances` and give a valid argument. |
296 | a8083063 | Iustin Pop | |
297 | a8083063 | Iustin Pop | """ |
298 | a8083063 | Iustin Pop | pointer = self.parents[-1] |
299 | b459a848 | Andrea Spadaccini | data = self.cfg._config_data # pylint: disable=W0212 |
300 | a8083063 | Iustin Pop | if pointer not in (data.instances, data.nodes): |
301 | a8083063 | Iustin Pop | print "Can only delete instances and nodes" |
302 | a8083063 | Iustin Pop | return False |
303 | a8083063 | Iustin Pop | if pointer == data.instances: |
304 | a8083063 | Iustin Pop | if line in data.instances: |
305 | a8083063 | Iustin Pop | self.cfg.RemoveInstance(line) |
306 | a8083063 | Iustin Pop | else: |
307 | a8083063 | Iustin Pop | print "Invalid instance name" |
308 | a8083063 | Iustin Pop | else: |
309 | a8083063 | Iustin Pop | if line in data.nodes: |
310 | a8083063 | Iustin Pop | self.cfg.RemoveNode(line) |
311 | a8083063 | Iustin Pop | else: |
312 | a8083063 | Iustin Pop | print "Invalid node name" |
313 | a8083063 | Iustin Pop | |
314 | 7e950d31 | Iustin Pop | @staticmethod |
315 | 7e950d31 | Iustin Pop | def do_EOF(line): |
316 | 3ecf6786 | Iustin Pop | """Exit the application. |
317 | 3ecf6786 | Iustin Pop | |
318 | 3ecf6786 | Iustin Pop | """ |
319 | a8083063 | Iustin Pop | |
320 | a8083063 | Iustin Pop | return True |
321 | a8083063 | Iustin Pop | |
322 | 7e950d31 | Iustin Pop | @staticmethod |
323 | 7e950d31 | Iustin Pop | def do_quit(line): |
324 | a8083063 | Iustin Pop | """Exit the application. |
325 | a8083063 | Iustin Pop | |
326 | a8083063 | Iustin Pop | """ |
327 | a8083063 | Iustin Pop | |
328 | a8083063 | Iustin Pop | return True |
329 | a8083063 | Iustin Pop | |
330 | 033b7b64 | Michael Hanselmann | |
331 | a8083063 | Iustin Pop | class Error(Exception): |
332 | a8083063 | Iustin Pop | """Generic exception""" |
333 | a8083063 | Iustin Pop | pass |
334 | a8083063 | Iustin Pop | |
335 | a8083063 | Iustin Pop | |
336 | a8083063 | Iustin Pop | def ParseOptions(): |
337 | a8083063 | Iustin Pop | """Parses the command line options. |
338 | a8083063 | Iustin Pop | |
339 | a8083063 | Iustin Pop | In case of command line errors, it will show the usage and exit the |
340 | a8083063 | Iustin Pop | program. |
341 | a8083063 | Iustin Pop | |
342 | 454723b5 | Iustin Pop | @return: a tuple (options, args), as returned by OptionParser.parse_args |
343 | a8083063 | Iustin Pop | |
344 | 454723b5 | Iustin Pop | """ |
345 | a8083063 | Iustin Pop | parser = optparse.OptionParser() |
346 | a8083063 | Iustin Pop | |
347 | a8083063 | Iustin Pop | options, args = parser.parse_args() |
348 | a8083063 | Iustin Pop | |
349 | a8083063 | Iustin Pop | return options, args |
350 | a8083063 | Iustin Pop | |
351 | a8083063 | Iustin Pop | |
352 | a8083063 | Iustin Pop | def main(): |
353 | a8083063 | Iustin Pop | """Application entry point. |
354 | a8083063 | Iustin Pop | |
355 | a8083063 | Iustin Pop | """ |
356 | f4ad2ef0 | Iustin Pop | _, args = ParseOptions() |
357 | a8083063 | Iustin Pop | if args: |
358 | a8083063 | Iustin Pop | cfg_file = args[0] |
359 | a8083063 | Iustin Pop | else: |
360 | a8083063 | Iustin Pop | cfg_file = None |
361 | a8083063 | Iustin Pop | shell = ConfigShell(cfg_file=cfg_file) |
362 | a8083063 | Iustin Pop | shell.cmdloop() |
363 | a8083063 | Iustin Pop | |
364 | a8083063 | Iustin Pop | |
365 | a8083063 | Iustin Pop | if __name__ == "__main__": |
366 | a8083063 | Iustin Pop | main() |