Ejemplo n.º 1
0
class AnsModuleConsumer(WebsocketConsumer):

    def __init__(self, *args, **kwargs):
        super(AnsModuleConsumer, self).__init__(*args, **kwargs)
        self.redis_instance = RedisOps(settings.REDIS_HOST, settings.REDIS_PORT, 4)
        self.ans_info = None

    def connect(self):
        self.accept()

    def receive(self, text_data=None, bytes_data=None):
        self.ans_info = json.loads(text_data)

        group_ids = self.ans_info['hostGroup']
        host_ids = self.ans_info['ans_group_hosts']
        selected_module_name = self.ans_info['ansibleModule']
        custom_model_name = self.ans_info.get('customModule', None)
        module_args = self.ans_info['ansibleModuleArgs']

        self.run_model(group_ids, host_ids, selected_module_name, custom_model_name, module_args)

    def disconnect(self, code):
        pass

    def run_model(self, group_ids, host_ids, selected_module_name, custom_model_name, module_args):
        gen_resource = GenResource()

        if group_ids == ['custom'] or group_ids == ['all']:
            resource = gen_resource.gen_host_list(host_ids)
        else:
            resource = gen_resource.gen_group_dict(group_ids)

        host_list = [ServerAssets.objects.get(id=host_id).assets.asset_management_ip for host_id in host_ids]

        module_name = selected_module_name if selected_module_name != 'custom' else custom_model_name

        unique_key = '{}.{}.{}'.format(host_ids, module_name, module_args)

        if self.redis_instance.get(unique_key):
            self.send('<code style="color: #FF0000">\n有相同的任务正在进行!请稍后重试!\n</code>', close=True)
        else:
            try:
                self.redis_instance.set(unique_key, 1)
                ans = ANSRunner(resource, become='yes', become_method='sudo', become_user='******', sock=self)
                ans.run_module(host_list=host_list, module_name=module_name, module_args=module_args)

                module_record.delay(ans_user=UserProfile.objects.get(id=self.ans_info['run_user']),
                                    ans_remote_ip=self.ans_info['remote_ip'],
                                    ans_module=module_name,
                                    ans_args=module_args,
                                    ans_server=host_list, ans_result=ans.get_module_results)
            except Exception as e:
                self.send('<code style="color: #FF0000">\nansible执行模块出错:{}\n</code>'.format(str(e)))
            finally:
                self.redis_instance.delete(unique_key)
                self.close()
Ejemplo n.º 2
0
class AnsPlaybookConsumer(WebsocketConsumer):
    def __init__(self, *args, **kwargs):
        super(AnsPlaybookConsumer, self).__init__(*args, **kwargs)
        self.redis_instance = RedisOps(settings.REDIS_HOST,
                                       settings.REDIS_PORT, 4)
        self.ans_info = None

    def connect(self):
        self.accept()

    def receive(self, text_data=None, bytes_data=None):
        self.ans_info = json.loads(text_data)

        group_ids = self.ans_info['group_ids']
        playbook_id = self.ans_info['playbook_id']

        self.run_playbook(group_ids, playbook_id)

    def disconnect(self, code):
        pass

    def run_playbook(self, group_ids, playbook_id):
        playbook = AnsiblePlaybook.objects.select_related('playbook_user').get(
            id=playbook_id)
        unique_key = '{}.{}'.format(playbook.playbook_name, group_ids)

        if self.redis_instance.get(unique_key):
            self.send(
                '<code style="color: #FF0000">\n有相同的任务正在进行!请稍后重试!\n</code>',
                close=True)
        else:
            try:
                self.redis_instance.set(unique_key, 1)
                resource = GenResource().gen_group_dict(group_ids)

                ans = ANSRunner(resource, sock=self)
                ans.run_playbook(playbook.playbook_file.path)

                playbook_record.delay(
                    playbook_user=UserProfile.objects.get(
                        id=self.ans_info['run_user']),
                    playbook_remote_ip=self.ans_info['remote_ip'],
                    playbook_name=playbook.playbook_name,
                    playbook_result=ans.get_playbook_results)
            except Exception as e:
                self.send(
                    '<code style="color: #FF0000">\nansible执行playbook出错:{}\n</code>'
                    .format(str(e)))
            finally:
                self.redis_instance.delete(unique_key)
                self.close()
Ejemplo n.º 3
0
def run_module(request):
    if request.method == 'POST':
        gen_resource = GenResource()
        redis_conn = RedisOps(settings.REDIS_HOST, settings.REDIS_PORT,
                              settings.REDIS_DB)
        remote_ip = request.META['REMOTE_ADDR']
        group_ids = request.POST.getlist('hostGroup')
        host_ids = request.POST.getlist('ans_group_hosts')
        if group_ids == ['custom'] or group_ids == ['all']:
            resource = gen_resource.gen_host_list(host_ids)
        else:
            resource = gen_resource.gen_group_dict(group_ids)

        host_list = [
            ServerAssets.objects.get(id=host_id).assets.asset_management_ip
            for host_id in host_ids
        ]
        selected_module_name = request.POST.get('ansibleModule')
        custom_model_name = request.POST.get('customModule')
        module_name = selected_module_name if selected_module_name != 'custom' else custom_model_name
        module_args = request.POST.get('ansibleModuleArgs')

        unique_key = '{}.{}.{}'.format(host_ids, module_name, module_args)

        if redis_conn.exists(unique_key):
            return JsonResponse({'msg': ['有相同的任务正在执行,请稍后再试'], 'code': 403})
        else:
            try:
                redis_conn.set(unique_key, 1)
                ans = ANSRunner(resource,
                                become='yes',
                                become_method='sudo',
                                become_user='******')
                ans.run_module(host_list=host_list,
                               module_name=module_name,
                               module_args=module_args)
                res = ans.get_model_result()

                return JsonResponse({'code': 200, 'msg': res})
            except Exception as e:
                return JsonResponse({
                    'code': 500,
                    'msg': ['任务执行失败:{}'.format(e)]
                })
            finally:
                redis_conn.delete(unique_key)
    inventory = AnsibleInventory.objects.prefetch_related('ans_group_hosts')
    hosts = ServerAssets.objects.select_related('assets')
    return render(request, 'task/run_module.html', locals())
Ejemplo n.º 4
0
class DeployConsumer(WebsocketConsumer):
    def __init__(self, *args, **kwargs):
        super(DeployConsumer, self).__init__(*args, **kwargs)
        self.redis_instance = RedisOps(settings.REDIS_HOST,
                                       settings.REDIS_PORT, 5)
        self.deploy_results = []
        self.config = None
        self.d_type = None
        self.release_name = None
        self.release_desc = None
        self.host_list = None
        self.branch_tag = None

    def connect(self):
        self.accept()

    def receive(self, text_data=None, bytes_data=None):
        info = json.loads(text_data)
        self.config = ProjectConfig.objects.select_related('project').get(
            id=info.get('config_id'))

        unique_key = self.config.project.project_name + self.config.project.project_env

        if self.redis_instance.get(unique_key):
            self.send('有相同的任务正在进行!请稍后重试!')
            self.close()
        else:
            self.redis_instance.set(unique_key, 1)
            timeline_header = '<li><i class="fa fa-flag bg-blue"></i><div class="timeline-item"><h3 class="timeline-header"><a href="javascript:void(0)">{}</a></h3><div class="timeline-body"></div></div></li>'
            cmd_detail = '<p style="font-style: italic; color: grey;">{}</p>'
            timeline_body_green = '<p style="color: #008000">{}</p>'
            timeline_body_red = '<p style="color: #FF0000">{}</p>'

            self.branch_tag = info.get('branch_tag')
            rollback = info.get('rollback', False)
            self.d_type = 'rollback' if rollback else 'deploy'
            commit = info.get('commit', None)
            self.release_name = commit if commit else self.branch_tag

            # 初始化ansible
            server_objs = self.config.deploy_server.all()
            host_ids = [server.id for server in server_objs]
            resource = gen_resource.GenResource().gen_host_list(
                host_ids=host_ids)
            self.host_list = [
                server.assets.asset_management_ip for server in server_objs
            ]
            ans = ansible_api_v2.ANSRunner(resource, sock=self)

            if self.config.repo == 'git':
                tool = GitTools(repo_url=self.config.repo_url,
                                path=self.config.src_dir,
                                env=self.config.project.project_env)

                tool.checkout(self.branch_tag)
                if commit:
                    tool.checkout(commit)
                    self.release_desc = tool.get_commit_msg(
                        self.branch_tag, commit)
                else:
                    self.release_desc = self.release_name

                self.deploy(rollback, timeline_header, cmd_detail,
                            timeline_body_green, timeline_body_red, ans, info,
                            tool)
            elif self.config.repo == 'svn':
                tool = SVNTools(repo_url=self.config.repo_url,
                                path=self.config.src_dir,
                                env=self.config.project.project_env,
                                username=self.config.repo_user,
                                password=self.config.repo_password)

                if commit:
                    model_name = '' if self.config.repo_model == 'trunk' else self.branch_tag
                    self.release_desc = tool.get_commit_msg(
                        int(commit),
                        self.config.repo_model,
                        model_name=model_name)
                else:
                    self.release_desc = self.release_name

                c = int(commit) if commit else None
                self.deploy(rollback,
                            timeline_header,
                            cmd_detail,
                            timeline_body_green,
                            timeline_body_red,
                            ans,
                            info,
                            tool,
                            repo='svn',
                            commit=c)

            self.close()
            self.redis_instance.delete(unique_key)

    def disconnect(self, close_code):
        if '<p style="color: #FF0000">所有主机均部署失败!退出部署流程!</p>' in self.deploy_results:
            self.deploy_results = self.deploy_results[:self.deploy_results.index(
                '<p style="color: #FF0000">所有主机均部署失败!退出部署流程!</p>') + 1]
        deploy_log.delay(project_config=self.config,
                         deploy_user=self.scope['user'],
                         d_type=self.d_type,
                         branch_tag=self.branch_tag,
                         release_name=self.release_name,
                         release_desc=self.release_desc,
                         result=self.deploy_results)

    def deploy(self,
               rollback,
               timeline_header,
               cmd_detail,
               timeline_body_green,
               timeline_body_red,
               ans,
               info,
               tool,
               repo='git',
               commit=None):
        if repo == 'svn':
            # 执行检出代码之前的命令,比如安装依赖等
            if self.config.prev_deploy:
                self.send_save(timeline_header.format('执行检出代码前置任务'))
                self.send_save(cmd_detail.format(self.config.prev_deploy))
                try:
                    code = tool.run_cmd(self.config.prev_deploy)
                    if code == 0:
                        self.send_save(
                            timeline_body_green.format('执行检出代码前置任务成功!'))
                    else:
                        self.send_save(
                            timeline_body_red.format('执行检出代码前置任务失败!'),
                            close=True)
                except Exception as e:
                    self.send_save(timeline_body_red.format(
                        '执行检出代码前置任务失败!{}'.format(e)),
                                   close=True)

            # 执行检出代码任务
            try:
                self.send_save(timeline_header.format('执行检出代码任务'))
                if self.config.repo_model == 'trunk':
                    tool.checkout(self.config.repo_model, revision=commit)
                else:
                    tool.checkout(self.config.repo_model,
                                  self.branch_tag,
                                  revision=commit)
                self.send_save(timeline_body_green.format('执行检出代码任务成功!'))
            except Exception as e:
                self.send_save(timeline_body_red.format(
                    '执行检出代码任务失败!'.format(e)),
                               close=True)
        if not rollback:
            # 执行同步代码之前的命令,比如编译等
            if self.config.post_deploy:
                self.send_save(timeline_header.format('执行同步代码前置任务'))
                self.send_save(cmd_detail.format(self.config.post_deploy))
                try:
                    code = tool.run_cmd(self.config.post_deploy)
                    if code == 0:
                        self.send_save(
                            timeline_body_green.format('执行同步代码前置任务成功!'))
                    else:
                        self.send_save(
                            timeline_body_red.format('执行同步代码前置任务失败!'),
                            close=True)
                except Exception as e:
                    self.send_save(timeline_body_red.format(
                        '执行同步代码前置任务失败!{}'.format(e)),
                                   close=True)

            # 检测目标机器是否连通,如果连通,判断是否存在存储代码版本的路径,如果不存在就创建
            self.send_save(timeline_header.format('检测目标机器是否连通'))
            ans.run_module(self.host_list,
                           module_name='file',
                           module_args='path={} state=directory'.format(
                               os.path.join(self.config.deploy_releases,
                                            tool.proj_name)),
                           deploy=True)

            # 将代码同步至目标服务器
            try:
                des_dir = self.gen_dir(tool, info)

                # 通过判断路径中是否存在target目录确定是否是JAVA项目
                target_path = os.path.join(tool.proj_path, 'target')

                src_dir = '{}/{}/'.format(
                    target_path, tool.proj_name) if os.path.exists(
                        target_path) else tool.proj_path + '/'

                self.send_save(timeline_header.format('执行同步代码任务'))

                self.sync_code(ans,
                               self.host_list,
                               src_dir,
                               des_dir,
                               excludes=self.config.exclude)

                # 如果运行服务的用户不是root,就将代码目录的属主改为指定的user
                if self.config.run_user != 'root':
                    ans.run_module(
                        self.host_list,
                        module_name='file',
                        module_args='path={} owner={} recurse=yes'.format(
                            des_dir, self.config.run_user),
                        deploy=True,
                        send_msg=False)

                # 将版本保存到数据库
                if self.release_name not in self.config.versions.split(','):
                    version = ',' + self.release_name if self.config.versions else self.release_name
                    self.config.versions += version
                    version_list = self.config.versions.split(',')
                    if len(version_list) > self.config.releases_num:
                        self.config.versions = ','.join(
                            version_list[len(version_list) -
                                         self.config.releases_num:])
                    self.config.save()
                self.del_release(ans,
                                 self.host_list,
                                 path=os.path.join(self.config.deploy_releases,
                                                   tool.proj_name),
                                 releases_num=self.config.releases_num)
            except Exception as e:
                self.send_save(timeline_body_red.format(
                    '执行同步代码任务失败!{}'.format(e)),
                               close=True)

        # 执行部署前任务
        if self.config.prev_release:
            self.send_save(timeline_header.format('执行部署前置任务'))
            self.send_save(cmd_detail.format(self.config.prev_release))
            try:
                self.run_cmds(ans, self.host_list, self.config.prev_release)
            except Exception as e:
                self.send_save(timeline_body_red.format(
                    '执行部署前置任务失败!{}'.format(e)),
                               close=True)

        # 配置软连接,指向指定的版本目录
        self.send_save(timeline_header.format('执行部署任务'))
        try:
            src = self.gen_dir(tool, info)
            dest = os.path.join(self.config.deploy_webroot, tool.proj_name)
            ans.run_module(self.host_list,
                           module_name='shell',
                           module_args='rm -rf {} && ln -s {} {}'.format(
                               dest, src, dest),
                           deploy=True)
        except Exception as e:
            self.send_save(timeline_body_red.format('执行部署任务失败!{}'.format(e)),
                           close=True)

        # 执行部署后任务
        if self.config.post_release:
            self.send_save(timeline_header.format('执行部署后置任务'))
            self.send_save(cmd_detail.format(self.config.post_release))
            try:
                self.run_cmds(ans, self.host_list, self.config.post_release)
            except Exception as e:
                self.send_save(timeline_body_red.format(
                    '执行部署后置任务失败!{}'.format(e)),
                               close=True)

    @staticmethod
    def sync_code(ans, host_list, src_dir, des_dir, excludes=None):
        if excludes:
            opts = ''
            for i in excludes.split('\n'):
                if i.startswith('#'):
                    continue
                opts += ',--exclude=' + i
            opts = opts.lstrip(',')
            ans.run_module(
                host_list,
                module_name='synchronize',
                module_args='src={} dest={} delete=yes rsync_opts="{}"'.format(
                    src_dir, des_dir, opts),
                deploy=True)
        else:
            ans.run_module(host_list,
                           module_name='synchronize',
                           module_args='src={} dest={} delete=yes'.format(
                               src_dir, des_dir),
                           deploy=True)

    @staticmethod
    def run_cmds(ans, host_list, cmds):
        c = ''
        for cmd in cmds.split('\n'):
            if cmd.startswith('#'):
                continue
            c += cmd + ' && '
        c = c.rstrip(' && ')
        ans.run_module(host_list,
                       module_name='shell',
                       module_args=c,
                       deploy=True)

    def gen_dir(self, tool, info):
        commit = info.get('commit', None)
        des_dir = os.path.join(
            self.config.deploy_releases, tool.proj_name,
            '{}'.format(commit)) if commit else os.path.join(
                self.config.deploy_releases, tool.proj_name, '{}'.format(
                    self.branch_tag))
        return des_dir

    @staticmethod
    def del_release(ans, host_list, path, releases_num):
        """按照数据库设置的保留版本个数,删除最早的多余的版本"""
        ans.run_module(
            host_list,
            module_name='shell',
            module_args='cd {path} && rm -rf `ls -t | tail -n +{releases_num}`'
            .format(path=path, releases_num=releases_num + 1),
            deploy=True,
            send_msg=False)

    def send_save(self, msg, close=False, send=True):
        if send:
            self.send(msg, close=close)
            self.deploy_results.append(msg)
Ejemplo n.º 5
0
class TicketDeployConsumer(WebsocketConsumer):
    def __init__(self, *args, **kwargs):
        super(TicketDeployConsumer, self).__init__(*args, **kwargs)
        self.redis_instance = RedisOps(settings.REDIS_HOST,
                                       settings.REDIS_PORT, 5)
        self.deploy_results = []
        self.host_fail = []
        self.config = None
        self.d_type = None
        self.release_name = None
        self.release_desc = None
        self.host_list = None
        self.branch_tag = None

    def connect(self):
        self.accept()

    def receive(self, text_data=None, bytes_data=None):

        # {"tid": "19", "pid": "3", "dserver": ["2", "3"], "d_type": "deploy"}
        info = json.loads(text_data)
        self.config = Project_Deploy_Ticket.objects.get(id=info.get('tid'))

        unique_key = self.config.ticket_config.proj_uuid
        self.d_type = info.get('d_type').capitalize()

        step_msg = "[{} " "{}] <font size=\"2\" color=\"blue\">{}</font></br>".format(
            time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), self.d_type,
            {})
        ok_msg = "[{} " "{}] <font size=\"2\" color=\"blue\">{}...OK</font></br>".format(
            time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), self.d_type,
            {})
        fail_msg = "[{} " "{}] <font size=\"2\" color=\"color: #FF0000\">{}...Error</font></br>".format(
            time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), self.d_type,
            {})
        warn_msg = "[{} " "{}] <font size=\"2\" color=\"orange\">{}...warning</font></br>".format(
            time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), self.d_type,
            {})

        if self.redis_instance.get(unique_key):
            self.send_save(fail_msg.format("有相同的任务正在进行,请稍后重试!"))
            self.close()
        else:
            self.redis_instance.set(unique_key, 1)
            ticket_no = self.config.ticket_no
            if self.d_type == 'Dcheck':
                self.deploy_check(ok_msg)
            elif self.config.ticket_status == 3 or self.config.ticket_status == 4:
                self.send_save(fail_msg.format("工单已关闭,请联系管理员!"), close=True)
                return
            elif self.d_type == 'Deploy':

                self.branch_tag = self.config.ticket_config.proj_branch_tag
                commit = self.config.ticket_commit
                self.release_name = commit if commit else self.branch_tag

                if self.config.ticket_config.proj_role.repo == 'git':
                    tool = GitTools(
                        repo_url=self.config.ticket_config.proj_role.repo_url,
                        path=self.config.ticket_config.proj_role.src_dir,
                        env=self.config.ticket_config.proj_env.projenv_name)
                    try:
                        tool.checkout(self.branch_tag)
                        self.send_save(ok_msg.format("切换分支:" +
                                                     self.branch_tag))
                        if commit:
                            self.send_save(
                                ok_msg.format("Commit ID:" + commit[:7]))
                            tool.checkout(commit)
                            self.release_desc = tool.get_commit_msg(
                                self.branch_tag, commit)
                        else:
                            self.release_desc = self.release_name
                        self.send_save(
                            ok_msg.format("Commit Msg: " + self.release_desc))
                    except Exception as e:
                        self.send_save(fail_msg("检出代码"), close=True)

                    self.deploy(ticket_no, info, tool, step_msg, ok_msg,
                                fail_msg, warn_msg)

                elif self.config.ticket_config.proj_role.repo == 'svn':
                    tool = SVNTools(
                        repo_url=self.config.ticket_config.proj_role.repo_url,
                        path=self.config.ticket_config.proj_role.src_dir,
                        env=self.config.ticket_config.proj_env.projenv_name,
                        username=self.config.ticket_config.proj_role.repo_user,
                        password=self.config.ticket_config.proj_role.
                        repo_password)
                    self.send_save(step_msg.format("【检出SVN代码】"))
                    if commit:
                        self.send_save(
                            ok_msg.format("Commit ID: " + commit[:7]))
                        model_name = '' if self.config.ticket_config.proj_role.repo_model == 'trunk' else self.brahch_tag
                        self.release_desc = tool.get_commit_msg(
                            int(commit),
                            self.config.ticket_config.proj_role.repo_model,
                            model_name=model_name)
                    else:
                        self.release_desc = self.release_name
                    self.send_save(
                        ok_msg.format("Commit Msg: " + self.release_desc))

                    c = int(commit) if commit else None

                    # 执行检出代码之前的命令,比如安装依赖等
                    if self.config.ticket_config.proj_role.prev_deploy:
                        try:
                            code = tool.run_cmd(self.config.ticket_config.
                                                proj_role.prev_deploy)
                            if code == 0:
                                self.send_save(ok_msg.format("执行检出代码之前的命令"))
                            else:
                                self.send_save(fail_msg("执行检出代码之前的命令"),
                                               close=True)
                        except Exception as e:
                            self.send_save(fail_msg("执行检出代码之前的命令"), close=True)

                    # 执行检出代码任务
                    try:
                        if self.config.ticket_config.proj_role.repo_model == 'trunk':
                            tool.checkout(
                                self.config.ticket_config.proj_role.repo_model,
                                revision=c)
                            self.send_save(ok_msg.format("检出代码Trunk版本号: " + c))
                        else:
                            tool.checkout(
                                self.config.ticket_config.proj_role.repo_model,
                                self.branch_tag,
                                revision=commit)
                            self.send_save(
                                ok_msg.format("检出分支: " + self.branch_tag +
                                              "版本号: "))
                    except Exception as e:
                        self.send_save(fail_msg("执行检出"), close=True)

                    self.deploy(ticket_no, info, tool, step_msg, ok_msg,
                                fail_msg, warn_msg)

                self.close()
                self.redis_instance.delete(unique_key)

            elif self.d_type == 'Rollback':
                if self.config.ticket_config.proj_role.repo == 'git':
                    tool = GitTools(
                        repo_url=self.config.ticket_config.proj_role.repo_url,
                        path=self.config.ticket_config.proj_role.src_dir,
                        env=self.config.ticket_config.proj_env.projenv_name)
                    self.deploy(ticket_no, info, tool, step_msg, ok_msg,
                                fail_msg, warn_msg)
                elif self.config.ticket_config.proj_role.repo == 'svn':
                    tool = SVNTools(
                        repo_url=self.config.ticket_config.proj_role.repo_url,
                        path=self.config.ticket_config.proj_role.src_dir,
                        env=self.config.ticket_config.proj_env.projenv_name,
                        username=self.config.ticket_config.proj_role.repo_user,
                        password=self.config.ticket_config.proj_role.
                        repo_password)
                    self.deploy(ticket_no, info, tool, step_msg, ok_msg,
                                fail_msg, warn_msg)
                else:
                    pass
            else:
                pass

    def deploy_check(self, ok_msg):
        count = 0
        for pid in self.config.ticket_platform.all():
            for ser in Assets.objects.filter(
                    asset_projenv=self.config.ticket_config.proj_env.id
            ).filter(asset_platform=pid).filter(
                    asset_projapp__id=self.config.ticket_config.proj_app.id):
                dt = Project_Deploy_Record.objects.filter(
                    deploy_ip=ser.asset_management_ip,
                    d_ticket_id=self.config.id)
                if dt.count() == 0:
                    self.send_save(
                        ok_msg.format("{}: {} {} {} 【无发布记录】").format(
                            pid.platform_name, ser.asset_management_ip,
                            ser.asset_hostname, 'Available'
                            if ser.asset_status == 0 else "Inactive"))
                    if ser.asset_status == 0:
                        count += 1
                else:
                    self.send_save(
                        ok_msg.format("{}: {} {} {} {} {} {} {}").format(
                            pid.platform_name, ser.asset_management_ip,
                            ser.asset_hostname, 'Available'
                            if ser.asset_status == 0 else "Inactive",
                            '【发布成功】' if dt[0].deploy_status == 9 else "【发布失败】",
                            dt[0].deploy_times,
                            '【回滚成功】' if dt[0].rollback_status == 1 else 'None',
                            'None' if dt[0].rollback_times == 0 else
                            dt[0].rollback_times))
                    if dt[0].deploy_status != 9:
                        count += 1
        self.send_save(
            ok_msg.format("Deploy Check Finished! Count: {}").format(count),
            close=True)

    def disconnect(self, close_code):
        self.send_save("def disconnect:")

    def deploy(self, ticket_no, info, tool, step_msg, ok_msg, fail_msg,
               warn_msg):

        host_ids = info.get('dserver')  # 这里检测发布(回滚)服务器
        tid = Project_Deploy_Ticket.objects.get(id=info.get('tid'))
        dsList = []
        for hs in host_ids:
            ds = Assets.objects.get(id=hs)
            dt = Project_Deploy_Record.objects.filter(
                deploy_ip=ds.asset_management_ip, d_ticket_id=info.get('tid'))
            if dt.count() == 1:  # 是否有发布记录:有
                if self.d_type == "Rollback":  # 不要移动这个代码段位置,否则会逻辑错误!必需要先判断是否是回滚
                    if dt[0].rollback_times != dt[0].deploy_times:
                        self.send_save(
                            ok_msg.format("{ip} {hs} 第{times}次回滚").format(
                                ip=ds.asset_management_ip,
                                hs=ds.asset_hostname,
                                times=dt[0].rollback_times + 1,
                            ))
                        try:  # 回滚次数+1
                            dsList.append(hs)
                            dt.update(rollback_times=dt[0].rollback_times + 1,
                                      update_date=time.strftime(
                                          "%Y-%m-%d %H:%M:%S",
                                          time.localtime()))
                        except Exception as e:
                            self.send_save(
                                fail_msg.format(
                                    "更新 rollback_times + 1 回滚次数异常!{}").format(
                                        e))
                    else:
                        self.send_save(fail_msg.format("回滚次数不能大于发布次数!"))
                elif dt[0].deploy_status == 9:  # 判断记录是否发布成功:发布成功

                    if self.d_type == "Deploy":
                        self.send_save(
                            warn_msg.format(
                                "{ip} {hs} 发布记录为成功,请勿重复发布,踢出发布列表!").format(
                                    ip=ds.asset_management_ip,
                                    hs=ds.asset_hostname
                                    if ds.asset_hostname else 'null'))
                        if dt[0].deploy_times == 1:
                            self.send_save(
                                warn_msg.format(
                                    "{ip} {hs} 回滚记录为成功,请确认部署状态!").format(
                                        ip=ds.asset_management_ip,
                                        hs=ds.asset_hostname
                                        if ds.asset_hostname else 'null'))

                elif dt[0].deploy_status < 9:  # 判断记录是否发布成功:没有发成功
                    if self.d_type == 'Deploy':
                        self.send_save(
                            ok_msg.format(
                                "{ip} {hs} 第{times}次发布 Start").format(
                                    ip=ds.asset_management_ip,
                                    hs=ds.asset_hostname,
                                    times=dt[0].deploy_times + 1,
                                ))
                        try:  # 发布次数+1
                            dsList.append(hs)
                            dt.update(deploy_times=dt[0].deploy_times + 1,
                                      update_date=time.strftime(
                                          "%Y-%m-%d %H:%M:%S",
                                          time.localtime()))
                        except Exception as e:
                            self.send_save(
                                fail_msg.format(
                                    "更新 deploy_times + 1 发布次数异常!{}").format(e))

            elif dt.count() == 0:  # 是否有发布记录:无

                if self.d_type == 'Deploy':
                    self.send_save(
                        ok_msg.format("{ip} {hs} 第1次发布 Start").format(
                            ip=ds.asset_management_ip,
                            hs=ds.asset_hostname
                            if ds.asset_hostname else 'null'))

                    try:  # 创建发布记录表
                        dtc = Project_Deploy_Record.objects.create(
                            assets=ds,
                            deploy_ip=ds.asset_management_ip,
                            deploy_times=1,
                            d_ticket_id=tid,
                        )
                        dsList.append(hs)
                    except Exception as e:
                        self.send_save(
                            fail_msg.format("创建发布记录表异常! {}").format(e))
                        return

                elif self.d_type == 'Rollback':
                    self.send_save(
                        fail_msg.format("{} 没有发布记录,回滚终止!").format(
                            ds.asset_management_ip))
                else:
                    pass

        if dsList:
            resource = gen_resource.GenResource().gen_host_list(
                host_ids=dsList)
            self.host_list = [server['ip'] for server in resource]
            ans = ansible_api_v2.ANSRunner(resource, sock=self)
        else:
            self.send_save(fail_msg.format("没有发布(回滚)主机,部署终止!"), close=True)
            return

        if self.d_type == 'Deploy':
            # 执行同步代码之前的命令,比如编译等
            if self.config.ticket_config.proj_role.post_deploy:
                try:
                    code = tool.run_cmd(
                        self.config.ticket_config.proj_role.post_deploy)
                    if code == 0:
                        self.send_save(ok_msg.format("执行同步代码之前的命令成功"))
                    else:
                        self.send_save(fail_msg.format("执行同步代码之前的命令失败"))
                        return
                except Exception as e:
                    self.send_save(
                        fail_msg.format("执行同步代码之前的命令异常! {}").format(e))
                    return

            # 检测目标机器是否连通,如果连通,判断是否存在存储代码版本的路径,如果不存在就创建
            ans.run_module(
                self.host_list,
                module_name='file',
                module_args='path={} state=directory'.format(
                    os.path.join(
                        self.config.ticket_config.proj_role.deploy_releases,
                        tool.proj_name)),
                deploy=True,
                d_type=self.d_type,
                task='connet',
                tid=tid.id)

            # 备份代码目录
            try:
                backup_dir = self.gen_dir(
                    tool, ticket_no,
                    backup="backup") + "/" + time.strftime("%Y%m%d%H%M%S")
                backup_dest = os.path.join(
                    self.config.ticket_config.proj_role.deploy_webroot,
                    tool.proj_name)
                ans.run_module(
                    self.host_list,
                    module_name='shell',
                    module_args='mkdir -p {} && /bin/cp -rf {}/* {}'.format(
                        backup_dir, backup_dest, backup_dir),
                    deploy=True,
                    d_type=self.d_type,
                    task="backup",
                    tid=tid.id)
            except Exception as e:
                self.send_save(fail_msg.format("目标服务器备份代码异常: {}").format(e))
                return

            # 将代码同步至目标服务器
            try:
                # /data/version/urlmonitor/123131232121238
                des_dir = self.gen_dir(tool, ticket_no)
                # 通过判断路径中是否存在target目录确定是否是JAVA项目
                # target_path /data/urlmonitor/UAT/target
                target_path = os.path.join(tool.proj_path, 'target')
                java_proj = os.path.exists(target_path)
                if java_proj:
                    self.send_save(ok_msg.format("是JAVA项目"))
                else:
                    self.send_save(ok_msg.format("非JAVA项目"))

                # 代码同步源目录
                src_dir = '{}/{}/'.format(
                    target_path, tool.proj_name) if os.path.exists(
                        target_path) else tool.proj_path + '/'
                self.sync_code(
                    ans,
                    self.host_list,
                    src_dir,
                    des_dir,
                    excludes=self.config.ticket_config.proj_role.exclude,
                    d_type=self.d_type,
                    task="sync_code",
                    tid=tid.id)

                # 如果运行服务的用户不是root,就将代码目录的属主改为指定的user
                if self.config.ticket_config.proj_role.run_user != 'root':
                    ans.run_module(
                        self.host_list,
                        module_name='file',
                        module_args='path={} owner={} recurse=yes'.format(
                            des_dir,
                            self.config.ticket_config.proj_role.run_user),
                        deploy=True,
                        send_msg=False,
                        d_type=self.d_type,
                        task="owner",
                        tid=tid.id)
            except Exception as e:
                self.send_save(fail_msg.format("代码同步至目标服务器异常 {}").format(e))
                return

            # 执行部署前任务
            if self.config.ticket_config.proj_role.prev_release:
                try:
                    self.run_cmds(
                        ans,
                        self.host_list,
                        self.config.ticket_config.proj_role.prev_release,
                        d_type=self.d_type,
                        task="prev_release",
                        tid=tid.id)
                except Exception as e:
                    self.send_save(
                        fail_msg.format("目标服务器部署前任务异常 {}").format(e))

            # 配置软连接,指向指定的版本目录
            try:
                src = self.gen_dir(tool, ticket_no)
                dest = os.path.join(
                    self.config.ticket_config.proj_role.deploy_webroot,
                    tool.proj_name)
                ans.run_module(self.host_list,
                               module_name='shell',
                               module_args='rm -rf {} && ln -sf {} {}'.format(
                                   dest, src + '/', dest),
                               deploy=True,
                               d_type=self.d_type,
                               task="release",
                               tid=tid.id)
            except Exception as e:
                self.send_save(fail_msg.format("目标服务器部署异常 {}").format(e))

            # 执行部署后任务
            if self.config.ticket_config.proj_role.post_release:
                try:
                    self.run_cmds(
                        ans,
                        self.host_list,
                        self.config.ticket_config.proj_role.post_release,
                        d_type=self.d_type,
                        task='post_release',
                        tid=tid.id)
                except Exception as e:
                    self.send_save(
                        fail_msg.format("目标服务器部署后任务异常 {}").format(e))

        elif self.d_type == 'Rollback':

            self.send_save(ok_msg.format("开始回滚!"))
            # 回滚代码
            try:
                backup_dir = self.gen_dir(tool, ticket_no, backup="backup")
                backup_dest = os.path.join(
                    self.config.ticket_config.proj_role.deploy_webroot,
                    tool.proj_name)
                print(backup_dir, backup_dest)
                ans.run_module(
                    self.host_list,
                    module_name='raw',
                    module_args=
                    'cd {bak_dir} && back_dir\=`ls -lt |egrep \^d|egrep -v rollback\$|cut -d " " -f 9|egrep \^[2][0-9]|head -n 1` &&  mv $back_dir $back_dir.rollback  && rm -rf {bak_dest} && ln -sf {bak_dir}/$back_dir.rollback {bak_dest}'
                    .format(
                        bak_dir=backup_dir,
                        bak_dest=backup_dest,
                    ),
                    deploy=True,
                    d_type=self.d_type,
                    task="rollback",
                    tid=tid.id)
            except Exception as e:
                self.send_save(fail_msg.format("目标服务器回滚代码异常: {}").format(e))
                return

    @staticmethod
    def sync_code(ans,
                  host_list,
                  src_dir,
                  des_dir,
                  excludes=None,
                  d_type=None,
                  task=None,
                  tid=None):
        d_type = d_type
        task = task
        tid = tid
        if excludes:
            opts = ''
            for i in excludes.split('\n'):
                if i.startswith('#'):
                    continue
                opts += ',--exclude=' + i
            opts = opts.lstrip(',')
            ans.run_module(
                host_list,
                module_name='synchronize',
                module_args='src={} dest={} delete=yes rsync_opts="{}"'.format(
                    src_dir, des_dir, opts),
                deploy=True,
                d_type=d_type,
                task=task)
        else:
            ans.run_module(host_list,
                           module_name='synchronize',
                           module_args='src={} dest={} delete=yes'.format(
                               src_dir, des_dir),
                           deploy=True,
                           d_type=d_type,
                           task=task,
                           tid=tid)

    @staticmethod
    def run_cmds(ans, host_list, cmds, d_type=None, task=None, tid=None):
        d_type = d_type
        task = task
        tid = tid
        c = ''
        for cmd in cmds.split('\n'):
            if cmd.startswith('#'):
                continue
            c += cmd + ' && '
        c = c.rstrip(' && ')
        ans.run_module(host_list,
                       module_name='shell',
                       module_args=c,
                       deploy=True,
                       d_type=d_type,
                       task=task,
                       tid=tid)

    def gen_dir(self, tool, ticket_no, backup=None):
        ticket_no = ticket_no
        backup = backup
        if backup:
            des_dir = os.path.join(
                self.config.ticket_config.proj_role.deploy_releases,
                tool.proj_name, backup, '{}'.format(ticket_no))
        else:
            des_dir = os.path.join(
                self.config.ticket_config.proj_role.deploy_releases,
                tool.proj_name, '{}'.format(ticket_no))
        return des_dir

    @staticmethod
    def del_release(ans, host_list, path, dir_name):
        """按照数据库设置的保留版本个数,删除最早的多余的版本"""
        ans.run_module(host_list,
                       module_name='shell',
                       module_args='cd {path} && rm -rf {dir_name}'.format(
                           path=path, dir_name=dir_name),
                       deploy=True,
                       send_msg=False)

    def send_save(self, msg, close=False, send=True):
        if send:
            self.send(msg, close=close)
            self.deploy_results.append(msg)