dever.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. dever-docker tools
  5. name:dever.py
  6. author:rabin
  7. """
  8. import time
  9. import datetime
  10. import os
  11. import sys
  12. import getopt
  13. import ConfigParser
  14. import commands
  15. import re
  16. import subprocess
  17. import urlparse
  18. PATH = ''
  19. class Dever(object):
  20. config = {}
  21. server = []
  22. core = {}
  23. base = {}
  24. path = ''
  25. method = ''
  26. conf = ''
  27. name = ''
  28. param = ''
  29. def __init__(self):
  30. self.initObject()
  31. self.initCore()
  32. self.initArgv()
  33. state = self.initConfig()
  34. if state == 1:
  35. self.run()
  36. def initObject(self):
  37. self.File = File()
  38. self.Container = Container()
  39. self.Image = Image()
  40. self.Alias = Alias()
  41. global PATH
  42. if PATH == '':
  43. PATH = self.File.path()
  44. self.path = PATH
  45. def initCore(self):
  46. core = self.path + 'conf/core.conf'
  47. if self.File.exists(core):
  48. config = ConfigParser.ConfigParser()
  49. config.read(core)
  50. for item in config.sections():
  51. self.core[item] = self.readConfig(config, item)
  52. else:
  53. print core + ' is not exists'
  54. sys.exit()
  55. def initConfig(self):
  56. filename = self.path + 'conf/dever/' + self.conf + '.conf'
  57. if self.File.exists(filename):
  58. config = ConfigParser.ConfigParser()
  59. config.read(filename)
  60. for item in config.sections():
  61. if item == 'base':
  62. self.base = self.readConfig(config, item)
  63. else:
  64. self.server.append(item)
  65. self.config[item] = self.readConfig(config, item)
  66. self.base['path'] = replace('{base}', self.path, self.base['path'])
  67. return 1
  68. else:
  69. print filename + ' is not exists'
  70. return 0
  71. def readConfig(self, config, type):
  72. value = config.options(type)
  73. result = {}
  74. for item in value:
  75. result[item] = config.get(type, item)
  76. return result
  77. def initArgv(self):
  78. slen = len(sys.argv)
  79. if slen >= 2:
  80. self.method = sys.argv[1]
  81. if slen >= 3:
  82. self.conf = sys.argv[2]
  83. if slen >= 4:
  84. self.name = sys.argv[3]
  85. if slen >= 5:
  86. self.param = sys.argv[4]
  87. self.options()
  88. self.handle()
  89. if self.method == '':
  90. print 'method error'
  91. sys.exit()
  92. def options(self):
  93. try:
  94. options, args = getopt.getopt(sys.argv[1:], "hm:n:a:p:", ['help', "method=", "name=", "action=", "param="])
  95. for name, value in options:
  96. if name in ('-h', '--help'):
  97. self.usage()
  98. elif name in ('-m', '--method'):
  99. self.method = value
  100. elif name in ('-c', '--conf'):
  101. self.conf = value
  102. elif name in ('-n', '--name'):
  103. self.name = value
  104. elif name in ('-p', '--param'):
  105. self.param = value
  106. except getopt.GetoptError:
  107. self.usage()
  108. def usage(self):
  109. print ' Usage: \n' + \
  110. ' dever [-m] <method> [-n] <name> [-a] <action> [-p] <param>\n' + \
  111. ' dever <method> <name> <action> <param>\n\n' + \
  112. ' -h or --help Help\n' + \
  113. ' -m or --method Method name\n' + \
  114. ' -c or --conf Configuration file name\n' + \
  115. ' -n or --name The index name in the configuration file\n' + \
  116. ' -p or --param Execution parameters are generally judged by actions\n\n' + \
  117. ' <method>list:\n' + \
  118. ' init Initializes and updates the Dever code\n' + \
  119. ' up Update the Dever code\n' + \
  120. ' show Displays the docker container that is currently started\n' + \
  121. ' showi Displays the current docker mirror\n' + \
  122. ' rm Remove an exception or docker container that has not been started\n' + \
  123. ' rmi Delete expired docker images\n\n' + \
  124. ' <method>+<conf>list:\n' + \
  125. ' run Run the container\n' + \
  126. ' stop Stop the container\n' + \
  127. ' create Create the container\n' + \
  128. ' call Run the container, run only once\n' + \
  129. ' up Run the container and update the docker mirror\n' + \
  130. ' rm Remove the running docker container\n' + \
  131. ' save Save or backup the running docker container\n' + \
  132. ' load Restores and re run the docker container that is saved or backed up\n\n' + \
  133. ' Example:\n' + \
  134. ' dever -m run -c web -n php\n' + \
  135. ' dever run web php\n' + \
  136. ' dever -m call -c tool -n apidoc -p input=demo^out=output\n' + \
  137. ' dever call tool apidoc input=demo^out=output\n\n' + \
  138. '[Dever is a small docker choreography tool]\n'
  139. sys.exit()
  140. def handle(self):
  141. method = ['build', 'push', 'shell']
  142. if self.method not in method and self.conf != '':
  143. return
  144. if self.method == 'init':
  145. self.update()
  146. popen('chmod -R +x ' + self.path + 'shell/')
  147. check('curl')
  148. check('docker')
  149. print 'dever init success'
  150. elif self.method == 'up':
  151. self.update()
  152. print 'dever update success'
  153. elif self.method == 'commit':
  154. print shell('git.push ' + self.path)
  155. elif self.method == 'push':
  156. self.push(self.conf)
  157. elif self.method == 'package':
  158. self.package()
  159. elif self.method == 'path':
  160. print self.path
  161. elif self.method == 'show':
  162. self.Container.show()
  163. elif self.method == 'showi':
  164. self.Image.show()
  165. elif self.method == 'rmi':
  166. self.Image.delete()
  167. print 'docker rm none container:yes'
  168. elif self.method == 'rm':
  169. self.Container.delete()
  170. print 'docker rm image:yes'
  171. elif self.method == 'drop':
  172. self.Container.drop()
  173. self.Image.drop()
  174. print 'docker drop image:yes'
  175. elif self.method == 'build':
  176. self.Image.build(self.core['base'], self.path, self.conf)
  177. self.Container.delete()
  178. self.Image.delete()
  179. print 'docker build '+self.conf+':yes'
  180. elif self.method == 'shell':
  181. print shell(self.conf)
  182. else:
  183. return
  184. sys.exit()
  185. def package(self):
  186. dockerHub = self.core['base']['dockerhub'] + '/'
  187. dockerMe = self.core['base']['dockerme'] + '/'
  188. result = {}
  189. for key in self.core['package']:
  190. print 'package : [' + dockerHub + key + '] or [' + dockerMe + self.core['package'][key] + ']'
  191. result[dockerHub + key] = dockerMe + self.core['package'][key]
  192. return result
  193. def push(self, name=''):
  194. package = self.package()
  195. if name != '':
  196. if name in package:
  197. self.Image.push(package[name], name)
  198. else:
  199. print 'error ' + name
  200. sys.exit()
  201. for key in package:
  202. self.Image.push(package[key], key)
  203. def update(self):
  204. print 'loading...'
  205. if '/usr/bin/' not in self.path:
  206. shell('git.pull ' + self.path)
  207. self.code()
  208. def code(self):
  209. content = self.File.read(self.path, 'dever.py')
  210. content = content.replace('PATH = \'\'', 'PATH="'+self.path+'"')
  211. self.File.write('/usr/bin/dever', content)
  212. content = '#!/usr/bin/env sh \nset -e\ncd ' + self.path
  213. self.File.write('/usr/bin/godever', content)
  214. def run(self):
  215. self.Container.network(self.base)
  216. self.rely(self.base, self.method)
  217. one = False
  218. if self.name in self.config:
  219. one = True
  220. if one:
  221. self.runServer(self.config[self.name], self.name, self.method)
  222. else:
  223. for item in self.server:
  224. if self.checkServer(self.name, item) == True:
  225. self.runServer(self.config[item], item, self.method)
  226. def checkServer(self, name, item):
  227. if '#' in item:
  228. return False
  229. elif name == '':
  230. return True
  231. elif name+'-' in item:
  232. return True
  233. else:
  234. return False
  235. def runServer(self, config, item, action='run'):
  236. self.rely(config, action)
  237. if 'num' not in config:
  238. config['num'] = 1
  239. num = int(config['num'])
  240. i = 1;
  241. while (i <= num):
  242. self.runServerOne(config, item, i, action)
  243. i = i + 1
  244. def runServerOne(self, config, item, i, action=''):
  245. name = self.getServerName(item, i)
  246. command = ''
  247. daemon = '-d'
  248. restart = '--restart=always'
  249. method = 'run'
  250. if 'daemon' in config:
  251. daemon = config['daemon']
  252. if daemon == 'false':
  253. daemon = ''
  254. restart = ''
  255. if 'image' not in config:
  256. config['image'] = ''
  257. if 'restart' in config:
  258. restart = ''
  259. run = True;
  260. if action == 'create':
  261. method = action
  262. elif action == 'call':
  263. runCommand = self.getServerParam(config, 'call', '', name)
  264. command = self.getServerParam(config, 'param', '', name)
  265. restart = '--entrypoint' + runCommand
  266. daemon = '--rm'
  267. elif action == 'stop':
  268. self.Container.stop(name)
  269. self.slave(config, item, action)
  270. run = False
  271. elif action == 'restart':
  272. self.Container.restart(name)
  273. self.slave(config, item, action)
  274. run = False
  275. elif action == 'logs':
  276. self.Container.logs(name)
  277. run = False
  278. elif action == 'show':
  279. self.Container.show(name)
  280. run = False
  281. elif action == 'inspect':
  282. self.Container.inspect(name)
  283. run = False
  284. elif action in ['rm', 'reset']:
  285. self.Container.delete(name)
  286. self.Alias.delete(self.path, config, name)
  287. self.slave(config, item, action)
  288. run = False
  289. elif action in ['up', 'reset']:
  290. self.update()
  291. self.Image.install(self.config, self.base['library'], config['image'])
  292. elif action in ['save', 'load']:
  293. path = self.path + 'data/backup/' + name + '/'
  294. self.File.mkdir(path)
  295. backup = 'backup/' + name
  296. tar = path + name + '.tar'
  297. if action == 'save':
  298. self.Container.save(self.File, tar, name, backup)
  299. elif action == 'load':
  300. self.Container.load(tar, name)
  301. self.base['library'] = ''
  302. config['image'] = backup
  303. self.runServerOne(config, item, i)
  304. run = False;
  305. if run == False:
  306. return
  307. state = self.Container.check(name)
  308. if state == 0:
  309. self.hook('start', config, name, self.path)
  310. docker = ['-it', '--name='+name, '--hostname='+name, restart, daemon, '-v '+self.path+'data/share:/share -v /etc/hosts:/etc/hosts.main']
  311. param = self.Container.config()
  312. for key in param:
  313. if param[key] != '':
  314. value = self.getServerParam(config, key, param[key], name)
  315. if value != '':
  316. docker.append(value)
  317. docker.append(self.base['library'] + config['image'])
  318. if command == '' and 'command' in param:
  319. value = self.getServerParam(config, 'command', param['command'], name)
  320. command = value
  321. if command != '':
  322. docker.append(command)
  323. command = ' '.join(docker)
  324. bg = False
  325. print 'setuping ' + name + ', please wait...'
  326. else:
  327. #if daemon == '':
  328. #self.runServerOne(config, item, i, 'rm')
  329. #self.runServerOne(config, item, i)
  330. #return
  331. method = 'restart';
  332. command = name
  333. bg = True
  334. print 'reloading ' + name + ', please wait...'
  335. if action == 'test':
  336. print command
  337. else:
  338. self.Container.run(method, method + ' ' + command, bg)
  339. if state == 0:
  340. self.Alias.add(self.path, config, name, 'docker ' + method + ' ' + command, self.File, action)
  341. self.hook('end', config, name, self.path)
  342. self.slave(config, item, action)
  343. def rely(self, config, action):
  344. if 'rely' in config:
  345. if ',' in config['rely']:
  346. rely = config['rely'].split(',');
  347. for i in rely:
  348. popen('dever ' + action + ' ' + i, True)
  349. else:
  350. popen('dever ' + action + ' ' + config['rely'], True)
  351. def hook(self, type, config, name, path):
  352. key = 'hook.'+type
  353. if key in config:
  354. shell('hook.' + config[key] + ' ' + name + ' ' + path, bg=True)
  355. def slave(self, config, name, action):
  356. if 'slave' in config:
  357. i = 1
  358. num = int(config['slave'])
  359. key = ['slave', 'command', 'alias', 'port', 'hook.start', 'hook.end']
  360. for k in key:
  361. if k in config:
  362. del config[k]
  363. while (i <= num):
  364. self.runServerOne(config, name + '-slave', i, action)
  365. i = i + 1
  366. def getServerName(self, name, i):
  367. name = self.conf + '-' + name
  368. if i > 1:
  369. self.base['i'] = '' + str(i)
  370. i = i - 1;
  371. self.base['num'] = '' + str(i)
  372. return name + self.base['num'];
  373. self.base['num'] = ''
  374. self.base['i'] = '1'
  375. return name;
  376. def getServerParam(self, config, item, prefix, name):
  377. result = ''
  378. concat = ' '
  379. if item in config:
  380. if '#' not in item:
  381. result = config[item]
  382. if '[' in config[item]:
  383. search = re.search(r'\[(.*?)\]', result, re.M|re.I)
  384. temp = search.group(1)
  385. if temp:
  386. result = replace('['+str(temp)+']', self.getServerName(temp, 1), result)
  387. result = replace('{num}', self.base['num'], result)
  388. result = replace('{i}', self.base['i'], result)
  389. result = replace('{path}', self.base['path'], result)
  390. result = replace('{name}', name, result)
  391. result = self.getServerParse(result)
  392. result = replace(',', ' ' + prefix + ' ', result)
  393. if item == 'hostname':
  394. name = result
  395. elif item == 'network' and 'network' in self.base:
  396. result = self.base['network']
  397. if result != '':
  398. if '=' in prefix:
  399. concat = ''
  400. result = prefix + concat + result
  401. return result
  402. def getServerParse(self, result):
  403. if '{$' in result:
  404. param = {}
  405. if self.param != '':
  406. if 'http://' not in self.param:
  407. self.param = 'http://shemic.com/?' + self.param;
  408. if '^' in self.param:
  409. self.param = replace('^', '&', self.param)
  410. parse = urlparse.urlparse(self.param)
  411. param = urlparse.parse_qs(parse.query,True)
  412. search = re.compile(r'\{\$(.*?)\}')
  413. search = search.findall(result)
  414. for key in search:
  415. value = ''
  416. index = key
  417. if ':' in key:
  418. arr = key.split(':');
  419. index = arr[0]
  420. value = arr[1]
  421. if index in param:
  422. value = param[index][0]
  423. if value == '':
  424. print 'please set param value:' + index
  425. sys.exit()
  426. else:
  427. result = replace('{$'+key+'}', value, result)
  428. return result
  429. class Container(object):
  430. def run(self, method, command, bg):
  431. command = 'container.exec ' + command
  432. shell(command, True, bg)
  433. return command
  434. def show(self, name=''):
  435. print shell('container.show ' + name)
  436. def config(self):
  437. config = {
  438. 'port' : '-p'
  439. ,'volumes' : '-v'
  440. ,'environment' : '-e'
  441. ,'link' : '--link'
  442. ,'volumes_from' : '--volumes-from'
  443. ,'command' : ''
  444. ,'entrypoint' : '--entrypoint'
  445. ,'network' : '--net='
  446. ,'host' : '--add-host'
  447. ,'root' : '--privileged='
  448. ,'memory' : '--memory='
  449. ,'expose' : '--expose',
  450. }
  451. return config
  452. def drop(self):
  453. shell('container.drop', bg=True)
  454. def stop(self, name):
  455. shell('container.stop ' + name, bg=True)
  456. def logs(self, name):
  457. shell('container.logs ' + name, True)
  458. def inspect(self, name):
  459. shell('container.inspect ' + name, True)
  460. def restart(self, name):
  461. shell('container.restart ' + name, bg=True)
  462. def delete(self, name=''):
  463. if name != '':
  464. print 'rm ' + name + ', please wait...'
  465. if self.check(name) == 1:
  466. shell('container.rm ' + name, bg=True)
  467. else:
  468. shell('container.rm', bg=True)
  469. def check(self, name):
  470. result = int(popen('docker ps -a | grep '+name+' | wc -l'))
  471. if result != 0:
  472. return 1
  473. else:
  474. return 0
  475. def network(self, config):
  476. if 'network' in config:
  477. result = int(popen('docker network ls | grep ' + config['network'] + ' | wc -l'))
  478. if result == 0:
  479. shell('container.network ' + config['network'], True)
  480. def save(self, File, tar, name, backup):
  481. if File.exists(tar) == True:
  482. now = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
  483. old = replace('.tar', '.' + now + '.tar', tar)
  484. File.rename(tar, old)
  485. popen('docker commit -p ' + name + ' ' + backup)
  486. popen('docker save ' + backup + ' > ' + tar, True)
  487. def load(self, tar, name):
  488. popen('docker load < ' + tar, True)
  489. self.delete(name)
  490. class Image(object):
  491. def push(self, me, hub):
  492. if self.check(me) == 1:
  493. command = 'image.push ' + me + ' ' + hub
  494. print command
  495. shell(command, bg=True)
  496. def show(self):
  497. print shell('image.show')
  498. def drop(self):
  499. shell('image.drop', bg=True)
  500. def delete(self):
  501. shell('image.rm', bg=True)
  502. def build(self, core, path, name):
  503. file = path + 'conf/docker/' + name + '/'
  504. name = core['dockerme'] + '/' + name
  505. shell('image.build ' + name + ' ' + file, True)
  506. def check(self, name):
  507. result = int(popen('docker images | grep '+name+' | wc -l'))
  508. if result != 0:
  509. return 1
  510. else:
  511. return 0
  512. def install(self, config, library, key):
  513. pull = 'docker pull';
  514. for item in config:
  515. if config[item]['image'] == key:
  516. command = pull + ' ' + library + config[item]['image']
  517. popen(command, True)
  518. print 'finished'
  519. class Alias(object):
  520. def delete(self, path, config, name):
  521. return
  522. result = self.get(path, config, name)
  523. for key in result:
  524. action = self.action(name, key)
  525. popen('rm -rf ' + action[1], bg=True)
  526. popen('rm -rf ' + action[2], bg=True)
  527. def add(self, path, config, name, content, File, type):
  528. result = self.get(path, config, name)
  529. for key in result:
  530. action = self.action(name, key)
  531. content = '#!/usr/bin/env sh \nset -e\n'
  532. if type != 'call':
  533. content = content + self.define(name) + \
  534. 'else\n' + \
  535. 'docker exec -it ' + name + ' ' + action[0] + ' $@\n' + \
  536. 'fi'
  537. else:
  538. content = content + ' $@'
  539. File.write(action[1], content)
  540. popen('ln -sf ' + action[1] + ' ' + action[2])
  541. def define(self, name):
  542. conf = ['logs', 'inspect', 'restart', 'stop', 'rm', 'run', 'show']
  543. result = ''
  544. container = name
  545. arr = name.split('-');
  546. prefix = arr[0]
  547. del arr[0]
  548. name = '-'.join(arr)
  549. name = prefix + ' ' + name
  550. for key in conf:
  551. control = 'elif'
  552. shell = 'dever ' + key + ' ' + name + '\n'
  553. if key == 'logs':
  554. control = 'if'
  555. result = result + control + ' [ "$1" = "'+key+'" ];then\n' + shell
  556. return result
  557. def get(self, path, config, name):
  558. self.path = path + 'data/alias/'
  559. result = []
  560. default = 'sh->' + name
  561. if 'alias' in config:
  562. config['alias'] = config['alias'] + ',' + default
  563. if ',' in config['alias']:
  564. result = config['alias'].split(',');
  565. else:
  566. result = [config['alias']]
  567. else:
  568. result = [default]
  569. return result
  570. def action(self, name, key):
  571. file = key
  572. if '->' in key:
  573. temp = key.split('->')
  574. key = temp[0]
  575. file = temp[1]
  576. link = '/usr/bin/' + file
  577. file = self.path + file
  578. return [key, file, link]
  579. class File(object):
  580. def write(self, file, content):
  581. handle = open(file, 'w')
  582. handle.write(content)
  583. handle.close()
  584. popen('chmod +x ' + file)
  585. def read(self, path, name):
  586. handle = open(path + name, 'r')
  587. content = handle.read()
  588. handle.close()
  589. return content
  590. def path(self):
  591. return os.path.split(os.path.realpath(__file__))[0] + '/'
  592. def exists(self, name):
  593. return os.path.exists(name)
  594. def rename(self, old, new):
  595. return os.rename(old, new)
  596. def remove(self, file):
  597. return os.remove(file)
  598. def mkdir(self, path):
  599. if self.exists(path) == False:
  600. os.mkdir(path)
  601. def shell(command, sub=False, bg=False):
  602. global PATH
  603. shell = PATH + 'shell/' + command.replace('.', '/', 1)
  604. return popen(shell, sub, bg)
  605. def popen(command, sub=False, bg=False):
  606. string = command
  607. if bg == True:
  608. command = command + ' 1>/dev/null 2>&1 &'
  609. if sub == False:
  610. process = os.popen(command)
  611. output = process.read()
  612. process.close()
  613. return output
  614. else:
  615. popen = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
  616. output = ''
  617. print string
  618. while True:
  619. output = popen.stdout.readline()
  620. print output
  621. if popen.poll() is not None:
  622. break
  623. return output
  624. def install(soft):
  625. print 'install ' + soft + '...'
  626. if soft == 'docker':
  627. shell('install.docker', True)
  628. else:
  629. shell('install.package ' + soft, True)
  630. def isset(v):
  631. try :
  632. type(eval(v))
  633. except :
  634. return 0
  635. else :
  636. return 1
  637. def check(soft):
  638. result = int(popen('which '+soft+' | wc -l'))
  639. if result != 0:
  640. return 1
  641. else:
  642. install(soft)
  643. return 0
  644. def replace(old, new, string):
  645. if old in string:
  646. string = string.replace(old, new)
  647. return string
  648. Dever()