1# Copyright (C) 2008-2011 Dejan Muhamedagic <dmuhamedagic@suse.de> 2# Copyright (C) 2013 Kristoffer Gronlund <kgronlund@suse.com> 3# See COPYING for license information. 4 5import os 6import re 7import shlex 8from . import command 9from . import completers as compl 10from . import utils 11from . import config 12from . import userdir 13from . import options 14from .template import LoadTemplate 15from .cliformat import cli_format 16from .cibconfig import mkset_obj, cib_factory 17from .msg import common_err, common_warn 18from .msg import syntax_err, err_buf 19 20 21def check_transition(inp, state, possible_l): 22 if state not in possible_l: 23 common_err("input (%s) in wrong state %s" % (inp, state)) 24 return False 25 return True 26 27 28def _unique_config_name(tmpl): 29 n = 0 30 while n < 99: 31 c = "%s-%d" % (tmpl, n) 32 if not os.path.isfile("%s/%s" % (userdir.CRMCONF_DIR, c)): 33 return c 34 n += 1 35 raise ValueError("Failed to generate unique configuration name") 36 37 38class Template(command.UI): 39 ''' 40 Configuration templates. 41 ''' 42 name = "template" 43 44 def __init__(self): 45 command.UI.__init__(self) 46 self.curr_conf = '' 47 self.init_dir() 48 49 @command.skill_level('administrator') 50 @command.completers_repeating(compl.null, compl.call(utils.listtemplates)) 51 def do_new(self, context, name, *args): 52 "usage: new [<config>] <template> [<template> ...] [params name=value ...]" 53 if not utils.is_filename_sane(name): 54 return False 55 if os.path.isfile("%s/%s" % (userdir.CRMCONF_DIR, name)): 56 common_err("config %s exists; delete it first" % name) 57 return False 58 lt = LoadTemplate(name) 59 rc = True 60 mode = 0 61 params = {"id": name} 62 loaded_template = False 63 for s in args: 64 if mode == 0 and s == "params": 65 mode = 1 66 elif mode == 1: 67 a = s.split('=') 68 if len(a) != 2: 69 syntax_err(args, context='new') 70 rc = False 71 else: 72 params[a[0]] = a[1] 73 elif lt.load_template(s): 74 loaded_template = True 75 else: 76 rc = False 77 if not loaded_template: 78 if name not in utils.listtemplates(): 79 common_err("Expected template argument") 80 return False 81 tmpl = name 82 name = _unique_config_name(tmpl) 83 lt = LoadTemplate(name) 84 lt.load_template(tmpl) 85 if rc: 86 lt.post_process(params) 87 if not rc or not lt.write_config(name): 88 return False 89 self.curr_conf = name 90 91 @command.skill_level('administrator') 92 @command.completers(compl.call(utils.listconfigs)) 93 def do_delete(self, context, name, force=''): 94 "usage: delete <config> [force]" 95 if force: 96 if force != "force" and force != "--force": 97 syntax_err((context.get_command_name(), force), context='delete') 98 return False 99 if not self.config_exists(name): 100 return False 101 if name == self.curr_conf: 102 if not force and not config.core.force and \ 103 not utils.ask("Do you really want to remove config %s which is in use?" % 104 self.curr_conf): 105 return False 106 else: 107 self.curr_conf = '' 108 os.remove("%s/%s" % (userdir.CRMCONF_DIR, name)) 109 110 @command.skill_level('administrator') 111 @command.completers(compl.call(utils.listconfigs)) 112 def do_load(self, context, name=''): 113 "usage: load [<config>]" 114 if not name: 115 self.curr_conf = '' 116 return True 117 if not self.config_exists(name): 118 return False 119 self.curr_conf = name 120 121 @command.skill_level('administrator') 122 @command.completers(compl.call(utils.listconfigs)) 123 def do_edit(self, context, name=''): 124 "usage: edit [<config>]" 125 if not name and not self.curr_conf: 126 common_err("please load a config first") 127 return False 128 if name: 129 if not self.config_exists(name): 130 return False 131 utils.edit_file("%s/%s" % (userdir.CRMCONF_DIR, name)) 132 else: 133 utils.edit_file("%s/%s" % (userdir.CRMCONF_DIR, self.curr_conf)) 134 135 @command.completers(compl.call(utils.listconfigs)) 136 def do_show(self, context, name=''): 137 "usage: show [<config>]" 138 if not name and not self.curr_conf: 139 common_err("please load a config first") 140 return False 141 if name: 142 if not self.config_exists(name): 143 return False 144 print(self.process(name)) 145 else: 146 print(self.process()) 147 148 @command.skill_level('administrator') 149 @command.completers(compl.join(compl.call(utils.listconfigs), 150 compl.choice(['replace', 'update'])), 151 compl.call(utils.listconfigs)) 152 def do_apply(self, context, *args): 153 "usage: apply [<method>] [<config>]" 154 method = "replace" 155 name = '' 156 if len(args) > 0: 157 i = 0 158 if args[0] in ("replace", "update"): 159 method = args[0] 160 i += 1 161 if len(args) > i: 162 name = args[i] 163 if not name and not self.curr_conf: 164 common_err("please load a config first") 165 return False 166 if name: 167 if not self.config_exists(name): 168 return False 169 s = self.process(name) 170 else: 171 s = self.process() 172 if not s: 173 return False 174 tmp = utils.str2tmp(s) 175 if not tmp: 176 return False 177 if method == "replace": 178 if options.interactive and cib_factory.has_cib_changed(): 179 if not utils.ask("This operation will erase all changes. Do you want to proceed?"): 180 return False 181 cib_factory.erase() 182 set_obj = mkset_obj() 183 rc = set_obj.import_file(method, tmp) 184 try: 185 os.unlink(tmp) 186 except: 187 pass 188 return rc 189 190 @command.completers(compl.choice(['configs', 'templates'])) 191 def do_list(self, context, templates=''): 192 "usage: list [configs|templates]" 193 if templates == "templates": 194 utils.multicolumn(utils.listtemplates()) 195 elif templates == "configs": 196 utils.multicolumn(utils.listconfigs()) 197 else: 198 print("Templates:") 199 utils.multicolumn(utils.listtemplates()) 200 print("\nConfigurations:") 201 utils.multicolumn(utils.listconfigs()) 202 203 def init_dir(self): 204 '''Create the conf directory, link to templates''' 205 if not os.path.isdir(userdir.CRMCONF_DIR): 206 try: 207 os.makedirs(userdir.CRMCONF_DIR) 208 except os.error as msg: 209 common_err("makedirs: %s" % msg) 210 211 def get_depends(self, tmpl): 212 '''return a list of required templates''' 213 # Not used. May need it later. 214 try: 215 templatepath = os.path.join(config.path.sharedir, 'templates', tmpl) 216 tf = open(templatepath, "r") 217 except IOError as msg: 218 common_err("open: %s" % msg) 219 return 220 l = [] 221 for s in tf: 222 a = s.split() 223 if len(a) >= 2 and a[0] == '%depends_on': 224 l += a[1:] 225 tf.close() 226 return l 227 228 def config_exists(self, name): 229 if not utils.is_filename_sane(name): 230 return False 231 if not os.path.isfile("%s/%s" % (userdir.CRMCONF_DIR, name)): 232 common_err("%s: no such config" % name) 233 return False 234 return True 235 236 def replace_params(self, s, user_data): 237 change = False 238 for i, word in enumerate(s): 239 for p in user_data: 240 # is parameter in the word? 241 pos = word.find('%' + p) 242 if pos < 0: 243 continue 244 endpos = pos + len('%' + p) 245 # and it isn't part of another word? 246 if re.match("[A-Za-z0-9]", word[endpos:endpos+1]): 247 continue 248 # if the value contains a space or 249 # it is a value of an attribute 250 # put quotes around it 251 if user_data[p].find(' ') >= 0 or word[pos-1:pos] == '=': 252 v = '"' + user_data[p] + '"' 253 else: 254 v = user_data[p] 255 word = word.replace('%' + p, v) 256 change = True # we did replace something 257 if change: 258 s[i] = word 259 if 'opt' in s: 260 if not change: 261 s = [] 262 else: 263 s.remove('opt') 264 return s 265 266 def generate(self, l, user_data): 267 '''replace parameters (user_data) and generate output 268 ''' 269 l2 = [] 270 for piece in l: 271 piece2 = [] 272 for s in piece: 273 s = self.replace_params(s, user_data) 274 if s: 275 piece2.append(' '.join(s)) 276 if piece2: 277 l2.append(cli_format(piece2, break_lines=True)) 278 return '\n'.join(l2) 279 280 def process(self, config=''): 281 '''Create a cli configuration from the current config''' 282 try: 283 f = open("%s/%s" % (userdir.CRMCONF_DIR, config or self.curr_conf), 'r') 284 except IOError as msg: 285 common_err("open: %s" % msg) 286 return '' 287 l = [] 288 piece = [] 289 user_data = {} 290 # states 291 START = 0 292 PFX = 1 293 DATA = 2 294 GENERATE = 3 295 state = START 296 err_buf.start_tmp_lineno() 297 rc = True 298 for inp in f: 299 inp = utils.to_ascii(inp) 300 err_buf.incr_lineno() 301 if inp.startswith('#'): 302 continue 303 inp = inp.strip() 304 try: 305 s = shlex.split(inp) 306 except ValueError as msg: 307 common_err(msg) 308 continue 309 while '\n' in s: 310 s.remove('\n') 311 if not s: 312 if state == GENERATE and piece: 313 l.append(piece) 314 piece = [] 315 elif s[0] in ("%name", "%depends_on", "%suggests"): 316 continue 317 elif s[0] == "%pfx": 318 if check_transition(inp, state, (START, DATA)) and len(s) == 2: 319 pfx = s[1] 320 state = PFX 321 elif s[0] == "%required": 322 if check_transition(inp, state, (PFX,)): 323 state = DATA 324 data_reqd = True 325 elif s[0] == "%optional": 326 if check_transition(inp, state, (PFX, DATA)): 327 state = DATA 328 data_reqd = False 329 elif s[0] == "%%": 330 if state != DATA: 331 common_warn("user data in wrong state %s" % state) 332 if len(s) < 2: 333 common_warn("parameter name missing") 334 elif len(s) == 2: 335 if data_reqd: 336 common_err("required parameter %s not set" % s[1]) 337 rc = False 338 elif len(s) == 3: 339 user_data["%s:%s" % (pfx, s[1])] = s[2] 340 else: 341 common_err("%s: syntax error" % inp) 342 elif s[0] == "%generate": 343 if check_transition(inp, state, (DATA,)): 344 state = GENERATE 345 piece = [] 346 elif state == GENERATE: 347 if s: 348 piece.append(s) 349 else: 350 common_err("<%s> unexpected" % inp) 351 if piece: 352 l.append(piece) 353 err_buf.stop_tmp_lineno() 354 f.close() 355 if not rc: 356 return '' 357 return self.generate(l, user_data) 358 359 360# vim:ts=4:sw=4:et: 361