dsadm.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. #! /usr/bin/python2
  2. #
  3. # --- BEGIN COPYRIGHT BLOCK ---
  4. # Copyright (C) 2015 Red Hat, Inc.
  5. # All rights reserved.
  6. #
  7. # License: GPL (version 3 or any later version).
  8. # See LICENSE for details.
  9. # --- END COPYRIGHT BLOCK ---
  10. # Authors:
  11. # Thierry Bordaz <[email protected]>
  12. import sys
  13. import os
  14. import argparse
  15. import pdb
  16. import tempfile
  17. import time
  18. import pwd
  19. import grp
  20. import platform
  21. import socket
  22. import shutil
  23. from subprocess import Popen, PIPE, STDOUT
  24. import string
  25. SETUP_DS = "/sbin/setup-ds.pl"
  26. REMOVE_DS = "/sbin/remove-ds.pl"
  27. INITCONFIGDIR = ".dirsrv"
  28. SCRIPT_START = "start-slapd"
  29. SCRIPT_STOP = "stop-slapd"
  30. SCRIPT_RESTART = "restart-slapd"
  31. ENVIRON_SERVERID = '389-SERVER-ID'
  32. ENVIRON_USER = '389-USER'
  33. ENVIRON_GROUP = '389-GROUP'
  34. ENVIRON_DIRECTORY = '389-DIRECTORY'
  35. ENVIRON_PORT = '389-PORT'
  36. ENVIRON_SECURE_PORT = '389-SECURE-PORT'
  37. DEFAULT_PORT_ROOT = str(389)
  38. DEFAULT_PORT_NON_ROOT = str(1389)
  39. DEFAULT_SECURE_PORT_ROOT = str(636)
  40. DEFAULT_SECURE_PORT_NON_ROOT = str(1636)
  41. DEFAULT_USER = 'nobody'
  42. DEFAULT_GROUP = 'nobody'
  43. DEFAULT_ROOT_DN = 'cn=Directory Manager'
  44. DEFAULT_HOSTNAME = socket.gethostname()
  45. def validate_user(user):
  46. '''
  47. If a user is provided it returns its username
  48. else it returns the current username.
  49. It checks that the userId or userName exists
  50. :param: user (optional) can be a userName or userId
  51. :return: userName of the provided user, if none is provided, it returns current user name
  52. '''
  53. assert(user)
  54. if user.isdigit():
  55. try:
  56. username = pwd.getpwuid(int(user)).pw_name
  57. except KeyError:
  58. raise KeyError('Unknown userId %d' % user)
  59. return username
  60. else:
  61. try:
  62. pwd.getpwnam(user).pw_uid
  63. except KeyError:
  64. raise KeyError('Unknown userName %s' % user)
  65. return user
  66. def get_default_user():
  67. user = os.environ.get(ENVIRON_USER, None)
  68. if not user:
  69. user = os.getuid()
  70. return str(user)
  71. def get_default_group():
  72. '''
  73. If a group is provided it returns its groupname
  74. else it returns the current groupname.
  75. It checks that the groupId or groupName exists
  76. :param: group (optional) can be a groupName or groupId
  77. :return: groupName of the provided group, if none is provided, it returns current group name
  78. '''
  79. group = os.environ.get(ENVIRON_GROUP, None)
  80. if not group:
  81. return pwd.getpwuid(os.getuid()).pw_name
  82. return group
  83. def validate_group(group):
  84. assert(group)
  85. if str(group).isdigit():
  86. try:
  87. groupname = grp.getgrgid(group).gr_name
  88. return groupname
  89. except:
  90. raise KeyError('Unknown groupId %d' % group)
  91. else:
  92. try:
  93. groupname = grp.getgrnam(group).gr_name
  94. return groupname
  95. except:
  96. raise KeyError('Unknown groupName %s' % group)
  97. def test_get_group():
  98. try:
  99. grpname = get_default_group()
  100. print('get_group: %s' % grpname)
  101. except:
  102. raise
  103. print("Can not find user group")
  104. pass
  105. try:
  106. grpname = get_default_group(group='tbordaz')
  107. print('get_group: %s' % grpname)
  108. except:
  109. raise
  110. print("Can not find user group")
  111. pass
  112. try:
  113. grpname = get_default_group(group='coucou')
  114. print('get_group: %s' % grpname)
  115. except:
  116. print("Can not find user group coucou")
  117. pass
  118. try:
  119. grpname = get_default_group('thierry')
  120. print('get_group: %s' % grpname)
  121. except:
  122. raise
  123. print("Can not find user group thierry")
  124. pass
  125. try:
  126. grpname = get_default_group(1000)
  127. print('get_group: %s' % grpname)
  128. except:
  129. raise
  130. print("Can not find user group 1000")
  131. pass
  132. try:
  133. grpname = get_default_group(20532)
  134. print('get_group: %s' % grpname)
  135. except:
  136. raise
  137. print("Can not find user group 20532")
  138. pass
  139. try:
  140. grpname = get_default_group(123)
  141. print('get_group: %s' % grpname)
  142. except:
  143. print("Can not find user group 123")
  144. pass
  145. def get_default_port():
  146. port = os.environ.get(ENVIRON_PORT, None)
  147. if port:
  148. return port
  149. if os.getuid() == 0:
  150. return DEFAULT_PORT_ROOT
  151. else:
  152. return DEFAULT_PORT_NON_ROOT
  153. def validate_port(port):
  154. assert port
  155. if not port.isdigit() or int(port) <= 0 :
  156. raise Exception("port number is invalid: %s" % port)
  157. def get_default_directory():
  158. directory = os.environ.get(ENVIRON_DIRECTORY, None)
  159. if not directory:
  160. directory = os.getcwd()
  161. return directory
  162. def validate_directory(directory):
  163. assert directory
  164. if not os.path.isdir(directory):
  165. raise Exception("Supplied directory path is not a directory")
  166. if not os.access(directory, os.W_OK):
  167. raise Exception("Supplied directory is not writable")
  168. def get_default_serverid():
  169. serverid = os.environ.get(ENVIRON_SERVERID, None)
  170. if not serverid:
  171. serverid = socket.gethostname().split('.')[0]
  172. return serverid
  173. def validate_serverid(serverid):
  174. if not serverid:
  175. raise Exception("Server id is not defined")
  176. return serverid
  177. def get_inst_dir(serverid):
  178. assert serverid
  179. home = os.getenv("HOME")
  180. inst_initconfig_file = "%s/%s/dirsrv-%s" % (home, INITCONFIGDIR, serverid)
  181. if not os.path.isfile(inst_initconfig_file):
  182. raise Exception("%s config file not found" % inst_initconfig_file)
  183. f = open(inst_initconfig_file, "r")
  184. for line in f:
  185. if line.startswith("INST_DIR"):
  186. inst_dir = line.split("=")[1]
  187. inst_dir = inst_dir.replace("\r", "")
  188. inst_dir = inst_dir.replace("\n", "")
  189. return inst_dir
  190. def sanity_check():
  191. if os.getuid() == 0:
  192. raise Exception("Not tested for root user.. sorry")
  193. home = os.getenv("HOME")
  194. inst_initconfig_dir = "%s/%s" % (home, INITCONFIGDIR)
  195. if not os.path.isdir(inst_initconfig_dir):
  196. raise Exception("Please create the directory \'%s\' and retry." % inst_initconfig_dir )
  197. class DSadmCmd(object):
  198. def __init__(self):
  199. self.version = '0.1'
  200. def _start_subparser(self, subparsers):
  201. start_parser = subparsers.add_parser(
  202. 'start',
  203. help='Start a Directory Server Instance')
  204. start_parser.add_argument('-I', '--server-id', dest='server_id', type=str, nargs='?',
  205. metavar='SERVER-ID',
  206. help='Server Identifier (Default: %s) ' % get_default_serverid())
  207. start_parser.set_defaults(func=self.start_action)
  208. def _stop_subparser(self, subparsers):
  209. start_parser = subparsers.add_parser(
  210. 'stop',
  211. help='Stop a Directory Server Instance')
  212. start_parser.add_argument('-I', '--server-id', dest='server_id', type=str, nargs='?',
  213. metavar='SERVER-ID',
  214. help='Server Identifier (Default: %s) ' % get_default_serverid())
  215. start_parser.set_defaults(func=self.stop_action)
  216. def _restart_subparser(self, subparsers):
  217. start_parser = subparsers.add_parser(
  218. 'restart',
  219. help='Retart a Directory Server Instance')
  220. start_parser.add_argument('-I', '--server-id', dest='server_id', type=str, nargs='?',
  221. metavar='SERVER-ID',
  222. help='Server Identifier (Default: %s) ' % get_default_serverid())
  223. start_parser.set_defaults(func=self.restart_action)
  224. def _delete_subparser(self, subparsers):
  225. delete_parser = subparsers.add_parser(
  226. 'delete',
  227. help='Delete a Directory Server Instance')
  228. delete_parser.add_argument('-I', '--server-id', dest='server_id', type=str, nargs='?',
  229. metavar='SERVER-ID',
  230. help='Server Identifier (Default: %s) ' % get_default_serverid())
  231. delete_parser.add_argument('-debug', '--debug', dest='debug_level', type=int, nargs='?',
  232. metavar='DEBUG_LEVEL',
  233. help='Debug level (Default: 0)')
  234. delete_parser.set_defaults(func=self.delete_action)
  235. def _create_subparser(self, subparsers):
  236. create_parser = subparsers.add_parser(
  237. 'create',
  238. help='Create a Directory Server Instance')
  239. create_parser.add_argument('-I', '--server-id', dest='server_id', type=str, nargs='?',
  240. metavar='SERVER-ID',
  241. help='Server Identifier (Default: %s) ' % get_default_serverid())
  242. create_parser.add_argument('-s', '--suffix', dest='suffix', type=str, nargs='?',
  243. metavar='SUFFIX-DN',
  244. help='Suffix (Default: create no suffix)')
  245. create_parser.add_argument('-p', '--port', dest='port', type=int, nargs='?',
  246. metavar='NON-SECURE-PORT',
  247. help='Normal Port to listen (Default: %s(root)/%s(non-root)) ' % (DEFAULT_PORT_ROOT, DEFAULT_PORT_NON_ROOT))
  248. create_parser.add_argument('-P', '--secure-port', dest='secure_port', type=int, nargs='?',
  249. metavar='SECURE-PORT',
  250. help='Secure Port to listen (Default: %s(root)/%s(non-root))' % (DEFAULT_SECURE_PORT_ROOT, DEFAULT_SECURE_PORT_NON_ROOT))
  251. create_parser.add_argument('-D', '--rootDN', dest='root_dn', type=str, nargs='?',
  252. metavar='ROOT-DN',
  253. help='Uses DN as Directory Manager DN (Default: \'%s\')' % (DEFAULT_ROOT_DN))
  254. create_parser.add_argument('-u', '--user-name', dest='user_name', type=str, nargs='?',
  255. metavar='USER-NAME',
  256. help='User name of the instance owner (Default: %s)' % DEFAULT_USER)
  257. create_parser.add_argument('-g', '--group-name', dest='group_name', type=str, nargs='?',
  258. metavar='GROUP-NAME',
  259. help='Group name of the instance owner (Default: %s)' % DEFAULT_GROUP)
  260. create_parser.add_argument('-d', '--directory-path', dest='directory_path', type=str, nargs='?',
  261. metavar='DIRECTORY-PATH',
  262. help='Installation directory path (Default: %s)' % get_default_directory())
  263. create_parser.add_argument('-debug', '--debug', dest='debug_level', type=int, nargs='?',
  264. metavar='DEBUG_LEVEL',
  265. help='Debug level (Default: 0)')
  266. create_parser.add_argument('-k', '--keep_template', dest='keep_template', type=str, nargs='?',
  267. help='Keep template file')
  268. create_parser.set_defaults(func=self.create_action)
  269. #
  270. # common function for start/stop/restart actions
  271. #
  272. def script_action(self, args, script, action_str):
  273. args = vars(args)
  274. serverid = args.get('server_id', None)
  275. if not serverid:
  276. serverid = get_default_serverid()
  277. script_file = "%s/%s" % (get_inst_dir(serverid), script)
  278. if not os.path.isfile(script_file):
  279. raise Exception("%s not found" % script_file)
  280. if not os.access(script_file, os.X_OK):
  281. raise Exception("%s not executable" % script_file)
  282. env = os.environ.copy()
  283. prog = [ script_file ]
  284. pipe = Popen(prog, cwd=os.getcwd(), env=env,
  285. stdin=PIPE, stdout=PIPE, stderr=STDOUT)
  286. child_stdin = pipe.stdin
  287. child_stdout = pipe.stdout
  288. for line in child_stdout:
  289. sys.stdout.write(line)
  290. child_stdout.close()
  291. child_stdin.close()
  292. rc = pipe.wait()
  293. if rc == 0:
  294. print("Directory %s %s" % (serverid, action_str))
  295. else:
  296. print("Failure: directory %s not %s (%s)" % (serverid, action_str, rc))
  297. return
  298. def start_action(self, args):
  299. self.script_action(args, SCRIPT_START, "started")
  300. def stop_action(self, args):
  301. self.script_action(args, SCRIPT_STOP, "stopped")
  302. def restart_action(self, args):
  303. self.script_action(args, SCRIPT_RESTART, "restarted")
  304. def delete_action(self, args):
  305. args = vars(args)
  306. serverid = args.get('server_id', None)
  307. if not serverid:
  308. serverid = get_default_serverid()
  309. #prepare the remove-ds options
  310. debug_level = args.get('debug_level', None)
  311. if debug_level:
  312. debug_str = ['-d']
  313. for i in range(1, int(debug_level)):
  314. debug_str.append('d')
  315. debug_str = ''.join(debug_str)
  316. env = os.environ.copy()
  317. prog = [REMOVE_DS]
  318. if debug_level:
  319. prog.append(debug_str)
  320. prog.append("-i")
  321. prog.append("slapd-%s" % serverid)
  322. # run the REMOVE_DS command and print the possible output
  323. pipe = Popen(prog, cwd=os.getcwd(), env=env,
  324. stdin=PIPE, stdout=PIPE, stderr=STDOUT)
  325. child_stdin = pipe.stdin
  326. child_stdout = pipe.stdout
  327. for line in child_stdout:
  328. if debug_level:
  329. sys.stdout.write(line)
  330. child_stdout.close()
  331. child_stdin.close()
  332. rc = pipe.wait()
  333. if rc == 0:
  334. print("Directory server \'%s\' successfully deleted" % serverid)
  335. else:
  336. print("Fail to delete directory \'%s\': %d" % (serverid, rc))
  337. return
  338. #
  339. # used by create subcommand to build the template file
  340. #
  341. def _create_setup_ds_file(self, args, user=None, group=None):
  342. # Get/checks the argument with the following order
  343. # - parameter
  344. # - Environment
  345. # - default
  346. serverid = args.get('server_id', None)
  347. if not serverid:
  348. serverid = get_default_serverid()
  349. serverid = validate_serverid(serverid)
  350. username = args.get('user_name', None)
  351. if not username:
  352. username = get_default_user()
  353. username = validate_user(username)
  354. groupname = args.get('group_name', None)
  355. if not groupname:
  356. groupname = get_default_group()
  357. groupname = validate_group(groupname)
  358. directoryname = args.get('directory_path', None)
  359. if not directoryname:
  360. directoryname = get_default_directory()
  361. validate_directory(directoryname)
  362. portnumber = args.get('port', None)
  363. if not portnumber:
  364. portnumber = get_default_port()
  365. validate_port(portnumber)
  366. suffix = args.get('suffix', None)
  367. tempf = tempfile.NamedTemporaryFile(delete=False)
  368. tempf.write('[General]\n')
  369. tempf.write('FullMachineName=%s\n' % DEFAULT_HOSTNAME)
  370. tempf.write('SuiteSpotUserID=%s\n' % username)
  371. tempf.write('SuiteSpotGroup=%s\n' % groupname)
  372. tempf.write('ServerRoot=%s\n' % directoryname)
  373. tempf.write('\n')
  374. tempf.write('[slapd]\n')
  375. tempf.write('ServerPort=1389\n')
  376. tempf.write('ServerIdentifier=%s\n' % serverid)
  377. if suffix:
  378. tempf.write('Suffix=%s\n' % suffix)
  379. tempf.write('RootDN=cn=Directory Manager\n')
  380. tempf.write('RootDNPwd=Secret12\n')
  381. tempf.write('sysconfdir=%s/etc\n' % directoryname)
  382. tempf.write('localstatedir=%s/var\n' % directoryname)
  383. tempf.write('inst_dir=%s/lib/dirsrv/slapd-%s\n'% (directoryname, serverid))
  384. tempf.write('config_dir=%s/etc/dirsrv/slapd-%s\n' % (directoryname, serverid))
  385. tempf.close()
  386. keep_template = args.get('keep_template', None)
  387. if keep_template:
  388. shutil.copy(tempf.name, keep_template)
  389. return tempf
  390. #
  391. # It silently creates an instance.
  392. # After creation the instance is started
  393. #
  394. def create_action(self, args):
  395. args = vars(args)
  396. # retrieve the serverid here just to log the final status
  397. serverid = args.get('server_id', None)
  398. if not serverid:
  399. serverid = get_default_serverid()
  400. # prepare the template file
  401. tempf = self._create_setup_ds_file(args)
  402. #prepare the setup-ds options
  403. debug_level = args.get('debug_level', None)
  404. if debug_level:
  405. debug_str = ['-d']
  406. for i in range(1, int(debug_level)):
  407. debug_str.append('d')
  408. debug_str = ''.join(debug_str)
  409. #
  410. # run the SETUP_DS command and print the possible output
  411. #
  412. env = os.environ.copy()
  413. prog = [SETUP_DS]
  414. if debug_level:
  415. prog.append(debug_str)
  416. prog.append("--silent")
  417. prog.append("--file=%s" % tempf.name)
  418. tempf.close()
  419. pipe = Popen(prog, cwd=os.getcwd(), env=env,
  420. stdin=PIPE, stdout=PIPE, stderr=STDOUT)
  421. child_stdin = pipe.stdin
  422. child_stdout = pipe.stdout
  423. for line in child_stdout:
  424. if debug_level:
  425. sys.stdout.write(line)
  426. child_stdout.close()
  427. child_stdin.close()
  428. os.unlink(tempf.name)
  429. rc = pipe.wait()
  430. if rc == 0:
  431. print("Directory server \'%s\' successfully created" % serverid)
  432. else:
  433. print("Fail to create directory \'%s\': %d" % (serverid, rc))
  434. return
  435. #
  436. # parser of the main command. It contains subcommands
  437. #
  438. def get_parser(self, argv):
  439. parser = argparse.ArgumentParser(
  440. description='Managing a local directory server instance')
  441. subparsers = parser.add_subparsers(
  442. metavar='SUBCOMMAND',
  443. help='The action to perform')
  444. #pdb.set_trace()
  445. # subcommands
  446. self._create_subparser(subparsers)
  447. self._delete_subparser(subparsers)
  448. self._start_subparser(subparsers)
  449. self._stop_subparser(subparsers)
  450. self._restart_subparser(subparsers)
  451. # Sanity check that the debug level is valid
  452. args = vars(parser.parse_args(argv))
  453. debug_level = args.get('debug_level', None)
  454. if debug_level and (int(debug_level) < 1 or int(debug_level > 5)):
  455. raise Exception("invalid debug level: range 1..5")
  456. return parser
  457. def main(self, argv):
  458. sanity_check()
  459. parser = self.get_parser(argv)
  460. args = parser.parse_args(argv)
  461. args.func(args)
  462. return
  463. if __name__ == '__main__':
  464. DSadmCmd().main(sys.argv[1:])