def repo_init(self, id): if id: #获取当前id项的业务ip与端口 Dev_info = DeviceInfo.objects.filter(id=int(id)).first() bus_port = Dev_info.businesses.all()[0].businesses_port host_ip = Dev_info.hostname # 扫描业务端口 localhost = Shell('127.0.0.1') #command = 'cd %s && git rev-parse --is-inside-work-tree' % (repo[0]['alias']) command = 'nmap -sT -Pn ' + str(host_ip) + ' -p ' + str( bus_port) + '|grep tcp|awk -F " " \'{print $2}\'' #command = 'ip a|grep eth0|grep inet' result = localhost.local(command) #result = localhost.run(command) #info_logger.error("执行命令结果result...{}".format(result)) if result.exited != 0: info_logger.error("查询业务端口状态信息失败...") elif result.stdout.strip() == 'open': info_logger.info("process is running...{}".format( result.stdout)) elif result.stdout.strip() != 'open': info_logger.info("process is NOT running...{}".format( result.stdout)) localhost.close() return result
def do_rollback(self, id, log, record_id): ''' 回滚 ''' sequence = 1 with open(log, 'a') as f: f.write('[INFO]------正在执行回滚[%s]------\n' % (sequence)) record = DeployRecord.objects.filter(id=int(id)).values() server_ids = record[0]['server_ids'].split(',') name = '回滚_' + str(id) + '_' + record_id for sid in server_ids: try: auth_info, auth_key = auth_init(sid) connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) version_file = '%s/%s' % (record[0]['target_root'], record[0]['alias'] + '_version.txt') # 判断回滚版本是否存在 command = '[ -d %s/%s ] || echo "false"' % (record[0]['target_releases'], record[0]['prev_record']) self.result = connect.run(command, write=log) if not self.result.stdout.strip() == 'false': # 删除目标软链 command = 'find %s -type l -delete' % (record[0]['target_root']) self.result = connect.run(command, write=log) # 创建需回滚版本软链到webroot command = 'ln -sfn %s/%s/* %s' % ( record[0]['target_releases'], record[0]['prev_record'], record[0]['target_root']) if self.result.exited == 0: self.result = connect.run(command, write=log) command = 'echo %s > %s' % (record[0]['prev_record'], version_file) if self.result.exited == 0: self.result = connect.run(command, write=log) connect.close() except Exception as e: error_logger.error(e) # 处理完记录入库 defaults = { 'name': name, 'record_id': record[0]['prev_record'], 'alias': record[0]['alias'], 'project_id': record[0]['project_id'], 'server_ids': record[0]['server_ids'], 'target_root': record[0]['target_root'], 'target_releases': record[0]['target_releases'], 'prev_record': record[0]['record_id'], 'is_rollback':True, 'status': 'Succeed' } if self.result.exited != 0 or self.result.stdout.strip() == 'false': defaults['status'] = 'Failed' defaults['is_rollback'] = False DeployRecord.objects.create(**defaults) Project.objects.filter(id=record[0]['project_id']).update(last_task_status='Failed') with open(log, 'a') as f: f.write('[ERROR]------回滚失败------\n') f.write('[ERROR]回滚的版本文件可能已删除!\n') else: DeployRecord.objects.create(**defaults) Project.objects.filter(id=record[0]['project_id']).update(last_task_status='Succeed') sequence = 6 with open(log, 'a') as f: f.write('[INFO]------回滚完成,请重启应用[%s]------\n' % (sequence))
def post(self, request, format=None): if request.data['excu'] == 'list': try: app_log_path = request.data['app_log_path'] host = request.data['host'] auth_info, auth_key = auth_init(host) connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) commands = "find %s -name '*.log'" % (app_log_path) result = connect.run(commands).stdout res = [] for i in result.split(): res.append(i) res = filter(None, res) connect.close() http_status = OK except Exception as e: http_status = BAD res = '执行错误:' + str(e) return XopsResponse(res, status=http_status) elif request.data['excu'] == 'filedown': file_path = request.data['file_path'] host = request.data['host'] file_name = os.path.basename(file_path) dir_name = os.path.dirname(file_path) old_file_name = os.path.splitext(file_name) if old_file_name[1] == '.log': new_file_name = old_file_name[0] + '.tar.gz' auth_info, auth_key = auth_init(host) connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) commands = 'mkdir -p /tmp/remote/ && tar czf /tmp/remote/%s -C %s %s' % ( new_file_name, dir_name, file_name) connect.run(commands) connect.get('/tmp/remote/' + new_file_name, '/tmp/' + new_file_name) response = FileResponse(open('/tmp/' + new_file_name, 'rb')) response['Content-Type'] = 'application/octet-stream' response[ 'Content-Disposition'] = 'attachment;filename="%s"' % old_file_name[ 0] commands = 'rm -f /tmp/remote/%s' % (new_file_name) connect.run(commands) connect.close() os.remove('/tmp/' + new_file_name) return response else: return XopsResponse('请求文件格式不对!', status=BAD)
def repo_init(self, id): if id: repo = Project.objects.filter(id=int(id)).values('alias', 'repo_url') path = self._path.rstrip('/') + '/' + str(id) + '_' + str(repo[0]['alias']) if not os.path.exists(path): os.makedirs(path) if not os.path.exists(path + '/logs'): os.makedirs(path + '/logs') localhost = Shell('127.0.0.1') command = 'cd %s && git rev-parse --is-inside-work-tree' % (repo[0]['alias']) with localhost.cd(path): result = localhost.local(command, exception=False) if result.exited != 0: command = 'rm -rf %s' % (path + '/' + str(repo[0]['alias'])) localhost.local(command) with localhost.cd(path): command = 'git clone %s %s' % (repo[0]['repo_url'], repo[0]['alias']) result = localhost.local(command) massage = '正在克隆%s到%s ...' % (repo[0]['repo_url'], path) info_logger.info(massage) localhost.close() return result
def get(self, request, format=None): file_path = request.query_params['file'] host = request.query_params['sid'] file_name = os.path.basename(file_path) new_file_name = os.path.splitext(file_name)[0] + '.tar.gz' auth_info, auth_key = auth_init(host) connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) commands = 'mkdir -p /tmp/remote/ && tar czf /tmp/remote/%s %s' % ( new_file_name, file_path) connect.run(commands) connect.get('/tmp/remote/' + new_file_name, '/tmp/' + new_file_name) response = FileResponse(open('/tmp/' + new_file_name, 'rb')) response['Content-Type'] = 'application/octet-stream' response[ 'Content-Disposition'] = 'attachment;filename="%s"' % new_file_name commands = 'rm -f /tmp/remote/%s' % (new_file_name) connect.run(commands) connect.close() os.remove('/tmp/' + new_file_name) return response
def scan_execution(): scan_settings_load = ScanSettingsLoad() start_time = time.time() auth_type = scan_settings_load.get_conf_content('hosts', 'auth_type') hosts = scan_settings_load.os_scan() login_hosts = [] for host in hosts: if host['open'] == 'open': login_hosts.append(host) elif host['os'] == 'unknown': DeviceScanInfo.objects.update_or_create(hostname=host['host'], defaults={ 'os_version': host['os'], 'os_type': 'Other' }) if login_hosts: # 从配置文件中获取登陆认证信息和认证类型 for host in login_hosts: kwargs = { 'hostname': host['host'], 'username': scan_settings_load.get_conf_content('hosts', 'ssh_username'), 'port': scan_settings_load.get_conf_content('hosts', 'ssh_port'), 'commands': scan_settings_load.get_conf_content('hosts', 'commands') } if auth_type == 'password': kwargs['password'] = scan_settings_load.get_conf_content( 'hosts', 'ssh_password') else: kwargs['password'] = scan_settings_load.get_conf_content( 'hosts', 'ssh_private_key') # 将登陆执行结果数据复制给defaults kwargs['auth_type'] = auth_type kwargs['os_type'] = 'Linux' # 构建连接信息 auth_info = '{user}@{host}:{port}'.format(user=kwargs['username'], host=kwargs['hostname'], port=kwargs['port']) auth_key = {auth_type: kwargs['password']} connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) commands = kwargs['commands'] kwargs['error_message'] = [] for key, value in commands.items(): result = connect.run(value) if hasattr(result, 'stdout'): if result.failed: kwargs['status'] = 'Failed' kwargs['error_message'].append( str(result.stderr) + '\n') else: kwargs[key] = result.stdout kwargs['status'] = 'Succeed' kwargs['error_message'] = '' continue else: kwargs['status'] = 'Failed' kwargs['error_message'] = str(result) break connect.close() defaults = kwargs defaults.pop('commands') DeviceScanInfo.objects.update_or_create(hostname=host['host'], defaults=defaults) end_time = time.time() msg = '扫描任务已完成, 执行时间:%(time)s秒, 共%(num)s台主机.' % { 'time': end_time - start_time, 'num': len(hosts) } info_logger.info(msg)
class DeployExcu(object): _path = settings.WORKSPACE sequence = 0 release_version = None prev_release_version = None result = None file = None start_time = None def __init__(self, webuser, record_id, id=None): self.localhost = Shell('127.0.0.1') if id: project = Project.objects.filter(id=int(id)).values() self.project_id = project[0]['id'] self.alias = str(project[0]['alias']) self.environment = str(project[0]['environment']) self.repo_url = str(project[0]['repo_url']) self.local_code_path = self._path + str(id) + '_' + str(project[0]['alias']) + '/' + str( project[0]['alias']) self.local_project_path = self._path + str(id) + '_' + str(project[0]['alias']) self.local_log_path = self._path + str(id) + '_' + str(project[0]['alias']) + '/logs' self.is_include = project[0]['is_include'] self.excludes = project[0]['excludes'] self.task_envs = project[0]['task_envs'] self.prev_deploy = project[0]['prev_deploy'] self.post_deploy = project[0]['post_deploy'] self.prev_release = project[0]['prev_release'] self.post_release = project[0]['post_release'] self.target_root = project[0]['target_root'] self.target_releases = project[0]['target_releases'] self.version_num = project[0]['version_num'] self.custom_global_env = { 'WEB_ROOT': str(self.target_root), 'CODE_ROOT': str(self.local_code_path), 'ALIAS': str(self.alias), 'START_TIME': str(self.start_time) } if project[0]['task_envs']: task_envs = [i.strip() for i in project[0]['task_envs'].split('\n') if i.strip() and not i.strip().startswith('#')] for var in task_envs: var_list = var.split('=', 1) if len(var_list) != 2: continue self.custom_global_env[var_list[0]] = var_list[1] self.localhost.init_env(env=self.custom_global_env) self.webuser = webuser self.record_id = record_id def do_prev_deploy(self, log): ''' 代码检出前要做的基础工作 ''' self.sequence = 1 with open(log, 'a') as f: f.write('[INFO]------正在执行代码检出前的工作[%s]------\n' % (self.sequence)) commands = self.prev_deploy if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with self.localhost.cd(self.local_code_path): self.result = self.localhost.local(command, write=log) def do_checkout(self, version, log): ''' 检出代码 ''' if self.result.exited == 0: self.sequence = 2 with open(log, 'a') as f: f.write('[INFO]------正在执行代码检出[%s]------\n' % (self.sequence)) # 更新到指定 commit with self.localhost.cd(self.local_code_path): self.result = self.localhost.local('git fetch --all', write=log) if self.environment == 'tag': command = 'git rev-parse %s' % (version) else: command = 'git rev-parse refs/remotes/origin/%s' % (version) commit_id = self.localhost.local(command, write=log).stdout.strip() command = 'git checkout -f %s' % (commit_id) if self.result.exited == 0: self.result = self.localhost.local(command, write=log) command = 'git show --stat' if self.result.exited == 0: self.result = self.localhost.local(command, write=log) def do_post_deploy(self, log): ''' 检出代码后的工作:如编译 ''' if self.result.exited == 0: self.sequence = 3 with open(log, 'a') as f: f.write('[INFO]------正在执行代码检出后的工作[%s]------\n' % (self.sequence)) commands = self.post_deploy if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with self.localhost.cd(self.local_code_path): if self.result.exited == 0: self.result = self.localhost.local(command, write=log) # 打包编译后的文件:包含或排除 self.release_version = self.record_id with self.localhost.cd(self.local_code_path): if self.is_include: files = includes_format(self.local_code_path, self.excludes) for file in files: dirname = file[0] filename = '.' if file[1] == '*' else file[1] tar_name = self.local_project_path.rstrip('/') + '/' + self.release_version + '.tar' tar_params = 'tar rf' if os.path.exists(tar_name) else 'tar cf' if dirname: command = '%s %s -C %s %s' % (tar_params, tar_name, dirname, filename) if self.result.exited == 0: self.result = self.localhost.local(command, write=log) else: command = '%s %s %s' % (tar_params, tar_name, filename) if self.result.exited == 0: self.result = self.localhost.local(command, write=log) else: files = excludes_format(self.local_code_path, self.excludes) command = 'tar cf ../%s %s' % (self.release_version + '.tar', files) if self.result.exited == 0: self.result = self.localhost.local(command, write=log) def do_prev_release(self, log, connect): ''' 部署代码到目标机器前执行 ''' if self.result.exited == 0: self.sequence = 4 with open(log, 'a') as f: f.write('[INFO]------正在执行部署前的工作[%s]------\n' % (self.sequence)) target_release_version = "%s/%s" % (self.target_releases, self.release_version) # 创建远程target_releases目录 command = '[ -d %s ] || mkdir -p %s' % (target_release_version, target_release_version) if self.result.exited == 0: self.result = connect.run(command, write=log) # 上传压缩包 self.file = '%s/%s' % (self.local_project_path.rstrip('/'), self.release_version + '.tar') if self.result.exited == 0: with open(log, 'a') as f: f.write('[INFO]------正在上传压缩包至远程服务器------\n') self.result = connect.put(self.file, remote=target_release_version, write=log) # 判断是否超过可存档的数量 with connect.cd(self.target_releases): command = 'ls -l |grep "^d"|wc -l' if self.result.remote: self.result = connect.run(command, write=log) releases_num = int(self.result.stdout.strip()) if releases_num >= self.version_num: command = "ls -t |sort -t '_' -k 2 |head -1" if self.result.exited == 0: self.result = connect.run(command, write=log) last_record_id = self.result.stdout.strip() command = 'rm -rf %s/%s' % (self.target_releases, last_record_id) if self.result.exited == 0: self.result = connect.run(command, write=log) DeployRecord.objects.filter(record_id=last_record_id).update(is_rollback=False) # 解压并删除压缩源 with connect.cd(target_release_version): command = 'tar xf %s && rm -f %s' % \ (self.release_version + '.tar',self.release_version + '.tar') if self.result.exited == 0: self.result = connect.run(command, write=log) # 执行自定义命令 commands = self.prev_release if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with connect.cd(target_release_version): if self.result.exited == 0: self.result = connect.run(command, write=log) def do_release(self, log, connect): ''' 执行部署到目标机器:生成软链等 ''' if self.result.exited == 0: self.sequence = 5 with open(log, 'a') as f: f.write('[INFO]------正在执行部署工作[%s]------\n' % (self.sequence)) # 创建远程target_root目录 command = '[ -d %s ] || mkdir -p %s' % (self.target_root, self.target_root) if self.result.exited == 0: self.result = connect.run(command, write=log) # 检查上次的版本 with connect.cd(self.target_root): version_file = '%s/%s' % (self.target_root, self.alias + '_version.txt') command = 'touch %s && cat %s' % (version_file, version_file) if self.result.exited == 0: self.result = connect.run(command, write=log) self.prev_release_version = self.result.stdout # 如果存在旧版本,则删除软链 if self.prev_release_version: command = 'find %s -type l -delete' % (self.target_root) if self.result.exited == 0: self.result = connect.run(command, write=log) # 创建当前版本软链到webroot command = 'ln -sfn %s/%s/* %s && echo %s > %s' % (self.target_releases, self.release_version, self.target_root, self.release_version, version_file) if self.result.exited == 0: self.result = connect.run(command, write=log) def do_post_release(self, log, connect): ''' 部署代码到目标机器后执行 ''' if self.result.exited == 0: self.sequence = 6 with open(log, 'a') as f: f.write('[INFO]------正在执行部署后的工作[%s]------\n' % (self.sequence)) commands = self.post_release if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with connect.cd(self.target_root): if self.result.exited == 0: self.result = connect.run(command, write=log) connect.close() def end(self, server_ids, record_id): if self.localhost: # 删除打包的源文件 self.localhost.local('rm -f %s' % (self.file)) # 关闭连接 self.localhost.close() # 关闭死循环读取本地日志 gl.set_value('deploy_' + str(self.webuser), True) sid = ','.join(server_ids) defaults = { 'record_id': record_id, 'alias': self.alias, 'server_ids': sid, 'target_root': self.target_root, 'target_releases': self.target_releases, 'prev_record': self.prev_release_version.strip(), 'is_rollback': True, 'status': 'Succeed' } name = '部署_' + record_id if self.result.exited == 0: DeployRecord.objects.filter(name=name).update(**defaults) Project.objects.filter(id=self.project_id).update(last_task_status='Succeed') else: defaults['status'] = 'Failed' defaults['is_rollback'] = False DeployRecord.objects.filter(name=name).update(**defaults) Project.objects.filter(id=self.project_id).update(last_task_status='Failed') @async def start(self, log, version, serverid, record_id, webuser, start_time): self.start_time = start_time try: self.do_prev_deploy(log) self.do_checkout(version, log) self.do_post_deploy(log) for sid in serverid: if sid: auth_info, auth_key = auth_init(sid) if auth_info and auth_key: connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) self.do_prev_release(log, connect) self.do_release(log, connect) self.do_post_release(log, connect) else: Tailf.send_message(webuser, '[ERROR]服务器ID%s已被删除,部署继续执行!' % sid) else: Tailf.send_message(webuser, '没有选择远程服务器!!!') self.end(serverid, record_id) except Exception as e: Tailf.send_message(webuser, str(e))
def post(self, request, format=None): if request.data['excu'] == 'init': # 项目初始化 id = request.data['id'] result = self.repo_init(id) if result.exited == 0: Project.objects.filter(id=id).update(status='Succeed') info_logger.info('初始化项目:' + str(id) + ',执行成功!') http_status = OK msg = '初始化成功!' else: error_logger.error('初始化项目:%s 执行失败! 错误信息:%s' % (str(id), result.stderr)) http_status = BAD msg = '初始化项目:%s 执行失败! 错误信息:%s' % (str(id), result.stderr) return XopsResponse(msg, status=http_status) elif request.data['excu'] == 'deploy': # 部署操作 id = request.data['id'] webuser = request.user.username alias = request.data['alias'] self.start_time = time.strftime("%Y%m%d%H%M%S", time.localtime()) record_id = str(alias) + '_' + str(self.start_time) name = '部署_' + record_id DeployRecord.objects.create(name=name, alias=alias, status='Failed', project_id=int(id)) Project.objects.filter(id=id).update(last_task_status='Failed') local_log_path = self._path.rstrip('/') + '/' + str( id) + '_' + str(request.data['alias']) + '/logs' log = local_log_path + '/' + record_id + '.log' version = request.data['version'].strip() serverid = request.data['server_ids'] # 调用celery异步任务 deploy.delay(id, log, version, serverid, record_id, webuser, self.start_time) return XopsResponse(record_id) elif request.data['excu'] == 'rollback': # 回滚 id = request.data['id'] project_id = request.data['project_id'] alias = request.data['alias'] self.start_time = time.strftime("%Y%m%d%H%M%S", time.localtime()) record_id = str(alias) + '_' + str(self.start_time) log = self._path.rstrip('/') + '/' + str(project_id) + '_' + str( alias) + '/logs/' + record_id + '.log' self.do_rollback(id, log, record_id) return XopsResponse(record_id) elif request.data['excu'] == 'deploymsg': # 部署控制台消息读取 try: id = request.data['id'] alias = request.data['alias'] record = request.data['record'] scenario = int(request.data['scenario']) logfile = self._path.rstrip('/') + '/' + str(id) + '_' + str( alias) + '/logs/' + record + '.log' webuser = request.user.username if scenario == 0: local_tailf.delay(logfile, webuser, id) http_status = OK request_status = '执行成功!' except Exception as e: http_status = BAD request_status = str(e) return XopsResponse(request_status, status=http_status) elif request.data['excu'] == 'readlog' and request.data[ 'scenario'] == 1: # 读取部署日志 try: id = request.data['id'] alias = request.data['alias'] record = request.data['record'] logfile = self._path.rstrip('/') + '/' + str(id) + '_' + str( alias) + '/logs/' + record + '.log' response = FileResponse(open(logfile, 'rb')) response['Content-Type'] = 'text/plain' return response except Exception: http_status = BAD request_status = '执行错误:文件不存在!' return XopsResponse(request_status, status=http_status) elif request.data['excu'] == 'app_start': # 项目启动 try: app_start = request.data['app_start'] host = request.data['host'] webuser = request.user.username auth_info, auth_key = auth_init(host) connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) app_start = app_start.strip().replace('&&', '').replace('||', '') connect.run(app_start, ws=True, webuser=webuser) connect.close() http_status = OK request_status = '执行成功!' except Exception as e: http_status = BAD request_status = '执行错误:' + str(e) return XopsResponse(request_status, status=http_status) elif request.data['excu'] == 'app_stop': # 项目停止 try: app_stop = request.data['app_stop'] host = request.data['host'] webuser = request.user.username auth_info, auth_key = auth_init(host) connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) app_stop = app_stop.strip().replace('&&', '').replace('||', '') connect.run(app_stop, ws=True, webuser=webuser) connect.close() http_status = OK request_status = '执行成功!' except Exception as e: http_status = BAD request_status = '执行错误:' + str(e) return XopsResponse(request_status, status=http_status) elif request.data['excu'] == 'tail_start': # 日志监控 try: filter_text = str(request.data['filter']) app_log_file = request.data['app_log_file'] host = request.data['host'] webuser = request.user.username device_info = DeviceInfo.objects.filter(id=int(host)).values() host = device_info[0]['hostname'] auth_type = device_info[0]['auth_type'] connect_info = ConnectionInfo.objects.filter( hostname=host, auth_type=auth_type).values() user = connect_info[0]['username'] passwd = connect_info[0]['password'] port = connect_info[0]['port'] tail = Tailf() tail.remote_tail(host, port, user, passwd, app_log_file, webuser, filter_text=filter_text) http_status = OK request_status = '执行成功!' except Exception as e: http_status = BAD request_status = str(e) return XopsResponse(request_status, status=http_status) elif request.data['excu'] == 'tail_stop': # 日志监控停止 try: webuser = request.user.username redis = RedisObj() redis.set('remote_tail_' + str(webuser), '1') http_status = OK request_status = '执行成功!' except Exception as e: http_status = BAD request_status = str(e) return XopsResponse(request_status, status=http_status)
class DeployExcu(Task): _path = settings.WORKSPACE sequence = 0 release_version = None prev_release_version = None result = None file = None def deploy_init(self, webuser, record_id, id=None): self.localhost = Shell('127.0.0.1') if id: project = Project.objects.filter(id=int(id)).values() self.project_id = project[0]['id'] self.alias = str(project[0]['alias']) self.repo_url = str(project[0]['repo_url']) self.local_code_path = self._path + str(id) + '_' + str( project[0]['alias']) + '/' + str(project[0]['alias']) self.local_project_path = self._path + str(id) + '_' + str( project[0]['alias']) self.local_log_path = self._path + str(id) + '_' + str( project[0]['alias']) + '/logs' self.is_include = project[0]['is_include'] self.excludes = project[0]['excludes'] self.task_envs = project[0]['task_envs'] self.prev_deploy = project[0]['prev_deploy'] self.post_deploy = project[0]['post_deploy'] self.prev_release = project[0]['prev_release'] self.post_release = project[0]['post_release'] self.target_root = project[0]['target_root'] self.target_releases = project[0]['target_releases'] self.version_num = project[0]['version_num'] self.custom_global_env = { 'WEB_ROOT': str(self.target_root), 'CODE_ROOT': str(self.local_code_path), 'ALIAS': str(self.alias) } if project[0]['task_envs']: task_envs = [ i.strip() for i in project[0]['task_envs'].split('\n') if i.strip() and not i.strip().startswith('#') ] for var in task_envs: var_list = var.split('=', 1) if len(var_list) != 2: continue self.custom_global_env[var_list[0]] = var_list[1] self.localhost.init_env(env=self.custom_global_env) self.webuser = webuser self.record_id = record_id def do_prev_deploy(self, log): ''' 代码检出前要做的基础工作 ''' self.sequence = 1 with open(log, 'a') as f: f.write('[INFO]------正在执行代码检出前的工作------%s\n' % (self.sequence)) commands = self.prev_deploy if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with self.localhost.cd(self.local_code_path): self.result = self.localhost.local(command, write=log) def do_checkout(self, version, log): ''' 检出代码 ''' if self.result.exited == 0: self.sequence = 2 with open(log, 'a') as f: f.write('[INFO]------正在执行代码检出------%s\n' % (self.sequence)) # 更新到指定 commit with self.localhost.cd(self.local_code_path): self.result = self.localhost.local('git fetch --all', write=log) command = 'git rev-parse %s' % (version) commit_id = self.localhost.local(command, write=log).stdout.strip() command = 'git checkout -f %s' % (commit_id) if self.result.exited == 0: self.result = self.localhost.local(command, write=log) def do_post_deploy(self, log): ''' 检出代码后的工作:如编译 ''' if self.result.exited == 0: self.sequence = 3 with open(log, 'a') as f: f.write('[INFO]------正在执行代码检出后的工作------%s\n' % (self.sequence)) commands = self.post_deploy if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with self.localhost.cd(self.local_code_path): if self.result.exited == 0: self.result = self.localhost.local(command, write=log) # 打包编译后的文件:包含或排除 self.release_version = self.record_id with self.localhost.cd(self.local_code_path): if self.is_include: files = includes_format(self.local_code_path, self.excludes) command = 'tar zcf %s/%s %s' % ( self.local_project_path.rstrip('/'), self.release_version + '.tar.gz', files) else: files = excludes_format(self.local_code_path, self.excludes) command = 'tar zcf ../%s %s' % (self.release_version + '.tar.gz', files) if self.result.exited == 0: self.result = self.localhost.local(command, write=log) def do_prev_release(self, log, connect): ''' 部署代码到目标机器前执行 ''' if self.result.exited == 0: self.sequence = 4 with open(log, 'a') as f: f.write('[INFO]------正在执行部署前的工作------%s\n' % (self.sequence)) # 创建远程target_releases目录 command = '[ -d %s ] || mkdir -p %s' % (self.target_releases, self.target_releases) if self.result.exited == 0: self.result = connect.run(command, write=log) # 上传压缩包 self.file = '%s/%s' % (self.local_project_path.rstrip('/'), self.release_version + '.tar.gz') if self.result.exited == 0: connect.put(self.file, remote=self.target_releases, write=log) # 判断是否超过可存档的数量 with connect.cd(self.target_releases): command = 'ls -l |grep "^d"|wc -l' if self.result.exited == 0: self.result = connect.run(command, write=log) releases_num = int(self.result.stdout.strip()) if releases_num >= self.version_num: last_data = DeployRecord.objects.filter( project_id=self.project_id, name__contains='部署', status='Succeed', is_rollback=True).order_by( '-id')[:self.version_num].values() last_record_id = last_data[self.version_num - 1]['record_id'] command = 'rm -rf %s/%s' % (self.target_releases, last_record_id) if self.result.exited == 0: self.result = connect.run(command, write=log) DeployRecord.objects.filter( id=last_data[self.version_num - 1]['id']).update(is_rollback=False) # 解压并删除压缩源 with connect.cd(self.target_releases): command = 'mkdir %s && tar zxf %s -C %s && rm -f %s' % \ (self.release_version, self.release_version + '.tar.gz', self.release_version, self.release_version + '.tar.gz') if self.result.exited == 0: self.result = connect.run(command, write=log) # 执行自定义命令 commands = self.prev_release if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue target_release_version = "%s/%s" % (self.target_releases, self.release_version) with connect.cd(target_release_version): if self.result.exited == 0: self.result = connect.run(command, write=log) def do_release(self, log, connect): ''' 执行部署到目标机器:生成软链等 ''' if self.result.exited == 0: self.sequence = 5 with open(log, 'a') as f: f.write('[INFO]------正在执行部署工作------%s\n' % (self.sequence)) # 创建远程target_root目录 command = '[ -d %s ] || mkdir -p %s' % (self.target_root, self.target_root) if self.result.exited == 0: self.result = connect.run(command, write=log) # 检查上次的版本 with connect.cd(self.target_releases): version_file = '%s/%s' % (self.target_root, self.alias + '_version.txt') command = 'touch %s && cat %s' % (version_file, version_file) if self.result.exited == 0: self.result = connect.run(command, write=log) self.prev_release_version = self.result.stdout # 如果存在旧版本,则删除软链 if self.prev_release_version: command = 'find %s -type l -delete' % (self.target_root) if self.result.exited == 0: self.result = connect.run(command, write=log) # 创建当前版本软链到webroot command = 'ln -sfn %s/%s/* %s && echo %s > %s' % ( self.target_releases, self.release_version, self.target_root, self.release_version, version_file) if self.result.exited == 0: self.result = connect.run(command, write=log) def do_post_release(self, log, connect): ''' 部署代码到目标机器后执行 ''' if self.result.exited == 0: self.sequence = 6 with open(log, 'a') as f: f.write('[INFO]------正在执行部署后的工作------%s\n' % (self.sequence)) commands = self.post_release if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with connect.cd(self.target_root): pty = False if command.find('nohup') >= 0 else True if self.result.exited == 0: self.result = connect.run(command, pty=pty, write=log) connect.close() def end(self, server_ids, record_id): if self.localhost: # 删除打包的源文件 self.localhost.local('rm -f %s' % (self.file)) # 关闭连接 self.localhost.close() # 关闭死循环读取本地日志 gl.set_value('deploy_' + str(self.webuser), True) sid = ','.join(server_ids) defaults = { 'record_id': record_id, 'alias': self.alias, 'server_ids': sid, 'target_root': self.target_root, 'target_releases': self.target_releases, 'prev_record': self.prev_release_version.strip(), 'is_rollback': True, 'status': 'Succeed' } name = '部署_' + record_id if self.result.exited == 0: DeployRecord.objects.filter(name=name).update(**defaults) else: defaults['status'] = 'Failed' defaults['is_rollback'] = False DeployRecord.objects.filter(name=name).update(**defaults) def start(self, log, version, serverid, record_id, webuser): try: self.do_prev_deploy(log) self.do_checkout(version, log) self.do_post_deploy(log) for sid in serverid: try: device_info = DeviceInfo.objects.filter( id=int(sid)).values() host = device_info[0]['hostname'] auth_type = device_info[0]['auth_type'] connect_info = ConnectionInfo.objects.filter( hostname=host, auth_type=auth_type).values() user = connect_info[0]['username'] passwd = connect_info[0]['password'] port = connect_info[0]['port'] auth_info = '{user}@{host}:{port}'.format(user=user, host=host, port=port) auth_key = {auth_type: passwd} connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) self.do_prev_release(log, connect) self.do_release(log, connect) self.do_post_release(log, connect) except Exception as e: send = Tailf() send.send_message(webuser, str(e)) self.end(serverid, record_id) except Exception as e: send = Tailf() send.send_message(webuser, str(e)) def run(self, id, log, version, serverid, record_id, webuser): self.deploy_init(webuser, record_id, id) self.start(log, version, serverid, record_id, webuser)
def post(self, request, format=None): if request.data['excu'] == 'init': # 项目初始化 id = request.data['id'] result = self.repo_init(id) if result.exited == 0: Project.objects.filter(id=id).update(status='Succeed') info_logger.info('初始化项目:' + str(id) + ',执行成功!') http_status = OK msg = '初始化成功!' else: error_logger.error('初始化项目:%s 执行失败! 错误信息:%s' % (str(id), result.stderr)) http_status = BAD msg = '初始化项目:%s 执行失败! 错误信息:%s' % (str(id), result.stderr) return XopsResponse(msg, status=http_status) elif request.data['excu'] == 'deploy': # 部署操作 id = request.data['id'] webuser = request.user.username alias = request.data['alias'] self.start_time = time.strftime("%Y%m%d%H%M%S", time.localtime()) record_id = str(alias) + '_' + str(self.start_time) name = '部署_' + record_id DeployRecord.objects.create(name=name, alias=alias, status='Failed', project_id=int(id)) Project.objects.filter(id=id).update(last_task_status='Failed') local_log_path = self._path.rstrip('/') + '/' + str( id) + '_' + str(request.data['alias']) + '/logs' log = local_log_path + '/' + record_id + '.log' version = request.data['version'].strip() serverid = request.data['server_ids'] deploy = DeployExcu(webuser, record_id, id) deploy.start(log, version, serverid, record_id, webuser) return XopsResponse(record_id) elif request.data['excu'] == 'rollback': # 回滚 id = request.data['id'] project_id = request.data['project_id'] alias = request.data['alias'] self.start_time = time.strftime("%Y%m%d%H%M%S", time.localtime()) record_id = str(alias) + '_' + str(self.start_time) log = self._path.rstrip('/') + '/' + str(project_id) + '_' + str( alias) + '/logs/' + record_id + '.log' self.do_rollback(id, log, record_id) return XopsResponse(record_id) elif request.data['excu'] == 'deploymsg': # 部署控制台消息读取 try: id = request.data['id'] alias = request.data['alias'] record = request.data['record'] scenario = int(request.data['scenario']) logfile = self._path.rstrip('/') + '/' + str(id) + '_' + str( alias) + '/logs/' + record + '.log' webuser = request.user.username msg = Tailf() if scenario == 0: msg.local_tail(logfile, webuser) else: msg.read_file(logfile, webuser) http_status = OK request_status = '执行成功!' except Exception: http_status = BAD request_status = '执行错误:文件不存在!' return XopsResponse(request_status, status=http_status) elif request.data['excu'] == 'app_start': # 项目启动 try: app_start = request.data['app_start'] host = request.data['host'] webuser = request.user.username auth_info, auth_key = auth_init(host) connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) app_start = app_start.strip().replace('&&', '').replace('||', '') commands = '/bin/bash -e %s' % (app_start) connect.run(commands, ws=True, webuser=webuser) connect.close() http_status = OK request_status = '执行成功!' except Exception as e: http_status = BAD request_status = '执行错误:' + str(e) return XopsResponse(request_status, status=http_status) elif request.data['excu'] == 'app_stop': # 项目停止 try: app_stop = request.data['app_stop'] host = request.data['host'] webuser = request.user.username auth_info, auth_key = auth_init(host) connect = Shell(auth_info, connect_timeout=5, connect_kwargs=auth_key) app_stop = app_stop.strip().replace('&&', '').replace('||', '') commands = '/bin/bash -e %s' % (app_stop) connect.run(commands, ws=True, webuser=webuser) connect.close() http_status = OK request_status = '执行成功!' except Exception as e: http_status = BAD request_status = '执行错误:' + str(e) return XopsResponse(request_status, status=http_status) elif request.data['excu'] == 'tail_start': # 日志监控 try: filter_text = str(request.data['filter']) app_log_file = request.data['app_log_file'] host = request.data['host'] webuser = request.user.username device_info = DeviceInfo.objects.filter(id=int(host)).values() host = device_info[0]['hostname'] auth_type = device_info[0]['auth_type'] connect_info = ConnectionInfo.objects.filter( hostname=host, auth_type=auth_type).values() user = connect_info[0]['username'] passwd = connect_info[0]['password'] port = connect_info[0]['port'] tail = Tailf() tail.remote_tail(host, port, user, passwd, app_log_file, webuser, filter_text=filter_text) http_status = OK request_status = '执行成功!' except Exception as e: http_status = BAD request_status = str(e) return XopsResponse(request_status, status=http_status) elif request.data['excu'] == 'tail_stop': # 日志监控停止 try: webuser = request.user.username if hasattr(gl, '_global_dict'): tail_key = 'tail_' + str(webuser) if tail_key in gl._global_dict.keys(): client = gl.get_value('tail_' + str(webuser)) client.close() http_status = OK request_status = '执行成功!' except Exception as e: http_status = BAD request_status = str(e) return XopsResponse(request_status, status=http_status)
class DeployExcu(Task): name = __name__ _path = settings.WORKSPACE sequence = 0 release_version = None prev_release_version = None result = None file = None start_time = None def init(self, webuser, record_id, id=None): self.localhost = Shell('127.0.0.1') if id: project = Project.objects.filter(id=int(id)).values() self.project_id = project[0]['id'] self.alias = str(project[0]['alias']) self.environment = str(project[0]['environment']) self.repo_url = str(project[0]['repo_url']) self.local_code_path = self._path + str(id) + '_' + str( project[0]['alias']) + '/' + str(project[0]['alias']) self.local_project_path = self._path + str(id) + '_' + str( project[0]['alias']) self.local_log_path = self._path + str(id) + '_' + str( project[0]['alias']) + '/logs' self.is_include = project[0]['is_include'] self.excludes = project[0]['excludes'] self.is_link = project[0]['is_link'] self.task_envs = project[0]['task_envs'] self.prev_deploy = project[0]['prev_deploy'] self.post_deploy = project[0]['post_deploy'] self.prev_release = project[0]['prev_release'] self.post_release = project[0]['post_release'] self.target_root = project[0]['target_root'] self.target_releases = project[0]['target_releases'] self.version_num = project[0]['version_num'] self.custom_global_env = { 'WEB_ROOT': str(self.target_root), 'CODE_ROOT': str(self.local_code_path), 'ALIAS': str(self.alias), 'START_TIME': str(self.start_time) } if project[0]['task_envs']: task_envs = [ i.strip() for i in project[0]['task_envs'].split('\n') if i.strip() and not i.strip().startswith('#') ] for var in task_envs: var_list = var.split('=', 1) if len(var_list) != 2: continue self.custom_global_env[var_list[0]] = var_list[1] self.localhost.init_env(env=self.custom_global_env) self.webuser = webuser self.record_id = record_id def do_prev_deploy(self, log): ''' 代码检出前要做的基础工作 ''' self.sequence = 1 with open(log, 'a') as f: f.write('[INFO]------正在执行代码检出前的工作[%s]------\n' % (self.sequence)) commands = self.prev_deploy if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with self.localhost.cd(self.local_code_path): self.result = self.localhost.local(command, write=log) def do_checkout(self, version, log): ''' 检出代码 ''' self.sequence = 2 with open(log, 'a') as f: f.write('[INFO]------正在执行代码检出[%s]------\n' % (self.sequence)) # 更新到指定 commit with self.localhost.cd(self.local_code_path): self.result = self.localhost.local( 'git checkout master && git pull', write=log) command = 'git rev-parse %s' % (version) commit_id = self.localhost.local(command, write=log).stdout.strip() command = 'git checkout -f %s' % (commit_id) if self.result.exited == 0: self.result = self.localhost.local(command, write=log) command = 'git show --stat' if self.result.exited == 0: self.result = self.localhost.local(command, write=log) def do_post_deploy(self, log): ''' 检出代码后的工作:如编译 ''' if self.result.exited == 0: self.sequence = 3 with open(log, 'a') as f: f.write('[INFO]------正在执行代码检出后的工作[%s]------\n' % (self.sequence)) commands = self.post_deploy if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with self.localhost.cd(self.local_code_path): if self.result.exited == 0: self.result = self.localhost.local(command, write=log) # 打包编译后的文件:包含或排除 if self.result.exited == 0: self.release_version = self.record_id with self.localhost.cd(self.local_code_path): if self.is_include: files = includes_format(self.local_code_path, self.excludes) for file in files: dirname = file[0] filename = '.' if file[1] == '*' else file[1] tar_name = self.local_project_path.rstrip( '/') + '/' + self.release_version + '.tar' tar_params = 'tar rf' if os.path.exists( tar_name) else 'tar cf' if dirname: command = '%s %s -C %s %s' % ( tar_params, tar_name, dirname, filename) if self.result.exited == 0: self.result = self.localhost.local( command, write=log) else: command = '%s %s %s' % (tar_params, tar_name, filename) if self.result.exited == 0: self.result = self.localhost.local( command, write=log) else: files = excludes_format(self.local_code_path, self.excludes) command = 'tar cf ../%s %s' % (self.release_version + '.tar', files) if self.result.exited == 0: self.result = self.localhost.local(command, write=log) def do_prev_release(self, log, connect): ''' 部署代码到目标机器前执行 ''' if self.result.exited == 0: self.sequence = 4 with open(log, 'a') as f: f.write('[INFO]------正在执行部署前的工作[%s]------\n' % (self.sequence)) target_release_version = "%s/%s" % (self.target_releases, self.release_version) # 创建远程target_releases目录 command = '[ -d %s ] || mkdir -p %s' % (target_release_version, target_release_version) if self.result.exited == 0: self.result = connect.run(command, write=log) # 上传压缩包 self.file = '%s/%s' % (self.local_project_path.rstrip('/'), self.release_version + '.tar') if self.result.exited == 0: with open(log, 'a') as f: f.write('[INFO]------正在上传压缩包至远程服务器------\n') self.result = connect.put(self.file, remote=target_release_version, write=log) # 删除打包的源文件 self.localhost.local('rm -f %s' % (self.file)) # 判断是否超过可存档的数量 with connect.cd(self.target_releases): command = 'ls -l |grep "^d"|wc -l' if self.result.remote: self.result = connect.run(command, write=log) releases_num = int(self.result.stdout.strip()) if releases_num >= self.version_num: command = "ls -t |sort -t '_' -k 2 |head -1" if self.result.exited == 0: self.result = connect.run(command, write=log) last_record_id = self.result.stdout.strip() command = 'rm -rf %s/%s' % (self.target_releases, last_record_id) if self.result.exited == 0: self.result = connect.run(command, write=log) DeployRecord.objects.filter( record_id=last_record_id).update(is_rollback=False) # 解压并删除压缩源 with connect.cd(target_release_version): command = 'tar xf %s && rm -f %s' % \ (self.release_version + '.tar', self.release_version + '.tar') if self.result.exited == 0: self.result = connect.run(command, write=log) # 执行自定义命令 commands = self.prev_release if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with connect.cd(target_release_version): if self.result.exited == 0: self.result = connect.run(command, write=log) def do_release(self, log, connect): ''' 执行部署到目标机器:生成软链等 ''' if self.result.exited == 0: self.sequence = 5 with open(log, 'a') as f: f.write('[INFO]------正在执行部署工作[%s]------\n' % (self.sequence)) # 创建远程target_root目录 command = '[ -d %s ] || mkdir -p %s' % (self.target_root, self.target_root) if self.result.exited == 0: self.result = connect.run(command, write=log) # 检查上次的版本 with connect.cd(self.target_root): version_file = '%s/%s' % (self.target_root, self.alias + '_version.txt') command = 'touch %s && cat %s' % (version_file, version_file) if self.result.exited == 0: self.result = connect.run(command, write=log) self.prev_release_version = self.result.stdout # 如果存在旧版本,则删除软链或删除文件 if self.prev_release_version: if self.is_link: command = 'find %s -type l -delete' % (self.target_root) if self.result.exited == 0: self.result = connect.run(command, write=log) # 创建当前版本软链到webroot command = 'ln -sfn %s/%s/* %s && echo %s > %s' % ( self.target_releases, self.release_version, self.target_root, self.release_version, version_file) if self.result.exited == 0: self.result = connect.run(command, write=log) else: command = 'rm -rf %s/*' % (self.target_root) if self.result.exited == 0: self.result = connect.run(command, write=log) # 复制文件到webroot command = 'cp -r %s/%s/* %s && echo %s > %s' % ( self.target_releases, self.release_version, self.target_root, self.release_version, version_file) if self.result.exited == 0: self.result = connect.run(command, write=log) else: if self.is_link: command = 'ln -sfn %s/%s/* %s && echo %s > %s' % ( self.target_releases, self.release_version, self.target_root, self.release_version, version_file) if self.result.exited == 0: self.result = connect.run(command, write=log) else: command = 'cp -r %s/%s/* %s && echo %s > %s' % ( self.target_releases, self.release_version, self.target_root, self.release_version, version_file) if self.result.exited == 0: self.result = connect.run(command, write=log) def do_post_release(self, log, connect): ''' 部署代码到目标机器后执行 ''' if self.result.exited == 0: self.sequence = 6 with open(log, 'a') as f: f.write('[INFO]------正在执行部署后的工作[%s]------\n' % (self.sequence)) target_release_version = "%s/%s" % (self.target_releases, self.release_version) # 执行自定义命令 commands = self.post_release if commands: for command in commands.split('\n'): if command.strip().startswith('#') or not command.strip(): continue with connect.cd(target_release_version): if self.result.exited == 0: self.result = connect.run(command, write=log) connect.close() def end(self, server_ids, record_id): sid = ','.join(server_ids) defaults = { 'record_id': record_id, 'alias': self.alias, 'server_ids': sid, 'target_root': self.target_root, 'target_releases': self.target_releases, 'prev_record': self.prev_release_version.strip(), 'is_rollback': True, 'status': 'Succeed' } name = '部署_' + record_id if self.result.exited == 0: DeployRecord.objects.filter(name=name).update(**defaults) Project.objects.filter(id=self.project_id).update( last_task_status='Succeed') else: defaults['status'] = 'Failed' defaults['is_rollback'] = False DeployRecord.objects.filter(name=name).update(**defaults) Project.objects.filter(id=self.project_id).update( last_task_status='Failed') def run(self, id, log, version, serverid, record_id, webuser, start_time): info_logger.info('[部署任务开始] 开始时间:%s 记录ID:%s 部署版本:%s 用户:%s 项目ID:%s' % (start_time, record_id, version, webuser, id)) redis = RedisObj() redis.set('deploy_' + str(webuser) + '_' + str(id), '0') self.init(webuser, record_id, id) self.start_time = start_time with open(log, 'a') as f: f.write('[INFO]版本: %s 执行用户: %s 开始时间: %s\n[INFO]本次部署日志路径: %s\n' % (version, webuser, start_time, log)) try: self.do_prev_deploy(log) self.do_checkout(version, log) self.do_post_deploy(log) for sid in serverid: try: connect = connect_init(sid) connect.init_env(env=self.custom_global_env) self.do_prev_release(log, connect) self.do_release(log, connect) self.do_post_release(log, connect) except Exception as e: time.sleep(5) Tailf.send_message(webuser, '[ERROR] 服务器为空或ID %s 可能已被删除!' % sid) Tailf.send_message(webuser, '[ERROR] 错误信息:' % e) error_logger.error( '[部署任务错误] 开始时间:%s 记录ID:%s 部署版本:%s 用户:%s 项目ID:%s 信息:%s' % (start_time, record_id, version, webuser, id, e)) self.end(serverid, record_id) info_logger.info('[部署任务已结束] 记录ID:%s 部署版本:%s 用户:%s 项目ID:%s' % (record_id, version, webuser, id)) except Exception as e: Tailf.send_message(webuser, '[ERROR] 错误信息: %s' % e) error_logger.error( '[部署任务错误] 开始时间:%s 记录ID:%s 部署版本:%s 用户:%s 项目ID:%s 信息:%s' % (start_time, record_id, version, webuser, id, e)) finally: if self.localhost: self.localhost.close() # 关闭local_tailf死循环 redis.set('deploy_' + str(webuser) + '_' + str(id), '1')