No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

slim.py 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. #!/usr/bin/env python3
  2. #
  3. # This file is part of Lodel 2 (https://github.com/OpenEdition)
  4. #
  5. # Copyright (C) 2015-2017 Cléo UMS-3287
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Affero General Public License as published
  9. # by the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Affero General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. import os, os.path
  21. import sys
  22. import shutil
  23. import argparse
  24. import logging
  25. import re
  26. import json
  27. import configparser
  28. import signal
  29. import subprocess
  30. from lodel import buildconf
  31. logging.basicConfig(level=logging.INFO)
  32. INSTANCES_ABSPATH="/tmp/lodel2_instances"
  33. LODEL2_INSTALLDIR="/usr/lib/python3/dist-packages"
  34. CONFFILE='conf.d/lodel2.ini'
  35. try:
  36. STORE_FILE = os.path.join("[@]SLIM_VAR_DIR[@]", 'slim_instances.json')
  37. PID_FILE = os.path.join("[@]SLIM_VAR_DIR[@]", 'slim_instances_pid.json')
  38. CREATION_SCRIPT = os.path.join("[@]LODEL2_PROGSDIR[@]", 'create_instance')
  39. INSTALL_TPL = "[@]INSTALLMODEL_DIR[@]"
  40. EMFILE = os.path.join("[@]SLIM_DATADIR[@]", 'emfile.pickle')
  41. except SyntaxError:
  42. STORE_FILE='./instances.json'
  43. PID_FILE = './slim_instances_pid.json'
  44. CREATION_SCRIPT='../scripts/create_instance.sh'
  45. INSTALL_TPL = './slim_ressources/slim_install_model'
  46. EMFILE = './slim_ressources/emfile.pickle'
  47. CREATION_SCRIPT=os.path.join(os.path.dirname(__file__), CREATION_SCRIPT)
  48. STORE_FILE=os.path.join(os.path.dirname(__file__), STORE_FILE)
  49. INSTALL_TPL=os.path.join(os.path.dirname(__file__), INSTALL_TPL)
  50. EMFILE=os.path.join(os.path.dirname(__file__), EMFILE)
  51. #STORE_FILE syntax :
  52. #
  53. #First level keys are instances names, their values are dict with following
  54. #informations :
  55. # - path
  56. #
  57. ##@brief Run 'make %target%' for each instances given in names
  58. #@param target str : make target
  59. #@param names list : list of instance name
  60. def run_make(target, names):
  61. validate_names(names)
  62. store_datas = get_store_datas()
  63. cwd = os.getcwd()
  64. for name in [n for n in store_datas if n in names]:
  65. datas = store_datas[name]
  66. logging.info("Running 'make %s' for '%s' in %s" % (
  67. target, name, datas['path']))
  68. os.chdir(datas['path'])
  69. os.system('make %s' % target)
  70. os.chdir(cwd)
  71. ##@brief Set configuration given args
  72. #@param name str : instance name
  73. #@param args : as returned by argparse
  74. def set_conf(name, args):
  75. validate_names([name])
  76. conffile = get_conffile(name)
  77. config = configparser.ConfigParser(interpolation=None)
  78. config.read(conffile)
  79. #Interface options
  80. if args.interface is not None:
  81. iarg = args.interface
  82. if iarg not in ('web', 'python'):
  83. raise TypeError("Interface can only be on of : 'web', 'python'")
  84. if iarg.lower() == 'web':
  85. iarg = 'webui'
  86. else:
  87. iarg = ''
  88. config['lodel2']['interface'] = iarg
  89. interface = config['lodel2']['interface']
  90. if interface == 'webui':
  91. if 'lodel2.webui' not in config:
  92. config['lodel2.webui'] = dict()
  93. config['lodel2.webui']['standalone'] = 'uwsgi'
  94. if args.listen_port is not None:
  95. config['lodel2.webui']['listen_port'] = str(args.listen_port)
  96. if args.listen_address is not None:
  97. config['lodel2.webui']['listen_address'] = str(args.listen_address)
  98. if args.static_url is not None:
  99. config['lodel2.webui']['static_url'] = str(args.static_url)
  100. if args.uwsgi_workers is not None:
  101. config['lodel2.webui']['uwsgi_workers'] = str(args.uwsgi_workers)
  102. else: #interface is python
  103. if args.listen_port is not None or args.listen_address is not None:
  104. logging.error("Listen port and listen address will not being set. \
  105. Selected interface is not the web iterface")
  106. if 'lodel.webui' in config:
  107. del(config['lodel2.webui'])
  108. #Datasource options
  109. if args.datasource_connectors is not None:
  110. darg = args.datasource_connectors
  111. if darg not in ('dummy', 'mongodb'):
  112. raise ValueError("Allowed connectors are : 'dummy' and 'mongodb'")
  113. if darg not in ('mongodb',):
  114. raise TypeError("Datasource_connectors can only be of : 'mongodb'")
  115. if darg.lower() == 'mongodb':
  116. dconf = 'mongodb_datasource'
  117. toadd = 'mongodb_datasource'
  118. todel = 'dummy_datasource'
  119. else:
  120. dconf = 'dummy_datasource'
  121. todel = 'mongodb_datasource'
  122. toadd = 'dummy_datasource'
  123. config['lodel2']['datasource_connectors'] = dconf
  124. #Delete old default & dummy2 conn
  125. kdel = 'lodel2.datasource.%s.%s' % (todel, 'default')
  126. if kdel in config:
  127. del(config[kdel])
  128. #Add the new default & dummy2 conn
  129. kadd = 'lodel2.datasource.%s.%s' % (toadd, 'default')
  130. if kadd not in config:
  131. config[kadd] = dict()
  132. #Bind to current conn
  133. for dsn in ('default', 'dummy2'):
  134. config['lodel2.datasources.%s' %dsn ]= {
  135. 'identifier':'%s.default' % toadd}
  136. #Set the conf for mongodb
  137. if darg == 'mongodb':
  138. dbconfname = 'lodel2.datasource.mongodb_datasource.default'
  139. if args.host is not None:
  140. config[dbconfname]['host'] = str(args.host)
  141. if args.user is not None:
  142. config[dbconfname]['username'] = str(args.user)
  143. if args.password is not None:
  144. config[dbconfname]['password'] = str(args.password)
  145. if args.db_name is not None:
  146. config[dbconfname]['db_name'] = str(args.db_name)
  147. else:
  148. config['lodel2.datasource.dummy_datasource.default'] = {'dummy':''}
  149. #Logger options
  150. if args.set_logger is not None:
  151. #Purge existing loggers
  152. for k in [ k for k in config if k.startswith('lodel2.logging.')]:
  153. del(config[k])
  154. if isinstance(args.set_logger, str):
  155. specs = [ args.set_logger ]
  156. else:
  157. specs = args.set_logger
  158. #Add the new one
  159. for log_spec in specs:
  160. spl = log_spec.split(':')
  161. if len(spl) == 3:
  162. loggername, loglevel, logfile = log_spec.split(':')
  163. else:
  164. raise ValueError(
  165. "Invalid format for logger spec : %s" % log_spec)
  166. loggerkey = 'lodel2.logging.%s' % loggername
  167. if '%s' in logfile:
  168. logfile = logfile.replace('%s', name)
  169. if '%l' in logfile:
  170. logfile = logfile.replace('%l', loglevel.lower())
  171. config[loggerkey] = {
  172. 'level': loglevel,
  173. 'filename': logfile,
  174. 'context': True }
  175. #Now config should be OK to be written again in conffile
  176. with open(conffile, 'w+') as cfp:
  177. config.write(cfp)
  178. ##@brief If the name is not valid raise
  179. def name_is_valid(name):
  180. allowed_chars = [chr(i) for i in range(ord('a'), ord('z')+1)]
  181. allowed_chars += [chr(i) for i in range(ord('A'), ord('Z')+1)]
  182. allowed_chars += [chr(i) for i in range(ord('0'), ord('9')+1)]
  183. allowed_chars += ['_']
  184. for c in name:
  185. if c not in allowed_chars:
  186. raise RuntimeError("Allowed characters for instance name are \
  187. lower&upper alphanum and '_'. Name '%s' is invalid" % name)
  188. ##@brief Create a new instance
  189. #@param name str : the instance name
  190. def new_instance(name):
  191. name_is_valid(name)
  192. store_datas = get_store_datas()
  193. if name in store_datas:
  194. logging.error("An instance named '%s' already exists" % name)
  195. exit(1)
  196. if not os.path.isdir(INSTANCES_ABSPATH):
  197. logging.info("Instances directory '%s' don't exists, creating it")
  198. os.mkdir(INSTANCES_ABSPATH)
  199. instance_path = os.path.join(INSTANCES_ABSPATH, name)
  200. creation_cmd = '{script} "{name}" "{path}" "{install_tpl}" \
  201. "{emfile}"'.format(
  202. script = CREATION_SCRIPT,
  203. name = name,
  204. path = instance_path,
  205. install_tpl = INSTALL_TPL,
  206. emfile = EMFILE)
  207. res = os.system(creation_cmd)
  208. if res != 0:
  209. logging.error("Creation script fails")
  210. exit(res)
  211. #storing new instance
  212. store_datas[name] = {'path': instance_path}
  213. save_datas(store_datas)
  214. ##@brief Delete an instance
  215. #@param name str : the instance name
  216. def delete_instance(name):
  217. pids = get_pids()
  218. if name in pids:
  219. logging.error("The instance '%s' is started. Stop it before deleting \
  220. it" % name)
  221. return
  222. store_datas = get_store_datas()
  223. logging.warning("Deleting instance %s" % name)
  224. logging.info("Deleting instance folder %s" % store_datas[name]['path'])
  225. shutil.rmtree(store_datas[name]['path'])
  226. logging.debug("Deleting instance from json store file")
  227. del(store_datas[name])
  228. save_datas(store_datas)
  229. ##@brief returns stored datas
  230. def get_store_datas():
  231. if not os.path.isfile(STORE_FILE) or os.stat(STORE_FILE).st_size == 0:
  232. return dict()
  233. else:
  234. with open(STORE_FILE, 'r') as sfp:
  235. datas = json.load(sfp)
  236. return datas
  237. ##@brief Checks names validity and exit if fails
  238. def validate_names(names):
  239. store_datas = get_store_datas()
  240. invalid = [ n for n in names if n not in store_datas]
  241. if len(invalid) > 0:
  242. print("Following names are not existing instance :", file=sys.stderr)
  243. for name in invalid:
  244. print("\t%s" % name, file=sys.stderr)
  245. exit(1)
  246. ##@brief Returns the PID dict
  247. #@return a dict with instance name as key an PID as value
  248. def get_pids():
  249. if not os.path.isfile(PID_FILE) or os.stat(PID_FILE).st_size == 0:
  250. return dict()
  251. with open(PID_FILE, 'r') as pfd:
  252. return json.load(pfd)
  253. ##@brief Save a dict of pid
  254. #@param pid_dict dict : key is instance name values are pid
  255. def save_pids(pid_dict):
  256. with open(PID_FILE, 'w+') as pfd:
  257. json.dump(pid_dict, pfd)
  258. ##@brief Given an instance name returns its PID
  259. #@return False or an int
  260. def get_pid(name):
  261. pid_datas = get_pids()
  262. if name not in pid_datas:
  263. return False
  264. else:
  265. pid = pid_datas[name]
  266. if not is_running(name, pid):
  267. return False
  268. return pid
  269. ##@brief Start an instance
  270. #@param names list : instance name list
  271. #@param foreground bool
  272. def start_instances(names, foreground):
  273. pids = get_pids()
  274. store_datas = get_store_datas()
  275. for name in names:
  276. if name in pids:
  277. logging.warning("The instance %s is allready running" % name)
  278. continue
  279. os.chdir(store_datas[name]['path'])
  280. args = [sys.executable, 'loader.py']
  281. if foreground:
  282. logging.info("Calling execl with : %s" % args)
  283. os.execl(args[0], *args)
  284. return #only usefull if execl call fails (not usefull)
  285. else:
  286. curexec = subprocess.Popen(args,
  287. stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
  288. stderr=subprocess.DEVNULL, preexec_fn=os.setsid,
  289. cwd = store_datas[name]['path'])
  290. pids[name] = curexec.pid
  291. logging.info("Instance '%s' started. PID %d" % (name, curexec.pid))
  292. save_pids(pids)
  293. ##@brief Stop an instance given its name
  294. #@param names list : names list
  295. def stop_instances(names):
  296. pids = get_pids()
  297. store_datas = get_store_datas()
  298. for name in names:
  299. if name not in pids:
  300. logging.warning("The instance %s is not running" % name)
  301. continue
  302. pid = pids[name]
  303. try:
  304. os.kill(pid, signal.SIGTERM)
  305. except ProcessLookupError:
  306. logging.warning("The instance %s seems to be in error, no process \
  307. with pid %d found" % (name, pids[name]))
  308. del(pids[name])
  309. save_pids(pids)
  310. ##@brief Checks that a process is running
  311. #
  312. #If not running clean the pid list
  313. #@return bool
  314. def is_running(name, pid):
  315. try:
  316. os.kill(pid, 0)
  317. return True
  318. except (OSError,ProcessLookupError):
  319. pid_datas = get_pids()
  320. logging.warning("Instance '%s' was marked as running, but not \
  321. process with pid %d found. Cleaning pid list" % (name, pid))
  322. del(pid_datas[name])
  323. save_pids(pid_datas)
  324. return False
  325. ##@brief Check if instance are specified
  326. def get_specified(args):
  327. if args.all:
  328. names = list(get_store_datas().keys())
  329. elif args.name is not None:
  330. names = args.name
  331. else:
  332. names = None
  333. return sorted(names)
  334. ##@brief Saves store datas
  335. def save_datas(datas):
  336. with open(STORE_FILE, 'w+') as sfp:
  337. json.dump(datas, sfp)
  338. ##@return conffile path
  339. def get_conffile(name):
  340. validate_names([name])
  341. store_datas = get_store_datas()
  342. return os.path.join(store_datas[name]['path'], CONFFILE)
  343. ##@brief Print the list of instances and exit
  344. #@param verbosity int
  345. #@param batch bool : if true make simple output
  346. def list_instances(verbosity, batch):
  347. verbosity = 0 if verbosity is None else verbosity
  348. if not os.path.isfile(STORE_FILE):
  349. print("No store file, no instances are existing. Exiting...",
  350. file=sys.stderr)
  351. exit(0)
  352. store_datas = get_store_datas()
  353. if not batch:
  354. print('Instances list :')
  355. for name in store_datas:
  356. details_instance(name, verbosity, batch)
  357. exit(0)
  358. ##@brief Print instance informations and return (None)
  359. #@param name str : instance name
  360. #@param verbosity int
  361. #@param batch bool : if true make simple output
  362. def details_instance(name, verbosity, batch):
  363. validate_names([name])
  364. store_datas = get_store_datas()
  365. pids = get_pids()
  366. if not batch:
  367. msg = "\t- '%s'" % name
  368. if name in pids and is_running(name, pids[name]):
  369. msg += ' [Run PID %d] ' % pids[name]
  370. if verbosity > 0:
  371. msg += ' path = "%s"' % store_datas[name]['path']
  372. if verbosity > 1:
  373. ruler = (''.join(['=' for _ in range(20)])) + "\n"
  374. msg += "\n\t\t====conf.d/lodel2.ini====\n"
  375. with open(get_conffile(name)) as cfp:
  376. for line in cfp:
  377. msg += "\t\t"+line
  378. msg += "\t\t=========================\n"
  379. print(msg)
  380. else:
  381. msg = name
  382. if name in pids and is_running(name, pids[name]):
  383. msg += ' %d ' % pids[name]
  384. else:
  385. msg += ' stopped '
  386. if verbosity > 0:
  387. msg += "\t"+'"%s"' % store_datas[name]['path']
  388. if verbosity > 1:
  389. conffile = get_conffile(name)
  390. msg += "\n\t#####"+conffile+"#####\n"
  391. with open(conffile, 'r') as cfp:
  392. for line in cfp:
  393. msg += "\t"+line
  394. msg += "\n\t###########"
  395. print(msg)
  396. ##@brief Given instance names generate nginx confs
  397. #@param names list : list of instance names
  398. def nginx_conf(names):
  399. ret = """
  400. server {
  401. listen 80;
  402. server_name _;
  403. include uwsgi_params;
  404. location /static/ {
  405. alias """ + LODEL2_INSTALLDIR + """/lodel/plugins/webui/templates/;
  406. }
  407. """
  408. for name in names:
  409. name = name.replace('/', '_')
  410. sockfile = os.path.join(buildconf.LODEL2VARDIR, 'uwsgi_sockets/')
  411. sockfile = os.path.join(sockfile, name + '.sock')
  412. ret += """
  413. location /{instance_name}/ {{
  414. uwsgi_pass unix://{sockfile};
  415. }}""".format(instance_name = name, sockfile = sockfile)
  416. ret += """
  417. }
  418. """
  419. print(ret)
  420. ##@brief Returns instanciated parser
  421. def get_parser():
  422. parser = argparse.ArgumentParser(
  423. description='SLIM (Simple Lodel Instance Manager.)')
  424. selector = parser.add_argument_group('Instances selectors')
  425. actions = parser.add_argument_group('Instances actions')
  426. confs = parser.add_argument_group('Options (use with -c or -s)')
  427. startstop = parser.add_argument_group('Start/stop options')
  428. parser.add_argument('-l', '--list',
  429. help='list existing instances and exit', action='store_const',
  430. const=True, default=False)
  431. parser.add_argument('-v', '--verbose', action='count')
  432. parser.add_argument('-b', '--batch', action='store_const',
  433. default=False, const=True,
  434. help="Format output (when possible) making it usable by POSIX scripts \
  435. (only implemented for -l for the moment)")
  436. selector.add_argument('-a', '--all', action='store_const',
  437. default=False, const=True,
  438. help='Select all instances')
  439. selector.add_argument('-n', '--name', metavar='NAME', type=str, nargs='*',
  440. help="Specify an instance name")
  441. actions.add_argument('-c', '--create', action='store_const',
  442. default=False, const=True,
  443. help="Create a new instance with given name (see -n --name)")
  444. actions.add_argument('-d', '--delete', action='store_const',
  445. default=False, const=True,
  446. help="Delete an instance with given name (see -n --name)")
  447. actions.add_argument('-p', '--purge', action='store_const',
  448. default=False, const=True,
  449. help="Delete ALL instances")
  450. actions.add_argument('-s', '--set-option', action='store_const',
  451. default=False, const=True,
  452. help="Use this flag to set options on instance")
  453. actions.add_argument('-e', '--edit-config', action='store_const',
  454. default=False, const=True,
  455. help='Edit configuration of specified instance')
  456. actions.add_argument('-i', '--interactive', action='store_const',
  457. default=False, const=True,
  458. help='Run a loader.py from ONE instance in foreground')
  459. actions.add_argument('-m', '--make', metavar='TARGET', type=str,
  460. nargs="?", default='not',
  461. help='Run make for selected instances')
  462. actions.add_argument('--nginx-conf', action='store_const',
  463. default = False, const=True,
  464. help="Output a conf for nginx given selected instances")
  465. startstop.add_argument('--stop', action='store_const',
  466. default=False, const=True, help="Stop instances")
  467. startstop.add_argument('--start', action='store_const',
  468. default=False, const=True, help="Start instances")
  469. startstop.add_argument('-f', '--foreground', action='store_const',
  470. default=False, const=True, help="Start in foreground (limited \
  471. to 1 instance")
  472. confs.add_argument('--interface', type=str,
  473. help="Select wich interface to run. Possible values are \
  474. 'python' and 'web'")
  475. confs.add_argument('-t', '--static-url', type=str, nargs="?",
  476. default='http://127.0.0.1/static/', metavar='URL',
  477. help='Set an url for static documents')
  478. confs.add_argument('--listen-port', type=int,
  479. help="Select the port on wich the web interface will listen to")
  480. confs.add_argument('--listen-address', type=str,
  481. help="Select the address on wich the web interface will bind to")
  482. confs.add_argument('--datasource_connectors', type=str,
  483. help="Select wich datasource to connect. Possible values are \
  484. 'mongodb' and 'mysql'")
  485. confs.add_argument('--host', type=str,
  486. help="Select the host on which the server DB listen to")
  487. confs.add_argument('--user', type=str,
  488. help="Select the user name to connect to the database")
  489. confs.add_argument('--password', type=str,
  490. help="Select the password name to connect the datasource")
  491. confs.add_argument('--db_name', type=str,
  492. help="Select the database name on which datasource will be connect")
  493. confs.add_argument('--uwsgi-workers', type=int, default='2',
  494. metavar = 'N', help="Number of workers to spawn at the start of uwsgi")
  495. confs.add_argument('--set-logger', type=str, default='default:INFO:-',
  496. metavar = 'LOGGERSPEC', nargs='*',
  497. help='Set a logger given a logger spec. A logger spec is a string \
  498. with this form : LOGGERNAME:LOGLEVEL:LOGFILE with LOGLEVEL one of DEBUG, \
  499. INFO, WARNING, SECURITY, ERROR or FATAL. LOGFILE can be a path to a logfile \
  500. or - to indicate stderr, else you can put a "%%s" in the string that will \
  501. be replaced by instance name and a "%%l" that will be replaced by the \
  502. loglevel.')
  503. return parser
  504. if __name__ == '__main__':
  505. parser = get_parser()
  506. args = parser.parse_args()
  507. if args.verbose is None:
  508. args.verbose = 0
  509. if args.list:
  510. # Instances list
  511. if args.name is not None:
  512. validate_names(args.name)
  513. for name in args.name:
  514. details_instance(name, args.verbose, args.batch)
  515. else:
  516. list_instances(args.verbose, args.batch)
  517. elif args.create:
  518. #Instance creation
  519. if args.name is None:
  520. parser.print_help()
  521. print("\nAn instance name expected when creating an instance !",
  522. file=sys.stderr)
  523. exit(1)
  524. for name in args.name:
  525. new_instance(name)
  526. elif args.purge:
  527. # SLIM Purge (stop & delete all)
  528. print("Do you really want to delete all the instances ? Yes/no ",)
  529. rep = sys.stdin.readline()
  530. if rep == "Yes\n":
  531. store = get_store_datas()
  532. stop_instances(store.keys())
  533. for name in store:
  534. delete_instance(name)
  535. elif rep.lower() != 'no':
  536. print("Expect exactly 'Yes' to confirm...")
  537. exit()
  538. elif args.delete:
  539. #Instance deletion
  540. if args.all:
  541. parser.print_help()
  542. print("\n use -p --purge instead of --delete --all",
  543. file=sys.stderr)
  544. exit(1)
  545. if args.name is None:
  546. parser.print_help()
  547. print("\nAn instance name expected when creating an instance !",
  548. file=sys.stderr)
  549. exit(1)
  550. validate_names(args.name)
  551. for name in args.name:
  552. delete_instance(name)
  553. elif args.make != 'not':
  554. #Running make in instances
  555. if args.make is None:
  556. target = 'all'
  557. else:
  558. target = args.make
  559. names = get_specified(args)
  560. if names is None:
  561. parser.print_help()
  562. print("\nWhen using -m --make options you have to select \
  563. instances, either by name using -n or all using -a")
  564. exit(1)
  565. run_make(target, names)
  566. elif args.edit_config:
  567. #Edit configuration
  568. names = get_specified(args)
  569. if len(names) > 1:
  570. print("\n-e --edit-config option works only when 1 instance is \
  571. specified")
  572. validate_names(names)
  573. name = names[0]
  574. store_datas = get_store_datas()
  575. conffile = get_conffile(name)
  576. os.system('editor "%s"' % conffile)
  577. exit(0)
  578. elif args.nginx_conf:
  579. names = get_specified(args)
  580. if len(names) == 0:
  581. parser.print_help()
  582. print("\nSpecify at least 1 instance or use --all")
  583. exit(1)
  584. nginx_conf(names)
  585. elif args.interactive:
  586. #Run loader.py in foreground
  587. if args.name is None or len(args.name) != 1:
  588. print("\n-i option only allowed with ONE instance name")
  589. parser.print_help()
  590. exit(1)
  591. validate_names(args.name)
  592. name = args.name[0]
  593. store_datas = get_store_datas()
  594. os.chdir(store_datas[name]['path'])
  595. os.execl('/usr/bin/env', '/usr/bin/env', 'python3', 'loader.py')
  596. elif args.set_option:
  597. names = None
  598. if args.all:
  599. names = list(get_store_datas().keys())
  600. elif args.name is not None:
  601. names = args.name
  602. if names is None:
  603. parser.print_help()
  604. print("\n-s option only allowed with instance specified (by name \
  605. or with -a)")
  606. exit(1)
  607. for name in names:
  608. set_conf(name, args)
  609. elif args.start:
  610. names = get_specified(args)
  611. if names is None:
  612. parser.print_help()
  613. print("\nPlease specify at least 1 instance with the --start \
  614. option", file=sys.stderr)
  615. elif args.foreground and len(names) > 1:
  616. parser.print_help()
  617. print("\nOnly 1 instance allowed with the use of the --forground \
  618. argument")
  619. start_instances(names, args.foreground)
  620. elif args.stop:
  621. names = get_specified(args)
  622. stop_instances(names)