diff --git a/shell/modules/completion.py b/shell/modules/completion.py new file mode 100644 index 0000000000..40d27d8274 --- /dev/null +++ b/shell/modules/completion.py @@ -0,0 +1,491 @@ +# Copyright (C) 2008 Dejan Muhamedagic +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import os +import time +import copy +import readline + +from cibconfig import CibFactory +from cibstatus import CibStatus +from levels import Levels +from ra import * +from vars import Vars +from utils import * +from xmlutil import * + +class CompletionHelp(object): + ''' + Print some help on whatever last word in the line. + ''' + timeout = 60 # don't print again and again + def __init__(self): + self.laststamp = 0 + self.lastitem = '' + def help(self,f,*args): + words = readline.get_line_buffer().split() + if not words: + return + key = words[-1] + if key.endswith('='): + key = key[0:-1] + if self.lastitem == key and \ + time.time() - self.laststamp < self.timeout: + return + help_s = f(key,*args) + if help_s: + print "\n%s" % help_s + print "%s%s" % (vars.prompt,readline.get_line_buffer()), + self.laststamp = time.time() + self.lastitem = key + +def attr_cmds(idx,delimiter = False): + if delimiter: + return ' ' + return ["delete","set","show"] +def nodes_list(idx,delimiter = False): + if delimiter: + return ' ' + return listnodes() +def shadows_list(idx,delimiter = False): + if delimiter: + return ' ' + return listshadows() +def listtemplates(): + l = [] + for f in os.listdir(vars.tmpl_dir): + if os.path.isfile("%s/%s" % (vars.tmpl_dir,f)): + l.append(f) + return l +def listconfigs(): + l = [] + for f in os.listdir(vars.tmpl_conf_dir): + if os.path.isfile("%s/%s" % (vars.tmpl_conf_dir,f)): + l.append(f) + return l +def templates_list(idx,delimiter = False): + if delimiter: + return ' ' + return listtemplates() +def config_list(idx,delimiter = False): + if delimiter: + return ' ' + return listconfigs() +def config_list_method(idx,delimiter = False): + if delimiter: + return ' ' + return listconfigs() + ["replace","update"] +def shadows_live_list(idx,delimiter = False): + if delimiter: + return ' ' + return listshadows() + ['live'] +def rsc_list(idx,delimiter = False): + if delimiter: + return ' ' + doc = resources_xml() + if not doc: + return [] + nodes = get_interesting_nodes(doc,[]) + return [x.getAttribute("id") for x in nodes if is_resource(x)] +def null_list(idx,delimiter = False): + if delimiter: + return ' ' + return [] +def loop(idx,delimiter = False): + "just a marker in a list" + pass +def id_xml_list(idx,delimiter = False): + if delimiter: + return ' ' + return cib_factory.id_list() + ['xml','changed'] +def id_list(idx,delimiter = False): + if delimiter: + return ' ' + return cib_factory.id_list() +def f_prim_id_list(idx,delimiter = False): + if delimiter: + return ' ' + return cib_factory.f_prim_id_list() +def f_children_id_list(idx,delimiter = False): + if delimiter: + return ' ' + return cib_factory.f_children_id_list() +def rsc_id_list(idx,delimiter = False): + if delimiter: + return ' ' + return cib_factory.rsc_id_list() +def node_id_list(idx,delimiter = False): + if delimiter: + return ' ' + return cib_factory.node_id_list() +def node_attr_keyw_list(idx,delimiter = False): + if delimiter: + return ' ' + return vars.node_attributes_keyw +def status_node_list(idx,delimiter = False): + if delimiter: + return ' ' + return cib_status.status_node_list() +def status_rsc_list(idx,delimiter = False): + if delimiter: + return ' ' + return cib_status.status_rsc_list() +def node_states_list(idx,delimiter = False): + if delimiter: + return ' ' + return vars.node_states +def ra_operations_list(idx,delimiter = False): + if delimiter: + return ' ' + return vars.ra_operations +def lrm_exit_codes_list(idx,delimiter = False): + if delimiter: + return ' ' + return vars.lrm_exit_codes.keys() +def lrm_status_codes_list(idx,delimiter = False): + if delimiter: + return ' ' + return vars.lrm_status_codes.keys() +def skills_list(idx,delimiter = False): + if delimiter: + return ' ' + return user_prefs.skill_levels.keys() +def ra_classes_list(idx,delimiter = False): + if delimiter: + return ':' + return ra_classes() + +# +# completion for primitives including help for parameters +# (help also available for properties) +# +def get_primitive_type(words): + try: + idx = words.index("primitive") + 2 + type_word = words[idx] + except: type_word = '' + return type_word +def ra_type_list(toks,idx,delimiter): + if idx == 2: + if toks[0] == "ocf": + dchar = ':' + l = ra_providers_all() + else: + dchar = ' ' + l = ra_types(toks[0]) + elif idx == 3: + dchar = ' ' + if toks[0] == "ocf": + l = ra_types(toks[0],toks[1]) + else: + l = ra_types(toks[0]) + if delimiter: + return dchar + return l +def prim_meta_attr_list(idx,delimiter = False): + if delimiter: + return '=' + return vars.rsc_meta_attributes +def op_attr_list(idx,delimiter = False): + if delimiter: + return '=' + return vars.op_attributes +def operations_list(): + return vars.op_cli_names +def prim_complete_meta(ra,delimiter = False): + if delimiter: + return '=' + return prim_meta_attr_list(0,delimiter) +def prim_complete_op(ra,delimiter): + words = split_buffer() + if (readline.get_line_buffer()[-1] == ' ' and words[-1] == "op") \ + or (readline.get_line_buffer()[-1] != ' ' and words[-2] == "op"): + dchar = ' ' + l = operations_list() + else: + if readline.get_line_buffer()[-1] == '=': + dchar = ' ' + l = [] + else: + dchar = '=' + l = op_attr_list() + if delimiter: + return dchar + return l +def prim_complete_params(ra,delimiter): + if readline.get_line_buffer()[-1] == '=': + dchar = ' ' + l = [] + else: + dchar = '=' + l = ra.params().keys() + if delimiter: + return dchar + return l +def prim_params_info(key,ra): + return ra.meta_parameter(key) +def meta_attr_info(key,ra): + pass +def op_attr_info(key,ra): + pass +def get_lastkeyw(words,keyw): + revwords = copy.copy(words) + revwords.reverse() + for w in revwords: + if w in keyw: + return w +def primitive_complete_complex(idx,delimiter = False): + ''' + This completer depends on the content of the line, i.e. on + previous tokens, in particular on the type of the RA. + ''' + completers_set = { + "params": (prim_complete_params, prim_params_info), + "meta": (prim_complete_meta, meta_attr_info), + "op": (prim_complete_op, op_attr_info), + } + # manage the resource type + words = readline.get_line_buffer().split() + type_word = get_primitive_type(words) + toks = type_word.split(':') + if toks[0] != "ocf": + idx += 1 + if idx in (2,3): + return ra_type_list(toks,idx,delimiter) + # create an ra object + ra = None + ra_class,provider,rsc_type = disambiguate_ra_type(type_word) + if ra_type_validate(type_word,ra_class,provider,rsc_type): + ra = RAInfo(ra_class,rsc_type,provider) + keywords = completers_set.keys() + if idx == 4: + if delimiter: + return ' ' + return keywords + lastkeyw = get_lastkeyw(words,keywords) + if '=' in words[-1] and readline.get_line_buffer()[-1] != ' ': + if not delimiter and lastkeyw and \ + readline.get_line_buffer()[-1] == '=' and len(words[-1]) > 1: + compl_help.help(completers_set[lastkeyw][1],ra) + if delimiter: + return ' ' + return ['*'] + else: + if lastkeyw: + return completers_set[lastkeyw][0](ra,delimiter) +def property_complete(idx,delimiter = False): + ''' + This completer depends on the content of the line, i.e. on + previous tokens. + ''' + if not vars.pe_metadata: + vars.pe_metadata = RAInfo("pengine","metadata") + words = readline.get_line_buffer().split() + if '=' in words[-1] and readline.get_line_buffer()[-1] != ' ': + if not delimiter and \ + readline.get_line_buffer()[-1] == '=' and len(words[-1]) > 1: + compl_help.help(prim_params_info,vars.pe_metadata) + if delimiter: + return ' ' + return ['*'] + else: + return prim_complete_params(vars.pe_metadata,delimiter) + +# +# core completer stuff +# +def lookup_dynamic(fun_list,idx,f_idx,words): + if not fun_list: + return [] + if fun_list[f_idx] == loop: + f_idx -= 1 + f = fun_list[f_idx] + w = words[0] + wordlist = f(idx) + delimiter = f(idx,1) + if len(wordlist) == 1 and wordlist[0] == '*': + return lookup_dynamic(fun_list,idx+1,f_idx+1,words[1:]) + elif len(words) == 1: + return [x+delimiter for x in wordlist if x.startswith(w)] + return lookup_dynamic(fun_list,idx+1,f_idx+1,words[1:]) +def lookup_words(ctab,words): + if not ctab: + return [] + if type(ctab) == type(()): + return lookup_dynamic(ctab,0,0,words) + if len(words) == 1: + return [x+' ' for x in ctab if x.startswith(words[0])] + elif words[0] in ctab.keys(): + return lookup_words(ctab[words[0]],words[1:]) + return [] +def split_buffer(): + p = readline.get_line_buffer() + p = p.replace(':',' ').replace('=',' ') + return p.split() + +def completer(txt,state): + levels = Levels.getInstance() + words = split_buffer() + if readline.get_begidx() == readline.get_endidx(): + words.append('') + matched = lookup_words(levels.completion_tab,words) + matched.append(None) + return matched[state] +def setup_readline(): + readline.set_history_length(100) + readline.parse_and_bind("tab: complete") + readline.set_completer(completer) + readline.set_completer_delims(\ + readline.get_completer_delims().replace('-','').replace('/','').replace('=','')) + try: readline.read_history_file(vars.hist_file) + except: pass + +# +# a dict of completer functions +# (feel free to add more completers) +# +completer_lists = { + "options" : { + "skill-level" : (skills_list,), + "editor" : None, + "pager" : None, + "user" : None, + "output" : None, + "colorscheme" : None, + "check-frequency" : None, + "check-mode" : None, + "sort-elements" : None, + "save" : None, + "show" : None, + }, + "cib" : { + "new" : None, + "delete" : (shadows_list,), + "reset" : (shadows_list,), + "commit" : (shadows_list,), + "use" : (shadows_live_list,), + "diff" : None, + "list" : None, + "import" : None, + "cibstatus" : None, + }, + "template" : { + "new" : (null_list,templates_list,loop), + "load" : (config_list,), + "edit" : (config_list,), + "delete" : (config_list,), + "show" : (config_list,), + "apply" : (config_list_method,config_list), + "list" : None, + }, + "resource" : { + "status" : (rsc_list,), + "start" : (rsc_list,), + "stop" : (rsc_list,), + "restart" : (rsc_list,), + "promote" : (rsc_list,), + "demote" : (rsc_list,), + "manage" : (rsc_list,), + "unmanage" : (rsc_list,), + "migrate" : (rsc_list,nodes_list), + "unmigrate" : (rsc_list,), + "param" : (rsc_list,attr_cmds), + "meta" : (rsc_list,attr_cmds), + "utilization" : (rsc_list,attr_cmds), + "failcount" : (rsc_list,attr_cmds,nodes_list), + "cleanup" : (rsc_list,nodes_list), + "refresh" : (nodes_list,), + "reprobe" : (nodes_list,), + }, + "node" : { + "status" : (nodes_list,), + "show" : (nodes_list,), + "standby" : (nodes_list,), + "online" : (nodes_list,), + "fence" : (nodes_list,), + "delete" : (nodes_list,), + "clearstate" : (nodes_list,), + "attribute" : (nodes_list,attr_cmds), + "utilization" : (nodes_list,attr_cmds), + "status-attr" : (nodes_list,attr_cmds), + }, + "ra" : { + "classes" : None, + "list" : None, + "providers" : None, + "meta" : None, + }, + "cibstatus" : { + "show" : None, + "save" : None, + "load" : None, + "origin" : None, + "node" : (status_node_list,node_states_list), + "op" : (ra_operations_list,status_rsc_list,lrm_exit_codes_list,lrm_status_codes_list,status_node_list), + "run" : None, + "simulate" : None, + "quorum" : None, + }, + "configure" : { + "erase" : None, + "verify" : None, + "refresh" : None, + "ptest" : None, + "commit" : None, + "upgrade" : None, + "show" : (id_xml_list,id_list,loop), + "edit" : (id_xml_list,id_list,loop), + "filter" : (null_list,id_xml_list,id_list,loop), + "delete" : (id_list,loop), + "default-timeouts" : (id_list,loop), + "rename" : (id_list,id_list), + "save" : None, + "load" : None, + "node" : (node_id_list,node_attr_keyw_list), + "primitive" : (null_list,ra_classes_list,primitive_complete_complex,loop), + "group" : (null_list,f_prim_id_list,loop), + "clone" : (null_list,f_children_id_list), + "ms" : (null_list,f_children_id_list), + "location" : (null_list,rsc_id_list), + "colocation" : (null_list,null_list,rsc_id_list,loop), + "order" : (null_list,null_list,rsc_id_list,loop), + "property" : (property_complete,loop), + "rsc_defaults" : (prim_complete_meta,loop), + "op_defaults" : (op_attr_list,loop), + "xml" : None, + "monitor" : None, + "ra" : None, + "cib" : None, + "cibstatus" : None, + "template" : None, + "_test" : None, + "_regtest" : None, + "_objects" : None, + }, +} +def get_completer_list(level,cmd): + 'Return a list of completer functions.' + try: return completer_lists[level][cmd] + except: return None + +compl_help = CompletionHelp() +user_prefs = UserPrefs.getInstance() +vars = Vars.getInstance() +cib_status = CibStatus.getInstance() +cib_factory = CibFactory.getInstance() + +# vim:ts=4:sw=4:et: diff --git a/shell/modules/levels.py b/shell/modules/levels.py index fcb5db2bdc..209416c4ba 100644 --- a/shell/modules/levels.py +++ b/shell/modules/levels.py @@ -1,93 +1,90 @@ # Copyright (C) 2008 Dejan Muhamedagic # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # import sys import re from singletonmixin import Singleton def topics_dict(help_tab): if not help_tab: return {} topics = {} for topic in help_tab: if topic != '.': topics[topic] = None return topics def mk_completion_tab(obj,ctab): + from completion import get_completer_list cmd_table = obj.cmd_table for key,value in cmd_table.items(): if key.startswith("_"): continue if type(value) == type(object): ctab[key] = {} elif key == "help": ctab[key] = topics_dict(obj.help_table) else: - try: - ctab[key] = value[3] - except: - ctab[key] = None - pass + ctab[key] = get_completer_list(obj.lvl_name,key) class Levels(Singleton): ''' Keep track of levels and prompts. ''' def __init__(self,start_level): self._marker = 0 self._in_transit = False self.level_stack = [] self.comp_stack = [] self.current_level = start_level() self.parse_root = self.current_level.cmd_table self.prompts = [] self.completion_tab = {} mk_completion_tab(self.current_level,self.completion_tab) def getprompt(self): return ' '.join(self.prompts) def mark(self): self._marker = len(self.level_stack) self._in_transit = False def release(self): while len(self.level_stack) > self._marker: self.droplevel() def new_level(self,level_obj,token): self.level_stack.append(self.current_level) self.comp_stack.append(self.completion_tab) self.prompts.append(token) self.current_level = level_obj() self.parse_root = self.current_level.cmd_table try: if not self.completion_tab[token]: mk_completion_tab(self.current_level,self.completion_tab[token]) self.completion_tab = self.completion_tab[token] except: pass self._in_transit = True def previous(self): if self.level_stack: return self.level_stack[-1] def droplevel(self): if self.level_stack: self.current_level.end_game(self._in_transit) self.current_level = self.level_stack.pop() self.completion_tab = self.comp_stack.pop() self.parse_root = self.current_level.cmd_table self.prompts.pop() # vim:ts=4:sw=4:et: diff --git a/shell/modules/main.py b/shell/modules/main.py index 3f7540b028..98da4b2a46 100644 --- a/shell/modules/main.py +++ b/shell/modules/main.py @@ -1,301 +1,292 @@ # Copyright (C) 2008 Dejan Muhamedagic # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # import sys import shlex -import readline import getopt from utils import * from userprefs import Options, UserPrefs from vars import Vars +from ui import cmd_exit from msg import * -from ui import cmd_exit, TopLevel, completer from levels import Levels def load_rc(rcfile): try: f = open(rcfile) except: return save_stdin = sys.stdin sys.stdin = f while True: inp = multi_input() if inp == None: break try: parse_line(levels,shlex.split(inp)) except ValueError, msg: common_err(msg) f.close() sys.stdin = save_stdin def multi_input(prompt = ''): """ Get input from user Allow multiple lines using a continuation character """ line = [] while True: try: text = raw_input(prompt) except EOFError: return None err_buf.incr_lineno() if options.regression_tests: print ".INP:",text sys.stdout.flush() sys.stderr.flush() stripped = text.strip() if stripped.endswith('\\'): stripped = stripped.rstrip('\\') line.append(stripped) if prompt: prompt = '> ' else: line.append(stripped) break return ''.join(line) def check_args(args,argsdim): if not argsdim: return True if len(argsdim) == 1: minargs = argsdim[0] return len(args) >= minargs else: minargs,maxargs = argsdim return len(args) >= minargs and len(args) <= maxargs # # Note on parsing # # Parsing tables are python dictionaries. # # Keywords are used as keys and the corresponding values are # lists (actually tuples, since they should be read-only) or # classes. In the former case, the keyword is a terminal and # in the latter, a new object for the class is created. The class # must have the cmd_table variable. # # The list has the following content: # # function: a function to handle this command # numargs_list: number of minimum/maximum arguments; for example, # (0,1) means one optional argument, (1,1) one required; if the # list is empty then the function will parse arguments itself # required minimum skill level: operator, administrator, expert # (encoded as a small integer from 0 to 2) # list of completer functions (optional) # def show_usage(cmd): p = None try: p = cmd.__doc__ except: pass if p: print >> sys.stderr, p else: syntax_err(cmd.__name__) def parse_line(lvl,s): if not s: return True if s[0].startswith('#'): return True lvl.mark() pt = lvl.parse_root cmd = None i = 0 for i in range(len(s)): token = s[i] if token in pt: if type(pt[token]) == type(object): lvl.new_level(pt[token],token) pt = lvl.parse_root # move to the next level else: cmd = pt[token] # terminal symbol break # and stop parsing else: syntax_err(s[i:]) lvl.release() return False if cmd: # found a terminal symbol if not user_prefs.check_skill_level(cmd[2]): lvl.release() skill_err(s[i]) return False args = s[i+1:] if not check_args(args,cmd[1]): lvl.release() show_usage(cmd[0]) return False args = s[i:] d = lambda: cmd[0](*args) rv = d() # execute the command lvl.release() return rv != False return True def prereqs(): proglist = "which cibadmin crm_resource crm_attribute crm_mon" for prog in proglist.split(): if not is_program(prog): print >> sys.stderr, "%s not available, check your installation"%prog sys.exit(1) # three modes: interactive (no args supplied), batch (input from # a file), half-interactive (args supplied, but not batch) def cib_prompt(): return vars.cib_in_use or "live" -def setup_readline(): - readline.set_history_length(100) - readline.parse_and_bind("tab: complete") - readline.set_completer(completer) - readline.set_completer_delims(\ - readline.get_completer_delims().replace('-','').replace('/','').replace('=','')) - try: readline.read_history_file(vars.hist_file) - except: pass - def usage(rc): f = sys.stderr if rc == 0: f = sys.stdout print >> f, """ usage: crm [-D display_type] [-f file] [-hF] [args] Use crm without arguments for an interactive session. Supply one or more arguments for a "single-shot" use. Specify with -f a file which contains a script. Use '-' for standard input or use pipe/redirection. crm displays cli format configurations using a color scheme and/or in uppercase. Pick one of "color" or "uppercase", or use "-D color,uppercase" if you want colorful uppercase. Get plain output by "-D plain". The default may be set in user preferences (options). -F stands for force, if set all operations will behave as if force was specified on the line (e.g. configure commit). Examples: # crm -f stopapp2.cli # crm < stopapp2.cli # crm resource stop global_www # crm status """ sys.exit(rc) user_prefs = UserPrefs.getInstance() options = Options.getInstance() err_buf = ErrorBuffer.getInstance() vars = Vars.getInstance() levels = Levels.getInstance() # prefer the user set PATH os.putenv("PATH", "%s:%s" % (os.getenv("PATH"),vars.crm_daemon_dir)) def run(): prereqs() inp_file = '' load_rc(vars.rc_file) if not sys.stdin.isatty(): err_buf.reset_lineno() options.batch = True else: options.interactive = True try: opts, args = getopt.getopt(sys.argv[1:], \ 'hdf:FRD:', ("version","help","debug","file=",\ "force","regression-tests","display=")) for o,p in opts: if o in ("-h","--help"): usage(0) elif o in ("--version"): print >> sys.stdout,("""%s Written by Dejan Muhamedagic """ % vars.crm_version) sys.exit(0) elif o == "-d": user_prefs.set_debug() elif o == "-R": options.regression_tests = True elif o in ("-D","--display"): user_prefs.set_output(p) elif o in ("-F","--force"): user_prefs.set_force() elif o in ("-f","--file"): options.batch = True err_buf.reset_lineno() inp_file = p except getopt.GetoptError,msg: print msg usage(1) # this special case is silly, but we have to keep it to # preserve the backward compatibility if len(args) == 1 and args[0].startswith("conf"): parse_line(levels,["configure"]) if not inp_file and sys.stdin.isatty(): options.interactive = True elif len(args) > 0: err_buf.reset_lineno() options.interactive = False if parse_line(levels,shlex.split(' '.join(args))): # if the user entered a level, then just continue if levels.previous(): if not inp_file and sys.stdin.isatty(): options.interactive = True else: sys.exit(0) else: sys.exit(1) if inp_file == "-": pass elif inp_file: try: f = open(inp_file) except IOError, msg: common_err(msg) usage(2) sys.stdin = f if options.interactive and not options.batch: + from completion import setup_readline setup_readline() rc = 0 while True: if options.interactive and not options.batch: vars.prompt = "crm(%s)%s# " % (cib_prompt(),levels.getprompt()) inp = multi_input(vars.prompt) if inp == None: if options.interactive: cmd_exit("eof") else: cmd_exit("eof", rc) try: if not parse_line(levels,shlex.split(inp)): rc = 1 except ValueError, msg: rc = 1 common_err(msg) # vim:ts=4:sw=4:et: diff --git a/shell/modules/ui.py.in b/shell/modules/ui.py.in index 902f82ae26..f2dc048d26 100644 --- a/shell/modules/ui.py.in +++ b/shell/modules/ui.py.in @@ -1,1992 +1,1674 @@ # Copyright (C) 2008 Dejan Muhamedagic # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # import sys import re import os -import readline import shlex import time import bz2 from help import HelpSystem, cmd_help from vars import Vars from levels import Levels from cibconfig import mkset_obj, CibFactory from cibstatus import CibStatus from template import LoadTemplate from cliformat import nvpairs2list from ra import * from msg import * from utils import * from xmlutil import * def cmd_end(cmd,dir = ".."): "Go up one level." levels.droplevel() def cmd_exit(cmd,rc = 0): "Exit the crm program" cmd_end(cmd) if options.interactive and not options.batch: print "bye" - try: - readline.write_history_file(vars.hist_file) - except: - pass + try: + from readline import write_history_file + write_history_file(vars.hist_file) + except: + pass for f in vars.tmpfiles: os.unlink(f) sys.exit(rc) class UserInterface(object): ''' Stuff common to all user interface classes. ''' global_cmd_aliases = { "quit": ("bye","exit"), "end": ("cd","up"), } def __init__(self): self.cmd_table = odict() self.cmd_table["help"] = (self.help,(0,1),0) self.cmd_table["quit"] = (self.exit,(0,0),0) self.cmd_table["end"] = (self.end,(0,1),0) self.cmd_aliases = self.global_cmd_aliases.copy() def end_game(self, no_questions_asked = False): pass def help(self,cmd,topic = ''): "usage: help []" cmd_help(self.help_table,topic) def end(self,cmd,dir = ".."): "usage: end" self.end_game() cmd_end(cmd,dir) def exit(self,cmd): "usage: exit" self.end_game() cmd_exit(cmd) class CliOptions(UserInterface): ''' Manage user preferences ''' + lvl_name = "options" def __init__(self): UserInterface.__init__(self) self.help_table = help_sys.load_level("options") - self.cmd_table["skill-level"] = (self.set_skill_level,(1,1),0,(skills_list,)) + self.cmd_table["skill-level"] = (self.set_skill_level,(1,1),0) self.cmd_table["editor"] = (self.set_editor,(1,1),0) self.cmd_table["pager"] = (self.set_pager,(1,1),0) self.cmd_table["user"] = (self.set_crm_user,(0,1),0) self.cmd_table["output"] = (self.set_output,(1,1),0) self.cmd_table["colorscheme"] = (self.set_colors,(1,1),0) self.cmd_table["check-frequency"] = (self.set_check_frequency,(1,1),0) self.cmd_table["check-mode"] = (self.set_check_mode,(1,1),0) self.cmd_table["sort-elements"] = (self.set_sort_elements,(1,1),0) self.cmd_table["save"] = (self.save_options,(0,0),0) self.cmd_table["show"] = (self.show_options,(0,0),0) setup_aliases(self) def set_skill_level(self,cmd,skill_level): """usage: skill-level level: operator | administrator | expert""" return user_prefs.set_skill_level(skill_level) def set_editor(self,cmd,prog): "usage: editor " return user_prefs.set_editor(prog) def set_pager(self,cmd,prog): "usage: pager " return user_prefs.set_pager(prog) def set_crm_user(self,cmd,user = ''): "usage: user []" return user_prefs.set_crm_user(user) def set_output(self,cmd,otypes): "usage: output " return user_prefs.set_output(otypes) def set_colors(self,cmd,scheme): "usage: colorscheme " return user_prefs.set_colors(scheme) def set_check_frequency(self,cmd,freq): "usage: check-frequence " return user_prefs.set_check_freq(freq) def set_check_mode(self,cmd,mode): "usage: check-mode " return user_prefs.set_check_mode(mode) def set_sort_elements(self,cmd,opt): "usage: sort-elements {yes|no}" if not verify_boolean(opt): common_err("%s: bad boolean option"%opt) return True return user_prefs.set_sort_elems(opt) def show_options(self,cmd): "usage: show" return user_prefs.write_rc(sys.stdout) def save_options(self,cmd): "usage: save" return user_prefs.save_options(vars.rc_file) def end_game(self, no_questions_asked = False): if no_questions_asked and not options.interactive: self.save_options("save") class CibShadow(UserInterface): ''' CIB shadow management class ''' + lvl_name = "cib" extcmd = ">/dev/null &1" % self.extcmd) except os.error: no_prog_err(self.extcmd) return False return True def new(self,cmd,name,*args): "usage: new [withstatus] [force]" if not is_filename_sane(name): return False new_cmd = "%s -c '%s'" % (self.extcmd,name) for par in args: if not par in ("force","--force","withstatus"): syntax_err((cmd,name,par), context = 'new') return False if user_prefs.get_force() or "force" in args or "--force" in args: new_cmd = "%s --force" % new_cmd if ext_cmd(new_cmd) == 0: common_info("%s shadow CIB created"%name) self.use("use",name) if "withstatus" in args: cib_status.load("shadow:%s" % name) def _find_pe(self,infile): 'Find a pe input' for p in ("%s/%s", "%s/%s.bz2", "%s/pe-*-%s.bz2"): fl = glob.glob(p % (vars.pe_dir,infile)) if fl: break if not fl: common_err("no %s pe input file"%infile) return '' if len(fl) > 1: common_err("more than one %s pe input file: %s" % \ (infile,' '.join(fl))) return '' return fl[0] def pe_import(self,cmd,infile,name = None): "usage: import {|} []" if name and not is_filename_sane(name): return False # where's the input? if not os.access(infile,os.F_OK): if "/" in infile: common_err("%s: no such file"%infile) return False infile = self._find_pe(infile) if not infile: return False if not name: name = os.path.basename(infile) # read input try: f = open(infile) except IOError,msg: common_err("open: %s"%msg) return s = ''.join(f) f.close() # decompresed and rename shadow if it ends with .bz2 if infile.endswith(".bz2"): name = name.replace(".bz2","") s = bz2.decompress(s) # copy input to the shadow try: f = open(shadowfile(name), "w") except IOError,msg: common_err("open: %s"%msg) return f.write(s) f.close() # use the shadow and load the status from there return self.use("use",name,"withstatus") def delete(self,cmd,name): "usage: delete " if not is_filename_sane(name): return False if vars.cib_in_use == name: common_err("%s shadow CIB is in use"%name) return False if ext_cmd("%s -D '%s' --force" % (self.extcmd,name)) == 0: common_info("%s shadow CIB deleted"%name) else: common_err("failed to delete %s shadow CIB"%name) return False def reset(self,cmd,name): "usage: reset " if not is_filename_sane(name): return False if ext_cmd("%s -r '%s'" % (self.extcmd,name)) == 0: common_info("copied live CIB to %s"%name) else: common_err("failed to copy live CIB to %s"%name) return False def commit(self,cmd,name): "usage: commit " if not is_filename_sane(name): return False if ext_cmd("%s -C '%s' --force" % (self.extcmd,name)) == 0: common_info("commited '%s' shadow CIB to the cluster"%name) else: common_err("failed to commit the %s shadow CIB"%name) return False def diff(self,cmd): "usage: diff" s = get_stdout(add_sudo("%s -d" % self.extcmd_stdout)) page_string(s) def list(self,cmd): "usage: list" if options.regression_tests: for t in listshadows(): print t else: multicolumn(listshadows()) def _use(self,name,withstatus): # Choose a shadow cib for further changes. If the name # provided is empty, then choose the live (cluster) cib. # Don't allow ' in shadow names if not name or name == "live": os.unsetenv(vars.shadow_envvar) vars.cib_in_use = "" if withstatus: cib_status.load("live") else: os.putenv(vars.shadow_envvar,name) vars.cib_in_use = name if withstatus: cib_status.load("shadow:%s" % name) def use(self,cmd,name = '', withstatus = ''): "usage: use [] [withstatus]" # check the name argument if name and not is_filename_sane(name): return False if name and name != "live": if not os.access(shadowfile(name),os.F_OK): common_err("%s: no such shadow CIB"%name) return False if withstatus and withstatus != "withstatus": syntax_err((cmd,withstatus), context = 'use') return False # If invoked from configure # take special precautions try: prev_level = levels.previous().myname() except: prev_level = '' if prev_level != "cibconfig": self._use(name,withstatus) return True if not cib_factory.has_cib_changed(): self._use(name,withstatus) # new CIB: refresh the CIB factory cib_factory.refresh() return True saved_cib = vars.cib_in_use self._use(name,'') # don't load the status yet if not cib_factory.is_current_cib_equal(silent = True): # user made changes and now wants to switch to a # different and unequal CIB; we refuse to cooperate common_err("the requested CIB is different from the current one") if user_prefs.get_force(): common_info("CIB overwrite forced") elif not ask("All changes will be dropped. Do you want to proceed?"): self._use(saved_cib,'') # revert to the previous CIB return False self._use(name,withstatus) # now load the status too return True -def listtemplates(): - l = [] - for f in os.listdir(vars.tmpl_dir): - if os.path.isfile("%s/%s" % (vars.tmpl_dir,f)): - l.append(f) - return l -def listconfigs(): - l = [] - for f in os.listdir(vars.tmpl_conf_dir): - if os.path.isfile("%s/%s" % (vars.tmpl_conf_dir,f)): - l.append(f) - return l def check_transition(inp,state,possible_l): if not state in possible_l: common_err("input (%s) in wrong state %s" % (inp,state)) return False return True class Template(UserInterface): ''' Configuration templates. ''' + lvl_name = "template" def __init__(self): UserInterface.__init__(self) self.help_table = help_sys.load_level("template") - self.cmd_table["new"] = (self.new,(2,),1,(null_list,templates_list,loop)) - self.cmd_table["load"] = (self.load,(0,1),1,(config_list,)) - self.cmd_table["edit"] = (self.edit,(0,1),1,(config_list,)) - self.cmd_table["delete"] = (self.delete,(1,2),1,(config_list,)) - self.cmd_table["show"] = (self.show,(0,1),0,(config_list,)) - self.cmd_table["apply"] = (self.apply,(0,2),1,(config_list_method,config_list)) + self.cmd_table["new"] = (self.new,(2,),1) + self.cmd_table["load"] = (self.load,(0,1),1) + self.cmd_table["edit"] = (self.edit,(0,1),1) + self.cmd_table["delete"] = (self.delete,(1,2),1) + self.cmd_table["show"] = (self.show,(0,1),0) + self.cmd_table["apply"] = (self.apply,(0,2),1) self.cmd_table["list"] = (self.list,(0,1),0) setup_aliases(self) self.init_dir() self.curr_conf = '' def init_dir(self): '''Create the conf directory, link to templates''' if not os.path.isdir(vars.tmpl_conf_dir): try: os.makedirs(vars.tmpl_conf_dir) except os.error,msg: common_err("makedirs: %s"%msg) return def get_depends(self,tmpl): '''return a list of required templates''' # Not used. May need it later. try: tf = open("%s/%s" % (vars.tmpl_dir, tmpl),"r") except IOError,msg: common_err("open: %s"%msg) return l = [] for s in tf: a = s.split() if len(a) >= 2 and a[0] == '%depends_on': l += a[1:] tf.close() return l def replace_params(self,s,user_data): change = False for i in range(len(s)): word = s[i] for p in user_data: # is parameter in the word? pos = word.find('%' + p) if pos < 0: continue endpos = pos + len('%' + p) # and it isn't part of another word? if re.match("[A-Za-z0-9]", word[endpos:endpos+1]): continue # if the value contains a space or # it is a value of an attribute # put quotes around it if user_data[p].find(' ') >= 0 or word[pos-1:pos] == '=': v = '"' + user_data[p] + '"' else: v = user_data[p] word = word.replace('%' + p, v) change = True # we did replace something if change: s[i] = word if 'opt' in s: if not change: s = [] else: s.remove('opt') return s def generate(self,l,user_data): '''replace parameters (user_data) and generate output ''' l2 = [] for piece in l: piece2 = [] for s in piece: s = self.replace_params(s,user_data) if s: piece2.append(' '.join(s)) if piece2: l2.append(' \\\n\t'.join(piece2)) return '\n'.join(l2) def process(self,config = ''): '''Create a cli configuration from the current config''' try: f = open("%s/%s" % (vars.tmpl_conf_dir, config or self.curr_conf),'r') except IOError,msg: common_err("open: %s"%msg) return '' l = [] piece = [] user_data = {} # states START = 0; PFX = 1; DATA = 2; GENERATE = 3 state = START err_buf.start_tmp_lineno() rc = True for inp in f: err_buf.incr_lineno() if inp.startswith('#'): continue if type(inp) == type(u''): inp = inp.encode('ascii') inp = inp.strip() try: s = shlex.split(inp) except ValueError, msg: common_err(msg) continue while '\n' in s: s.remove('\n') if not s: if state == GENERATE and piece: l.append(piece) piece = [] elif s[0] in ("%name","%depends_on","%suggests"): continue elif s[0] == "%pfx": if check_transition(inp,state,(START,DATA)) and len(s) == 2: pfx = s[1] state = PFX elif s[0] == "%required": if check_transition(inp,state,(PFX,)): state = DATA data_reqd = True elif s[0] == "%optional": if check_transition(inp,state,(PFX,DATA)): state = DATA data_reqd = False elif s[0] == "%%": if state != DATA: common_warn("user data in wrong state %s" % state) if len(s) < 2: common_warn("parameter name missing") elif len(s) == 2: if data_reqd: common_err("required parameter %s not set" % s[1]) rc = False elif len(s) == 3: user_data["%s:%s" % (pfx,s[1])] = s[2] else: common_err("%s: syntax error" % inp) elif s[0] == "%generate": if check_transition(inp,state,(DATA,)): state = GENERATE piece = [] elif state == GENERATE: if s: piece.append(s) else: common_err("<%s> unexpected" % inp) if piece: l.append(piece) err_buf.stop_tmp_lineno() f.close() if not rc: return '' return self.generate(l,user_data) def new(self,cmd,name,*args): "usage: new