#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    dever-docker tools
    name:dever.py
    author:rabin
"""
import time
import datetime
import os
import sys
import getopt
import ConfigParser
import commands
import re
import subprocess
import urlparse
PATH = ''
class Dever(object):

	config = {}
	server = []
	core = {}
	base = {}
	path = ''
	method = ''
	conf = ''
	name = ''
	param = ''

	def __init__(self):
		self.initObject()
		self.initCore()
		self.initArgv()
		state = self.initConfig()
		if state == 1:
			self.run()

	def initObject(self):
		self.File = File()
		self.Container = Container()
		self.Image = Image()
		self.Alias = Alias()
		global PATH
		if PATH == '':
			PATH = self.File.path()
		self.path = PATH

	def initCore(self):
		core = self.path + 'conf/core.conf'
		if self.File.exists(core):
			config = ConfigParser.ConfigParser()
			config.read(core)
			for item in config.sections():
				self.core[item] = self.readConfig(config, item)
		else:
			print core + ' is not exists'
			sys.exit()

	def initConfig(self):
		filename = self.path + 'conf/dever/' + self.conf + '.conf'
		if self.File.exists(filename):
			config = ConfigParser.ConfigParser()
			config.read(filename)

			for item in config.sections():
				if item == 'base':
					self.base = self.readConfig(config, item)
				else:
					self.server.append(item)
					self.config[item] = self.readConfig(config, item)

			self.base['path'] = replace('{base}', self.path, self.base['path'])
			return 1
		else:
			print filename + ' is not exists'
			return 0


	def readConfig(self, config, type):
		value = config.options(type)
		result = {}
		for item in value:
			result[item] = config.get(type, item)
		return result

	def initArgv(self):
		slen = len(sys.argv)
		if slen >= 2:
			self.method = sys.argv[1]
		if slen >= 3:
			self.conf = sys.argv[2]
		if slen >= 4:
			self.name = sys.argv[3]
		if slen >= 5:
			self.param = sys.argv[4]

		self.options()

		self.handle()
		if self.method == '':
			print 'method error'
			sys.exit()

	def options(self):
		try:
			options, args = getopt.getopt(sys.argv[1:], "hm:n:a:p:", ['help', "method=", "name=", "action=", "param="])
			for name, value in options:
				if name in ('-h', '--help'):
					self.usage()
				elif name in ('-m', '--method'):
					self.method = value
				elif name in ('-c', '--conf'):
					self.conf = value
				elif name in ('-n', '--name'):
					self.name = value
				elif name in ('-p', '--param'):
					self.param = value
		except getopt.GetoptError:
			self.usage()

	def usage(self):
		print ' Usage: \n' + \
		'  dever [-m] <method> [-n] <name> [-a] <action> [-p] <param>\n' + \
		'  dever <method> <name> <action> <param>\n\n' + \
		' -h or --help		Help\n' + \
		' -m or --method		Method name\n' + \
		' -c or --conf		Configuration file name\n' + \
		' -n or --name		The index name in the configuration file\n' + \
		' -p or --param		Execution parameters are generally judged by actions\n\n' + \
		' <method>list:\n' + \
		' init		Initializes and updates the Dever code\n' + \
		' up		Update the Dever code\n' + \
		' show		Displays the docker container that is currently started\n' + \
		' showi		Displays the current docker mirror\n' + \
		' rm		Remove an exception or docker container that has not been started\n' + \
		' rmi 		Delete expired docker images\n\n' + \
		' <method>+<conf>list:\n' + \
		' run		Run the container\n' + \
		' stop		Stop the container\n' + \
		' create		Create the container\n' + \
		' call		Run the container, run only once\n' + \
		' up		Run the container and update the docker mirror\n' + \
		' rm		Remove the running docker container\n' + \
		' save		Save or backup the running docker container\n' + \
		' load		Restores and re run the docker container that is saved or backed up\n\n' + \
		' Example:\n' + \
		' dever -m run -c web -n php\n' + \
		' dever run web php\n' + \
		' dever -m call -c tool -n apidoc -p input=demo^out=output\n' + \
		' dever call tool apidoc input=demo^out=output\n\n' + \
		'[Dever is a small docker choreography tool]\n'
		sys.exit()

	def handle(self):
		method = ['build', 'push', 'shell']
		if self.method not in method and self.conf != '':
			return
		if self.method == 'init':
			self.update()
			popen('chmod -R +x ' + self.path + 'shell/')
			check('curl')
			check('docker')
			print 'dever init success'
		elif self.method == 'up':
			self.update()
			print 'dever update success'
		elif self.method == 'commit':
			print shell('git.push ' + self.path)
		elif self.method == 'push':
			self.push(self.conf)
		elif self.method == 'package':
			self.package()
		elif self.method == 'path':
			print self.path
		elif self.method == 'show':
			self.Container.show()
		elif self.method == 'showi':
			self.Image.show()
		elif self.method == 'rmi':
			self.Image.delete()
			print 'docker rm none container:yes'
		elif self.method == 'rm':
			self.Container.delete()
			print 'docker rm image:yes'
		elif self.method == 'drop':
			self.Container.drop()
			self.Image.drop()
			print 'docker drop image:yes'
		elif self.method == 'build':
			self.Image.build(self.core['base'], self.path, self.conf)
			self.Container.delete()
			self.Image.delete()
			print 'docker build '+self.conf+':yes'
		elif self.method == 'shell':
			print shell(self.conf)
		else:
			return
		sys.exit()

	def package(self):
		dockerHub = self.core['base']['dockerhub'] + '/'
		dockerMe = self.core['base']['dockerme'] + '/'
		result = {}
		for key in self.core['package']:
			print 'package	:	[' + dockerHub + key + ']	or 	[' + dockerMe + self.core['package'][key] + ']'
			result[dockerHub + key] = dockerMe + self.core['package'][key]
		return result

	def push(self, name=''):
		package = self.package()

		if name != '':
			if name in package:
				self.Image.push(package[name], name)
			else:
				print 'error ' + name
			sys.exit()

		for key in package:
			self.Image.push(package[key], key)

	def update(self):
		print 'loading...'
		if '/usr/bin/' not in self.path:
			shell('git.pull ' + self.path)
			self.code()

	def code(self):
		content = self.File.read(self.path, 'dever.py')
		content = content.replace('PATH = \'\'', 'PATH="'+self.path+'"')
		self.File.write('/usr/bin/dever', content)
		content = '#!/usr/bin/env sh \nset -e\ncd ' + self.path
		self.File.write('/usr/bin/godever', content)

	def run(self):
		self.Container.network(self.base)
		self.rely(self.base, self.method)
		one = False
		if self.name in self.config:
			one = True
		if one:
			self.runServer(self.config[self.name], self.name, self.method)
		else:
			for item in self.server:
				if self.checkServer(self.name, item) == True:
					self.runServer(self.config[item], item, self.method)

	def checkServer(self, name, item):
		if '#' in item:
			return False
		elif name == '':
			return True
		elif name+'-' in item:
			return True
		else:
			return False

	def runServer(self, config, item, action='run'):
		self.rely(config, action)
		if 'num' not in config:
			config['num'] = 1
		num = int(config['num'])
		i = 1;
		while (i <= num):
			self.runServerOne(config, item, i, action)
			i = i + 1

	def runServerOne(self, config, item, i, action=''):
		name = self.getServerName(item, i)
		command = ''
		daemon = '-d'
		restart = '--restart=always'
		method = 'run'
		if 'daemon' in config:
			daemon = config['daemon']
			if daemon == 'false':
				daemon = ''
				restart = ''
		if 'image' not in config:
			config['image'] = ''
		if 'restart' in config:
			restart = ''

		run = True;
		if action == 'create':
			method = action
		elif action == 'call':
			runCommand = self.getServerParam(config, 'call', '', name)
			command = self.getServerParam(config, 'param', '', name)
			restart = '--entrypoint' + runCommand
			daemon = '--rm'

		elif action == 'stop':
			self.Container.stop(name)
			self.slave(config, item, action)
			run = False

		elif action == 'restart':
			self.Container.restart(name)
			self.slave(config, item, action)
			run = False

		elif action == 'logs':
			self.Container.logs(name)
			run = False

		elif action == 'show':
			self.Container.show(name)
			run = False

		elif action == 'inspect':
			self.Container.inspect(name)
			run = False

		elif action in ['rm', 'reset']:
			self.Container.delete(name)
			self.Alias.delete(self.path, config, name)
			self.slave(config, item, action)
			run = False

		elif action in ['up', 'reset']:
			self.update()
			self.Image.install(self.config, self.base['library'], config['image'])

		elif action in ['save', 'load']:
			path = self.path + 'data/backup/' + name + '/'
			self.File.mkdir(path)
			backup = 'backup/' + name
			tar = path + name + '.tar'
			if action == 'save':
				self.Container.save(self.File, tar, name, backup)
				
			elif action == 'load':
				self.Container.load(tar, name)
				self.base['library'] = ''
				config['image'] = backup
				self.runServerOne(config, item, i)
			run = False;

		if run == False:
			return

		state = self.Container.check(name)
		if state == 0:
			self.hook('start', config, name, self.path)
			docker = ['-it', '--name='+name, '--hostname='+name, restart, daemon, '-v '+self.path+'data/share:/share -v /etc/hosts:/etc/hosts.main']

			param = self.Container.config()
			for key in param:
				if param[key] != '':
					value = self.getServerParam(config, key, param[key], name)
					if value != '':
						docker.append(value)

			docker.append(self.base['library'] + config['image'])

			if command == '' and 'command' in param:
				value = self.getServerParam(config, 'command', param['command'], name)
				command = value

			if command != '':
				docker.append(command)

			command = ' '.join(docker)
			bg = False
			print 'setuping ' + name + ', please wait...'

		else:
			#if daemon == '':
				#self.runServerOne(config, item, i, 'rm')
				#self.runServerOne(config, item, i)
				#return
			method = 'restart';
			command = name
			bg = True
			print 'reloading ' + name + ', please wait...'
		
		if action == 'test':
			print command
		else:
			self.Container.run(method, method + ' ' + command, bg)
			if state == 0:
				self.Alias.add(self.path, config, name, 'docker ' + method + ' ' + command, self.File, action)
				self.hook('end', config, name, self.path)
		self.slave(config, item, action)

	def rely(self, config, action):
		if 'rely' in config:
			if ',' in config['rely']:
				rely = config['rely'].split(',');
				for i in rely:
					popen('dever ' + action + ' ' + i, True)
			else:
				popen('dever ' + action + ' ' + config['rely'], True)

	def hook(self, type, config, name, path):
		key = 'hook.'+type
		if key in config:
			shell('hook.' + config[key] + ' ' + name + ' ' + path, bg=True)

	def slave(self, config, name, action):
		if 'slave' in config:
			i = 1
			num = int(config['slave'])
			key = ['slave', 'command', 'alias', 'port', 'hook.start', 'hook.end']
			for k in key:
				if k in config:
					del config[k]
			while (i <= num):
				self.runServerOne(config, name + '-slave', i, action)
				i = i + 1

	def getServerName(self, name, i):
		name = self.conf + '-' + name
		if i > 1:
			self.base['i'] = '' + str(i)
			i = i - 1;
			self.base['num'] = '' + str(i)
			return name + self.base['num'];

		self.base['num'] = ''
		self.base['i'] = '1'
		return name;

	def getServerParam(self, config, item, prefix, name):
		result = ''
		concat = ' '
		if item in config:
			if '#' not in item:
				result = config[item]
				if '[' in config[item]:
					search = re.search(r'\[(.*?)\]', result, re.M|re.I)
					temp = search.group(1)
					if temp:
						result = replace('['+str(temp)+']', self.getServerName(temp, 1), result)
				result = replace('{num}', self.base['num'], result)
				result = replace('{i}', self.base['i'], result)
				result = replace('{path}', self.base['path'], result)
				result = replace('{name}', name, result)
				result = self.getServerParse(result)
				result = replace(',', ' ' + prefix + ' ', result)
				if item == 'hostname':
					name = result

		elif item == 'network' and 'network' in self.base:
			result = self.base['network']

		if result != '':
			if '=' in prefix:
				concat = ''
			result = prefix + concat + result

		return result

	def getServerParse(self, result):
		if '{$' in result:
			param = {}
			if self.param != '':
				if 'http://' not in self.param:
					self.param = 'http://shemic.com/?' + self.param;
				if '^' in self.param:
					self.param = replace('^', '&', self.param)
				parse = urlparse.urlparse(self.param)
				param = urlparse.parse_qs(parse.query,True)

			search = re.compile(r'\{\$(.*?)\}')
			search = search.findall(result)
			for key in search:
				value = ''
				index = key
				if ':' in key:
					arr = key.split(':');
					index = arr[0]
					value = arr[1]
				if index in param:
					value = param[index][0]
				if value == '':
					print 'please set param value:' + index
					sys.exit()
				else:
					result = replace('{$'+key+'}', value, result)
		return result

class Container(object):

	def run(self, method, command, bg):
		command = 'container.exec ' + command
		shell(command, True, bg)
		return command

	def show(self, name=''):
		print shell('container.show ' + name)

	def config(self):
		config = {
			'port' : '-p'
			,'volumes' : '-v'
			,'environment' : '-e'
			,'link' : '--link'
			,'volumes_from' : '--volumes-from'
			,'command' : ''
			,'entrypoint' : '--entrypoint'
			,'network' : '--net='
			,'host' : '--add-host'
			,'root' : '--privileged='
			,'memory' : '--memory='
			,'expose' : '--expose',
		}
		return config

	def drop(self):
		shell('container.drop', bg=True)

	def stop(self, name):
		shell('container.stop ' + name, bg=True)

	def logs(self, name):
		shell('container.logs ' + name, True)

	def inspect(self, name):
		shell('container.inspect ' + name, True)

	def restart(self, name):
		shell('container.restart ' + name, bg=True)

	def delete(self, name=''):
		if name != '':
			print 'rm ' + name + ', please wait...'
			if self.check(name) == 1:
				shell('container.rm ' + name, bg=True)
		else:
			shell('container.rm', bg=True)
			

	def check(self, name):
		result = int(popen('docker ps -a | grep '+name+' | wc -l'))
		if result != 0:
			return 1
		else:
			return 0

	def network(self, config):
		if 'network' in config:
			result = int(popen('docker network ls | grep ' + config['network'] + ' | wc -l'))
			if result == 0:
				shell('container.network ' + config['network'], True)

	def save(self, File, tar, name, backup):
		if File.exists(tar) == True:
			now = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
			old = replace('.tar', '.' + now + '.tar', tar)
			File.rename(tar, old)
		popen('docker commit -p ' + name + ' ' + backup)
		popen('docker save ' + backup + ' > ' + tar, True)

	def load(self, tar, name):
		popen('docker load < ' + tar, True)
		self.delete(name)

class Image(object):

	def push(self, me, hub):
		if self.check(me) == 1:
			command = 'image.push ' + me + ' ' + hub
			print command
			shell(command, bg=True)
	def show(self):
		print shell('image.show')

	def drop(self):
		shell('image.drop', bg=True)

	def delete(self):
		shell('image.rm', bg=True)

	def build(self, core, path, name):
		file = path + 'conf/docker/' + name + '/'
		name = core['dockerme'] + '/' + name
		shell('image.build ' + name + ' ' + file, True)

	def check(self, name):
		result = int(popen('docker images | grep '+name+' | wc -l'))
		if result != 0:
			return 1
		else:
			return 0

	def install(self, config, library, key):
		pull = 'docker pull';
		for item in config:
			if config[item]['image'] == key:
				command = pull + ' ' + library + config[item]['image']
				popen(command, True)
		print 'finished'

class Alias(object):

	def delete(self, path, config, name):
		return
		result = self.get(path, config, name)
		for key in result:
			action = self.action(name, key)
			popen('rm -rf ' + action[1], bg=True)
			popen('rm -rf ' + action[2], bg=True)

	def add(self, path, config, name, content, File, type):
		result = self.get(path, config, name)
		for key in result:
			action = self.action(name, key)
			content = '#!/usr/bin/env sh \nset -e\n'
			if type != 'call':
				content = content + self.define(name) + \
						'else\n' + \
						'docker exec -it ' + name + ' ' + action[0] + ' $@\n' + \
						'fi'
			else:
				content = content + ' $@'
			File.write(action[1], content)
			popen('ln -sf ' + action[1] + ' ' + action[2])

	def define(self, name):
		conf = ['logs', 'inspect', 'restart', 'stop', 'rm', 'run', 'show']
		result = ''
		container = name
		arr = name.split('-');
		prefix = arr[0]
		del arr[0]
		name = '-'.join(arr)
		name = prefix + ' ' + name
		for key in conf:
			control = 'elif'
			shell = 'dever ' + key + ' ' + name + '\n'
			if key == 'logs':
				control = 'if'
			result = result + control + ' [ "$1" = "'+key+'" ];then\n' + shell
		return result

	def get(self, path, config, name):
		self.path = path + 'data/alias/'
		result = []
		default = 'sh->' + name
		if 'alias' in config:
			config['alias'] = config['alias'] + ',' + default
			if ',' in config['alias']:
				result = config['alias'].split(',');
			else:
				result = [config['alias']]
		else:
			result = [default]
		return result

	def action(self, name, key):
		file = key
		if '->' in key:
			temp = key.split('->')
			key = temp[0]
			file = temp[1]
		link = '/usr/bin/' + file
		file = self.path + file
		return [key, file, link]

class File(object):

	def write(self, file, content):
		handle = open(file, 'w')
		handle.write(content)
		handle.close()
		popen('chmod +x ' + file)

	def read(self, path, name):
		handle = open(path + name, 'r')
		content = handle.read()
		handle.close()
		return content

	def path(self):
		return os.path.split(os.path.realpath(__file__))[0] + '/'

	def exists(self, name):
		return os.path.exists(name)

	def rename(self, old, new):
		return os.rename(old, new)

	def remove(self, file):
		return os.remove(file)

	def mkdir(self, path):
		if self.exists(path) == False:
			os.mkdir(path)

def shell(command, sub=False, bg=False):
	global PATH
	shell = PATH + 'shell/' + command.replace('.', '/', 1)
	return popen(shell, sub, bg)

def popen(command, sub=False, bg=False):
	string = command
	if bg == True:
		command = command + ' 1>/dev/null 2>&1 &'
	if sub == False:
		process = os.popen(command)
		output = process.read()
		process.close()
		return output
	else:
		popen  = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
		output = ''
		print string
		while True:
			output = popen.stdout.readline()
			print output
			if popen.poll() is not None:
				break
		return output

def install(soft):
	print 'install ' + soft + '...'
	if soft == 'docker':
		shell('install.docker', True)
	else:
		shell('install.package ' + soft, True)

def isset(v): 
	try : 
		type(eval(v))
	except : 
		return  0 
	else : 
		return  1

def check(soft):
	result = int(popen('which '+soft+' | wc -l'))
	if result != 0:
		return 1
	else:
		install(soft)
		return 0

def replace(old, new, string):
	if old in string:
		string = string.replace(old, new)
	return string

Dever()