示例#1
0
    def __init__(self, task_id=None, project_id=None, console=False):
        self.local_codebase = current_app.config.get('CODE_BASE')
        self.localhost = Waller(host='127.0.0.1')
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            current_app.logger.info(self.task_id)
            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.project_info = self.taskMdl.get('project_info')

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()
            self.servers = self.project_info['servers_info']

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.local_codebase + str(self.project_name)

        # self.init_repo()

        # start to deploy
        self.console = console
示例#2
0
    def project_detection(self):
        errors = []
        # LOCAL_SERVER_USER => git

        # LOCAL_SERVER_USER => target_servers
        for server_info in self.servers:
            waller = Waller(host=server_info['host'], user=server_info['user'], port=server_info['port'])
            result = waller.run('id', exception=False, wenv=self.config())
            if result.failed:
                errors.append({
                    'title': '远程目标机器免密码登录失败',
                    'why': '远程目标机器:%s 错误:%s' % (server_info['host'], result.stdout),
                    'how': '在宿主机中配置免密码登录,把宿主机用户%s的~/.ssh/ssh_rsa.pub添加到远程目标机器用户%s的~/.ssh/authorized_keys。了解更多:http://walle-web.io/docs/troubleshooting.html' % (
                    pwd.getpwuid(os.getuid())[0], server_info['host']),
                })

            # maybe this is no webroot's parent dir
            command = '[ -d {webroot} ] || mkdir -p {webroot}'.format(webroot=os.path.basename(self.project_info['target_root']))
            result = waller.run(command, exception=False, wenv=self.config(console=False))

                # 检查 webroot 父目录是否存在,是否为软链
            command = '[ -L "%s" ] && echo "true" || echo "false"' % (self.project_info['target_root'])
            result = waller.run(command, exception=False, wenv=self.config())
            if result.stdout == 'false':
                errors.append({
                    'title': '远程目标机器webroot不能是已建好的目录',
                    'why': '远程目标机器%s webroot不能是已存在的目录,必须为软链接,你不必新建,walle会自行创建。' % (server_info['host']),
                    'how': '手工删除远程目标机器:%s webroot目录:%s' % (server_info['host'], self.project_info['target_root']),
                })

        # remote release directory
        return errors
示例#3
0
    def project_detection(self):
        errors = []
        # LOCAL_SERVER_USER => git

        # LOCAL_SERVER_USER => target_servers
        for server_info in self.servers:
            waller = Waller(host=server_info['host'], user=server_info['user'], port=server_info['port'])
            result = waller.run('id', exception=False, wenv=self.config())
            if result.failed:
                errors.append({
                    'title': '远程目标机器免密码登录失败',
                    'why': '远程目标机器:%s 错误:%s' % (server_info['host'], result.stdout),
                    'how': '在宿主机中配置免密码登录,把宿主机用户%s的~/.ssh/ssh_rsa.pub添加到远程目标机器用户%s的~/.ssh/authorized_keys。了解更多:http://walle-web.io/docs/troubleshooting.html' % (
                    pwd.getpwuid(os.getuid())[0], server_info['host']),
                })

            # maybe this is no webroot's parent dir
            command = '[ -d {webroot} ] || mkdir -p {webroot}'.format(webroot=os.path.basename(self.project_info['target_root']))
            result = waller.run(command, exception=False, wenv=self.config(console=False))

                # 检查 webroot 父目录是否存在,是否为软链
            command = '[ -L "%s" ] && echo "true" || echo "false"' % (self.project_info['target_root'])
            result = waller.run(command, exception=False, wenv=self.config())
            if result.stdout == 'false':
                errors.append({
                    'title': '远程目标机器webroot不能是已建好的目录',
                    'why': '远程目标机器%s webroot不能是已存在的目录,必须为软链接,你不必新建,walle会自行创建。' % (server_info['host']),
                    'how': '手工删除远程目标机器:%s webroot目录:%s' % (server_info['host'], self.project_info['target_root']),
                })

        # remote release directory
        return errors
示例#4
0
    def walle_rollback(self):
        self.start()

        try:
            is_all_servers_success = True
            self.release_version = self.taskMdl.get('link_id')
            for server_info in self.servers:
                host = server_info['host']
                try:
                    waller = Waller(host=host, user=server_info['user'], port=server_info['port'], inline_ssh_env=True)
                    waller.init_env(env=self.custom_global_env)

                    self.connections[host] = waller                   
                    self.prev_release_custom(self.connections[host])
                    self.release(self.connections[host])
                    self.post_release(self.connections[host])
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_success, host=host,
                                              user=server_info['user'], command='')
                    emit('success', {'event': 'finish', 'data': {'host': host, 'message': host + ' 部署完成!'}}, room=self.task_id)
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.error(e)
                    self.errors[host] = e.message
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_fail, host=host,
                                              user=server_info['user'], command='')
                    emit('fail', {'event': 'finish', 'data': {'host': host, 'message': host + Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)

        return {'success': self.success, 'errors': self.errors}
示例#5
0
    def walle_rollback(self):
        self.start()

        try:
            is_all_servers_success = True
            for server_info in self.servers:
                host = server_info['host']
                try:
                    waller = Waller(host=host, user=server_info['user'], port=server_info['port'], inline_ssh_env=True)
                    waller.init_env(env=self.custom_global_env)

                    self.connections[self.task_id] = waller
                    self.prev_release_custom(self.connections[self.task_id])
                    self.release(self.connections[self.task_id])
                    self.post_release(self.connections[self.task_id])
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_success, host=host,
                                              user=server_info['user'], command='')
                    emit('success', {'event': 'finish', 'data': {'host': host, 'message': host + ' 部署完成!'}}, room=self.task_id)
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.exception(e)
                    self.errors[host] = e.message
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_fail, host=host,
                                              user=server_info['user'], command='')
                    emit('fail', {'event': 'finish', 'data': {'host': host, 'message': host + Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)

        return {'success': self.success, 'errors': self.errors}
示例#6
0
    def __init__(self, task_id=None, project_id=None, console=False):
        self.local_codebase = current_app.config.get('CODE_BASE')
        self.local = Waller(
            host=current_app.config.get('LOCAL_SERVER_HOST'),
            user=current_app.config.get('LOCAL_SERVER_USER'),
            port=current_app.config.get('LOCAL_SERVER_PORT'),
        )
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            current_app.logger.info(self.task_id)
            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.task = self.taskMdl.get('target_user')
            self.project_info = self.taskMdl.get('project_info')

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.local_codebase + str(
            self.project_name)

        self.init_repo()

        # start to deploy
        self.console = console
示例#7
0
    def __init__(self, task_id=None, project_id=None, console=False):
        self.local_codebase = current_app.config.get('CODE_BASE').rstrip('/') + '/'
        self.localhost = Waller(host='127.0.0.1')
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            current_app.logger.info(self.task_id)
            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.project_info = self.taskMdl.get('project_info')

            # copy to a local version
            self.release_version = '{project_id}_{task_id}_{timestamp}'.format(
                project_id=self.project_info['id'],
                task_id=self.task_id,
                timestamp=time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())),
            )
            current_app.logger.info(self.taskMdl)

            self.custom_global_env = {
                'WEBROOT': str(self.project_info['target_root']),
                'VERSION': str(self.release_version),
                'CURRENT_RELEASE': str(self.project_info['target_releases']),
                'BRANCH': str(self.taskMdl.get('branch')),
                'TAG': str(self.taskMdl.get('tag')),
                'COMMIT_ID': str(self.taskMdl.get('commit_id')),
                'PROJECT_NAME': str(self.project_info['name']).replace('"', '').replace("'", '').replace(" ", '_'),
                'PROJECT_ID': str(self.project_info['id']),
                'TASK_NAME': str(self.taskMdl.get('name')).replace('"', '').replace("'", '').replace(" ", '_'),
                'TASK_ID': str(self.task_id),
                'DEPLOY_USER': str(self.taskMdl.get('user_name')),
                'DEPLOY_TIME': str(time.strftime('%Y%m%d-%H:%M:%S', time.localtime(time.time()))),
            }
            if self.project_info['task_vars']:
                task_vars = [i.strip() for i in self.project_info['task_vars'].split('\n') if i.strip() and not i.strip().startswith('#')]
                for var in task_vars:
                    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)

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()
            self.servers = self.project_info['servers_info']

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.local_codebase + str(self.project_name)

        # self.init_repo()

        # start to deploy
        self.console = console
示例#8
0
    def project_detection(self):
        errors = []
        #  walle user => walle LOCAL_SERVER_USER
        # show ssh_rsa.pub (maybe not necessary)
        command = 'whoami'
        current_app.logger.info(command)
        result = self.local.run(command, exception=False, wenv=self.config())
        if result.failed:
            errors.append({
                'title':
                u'本地免密码登录失败',
                'why':
                result.stdout,
                'how':
                u'在宿主机中配置免密码登录,把walle启动用户%s的~/.ssh/ssh_rsa.pub添加到LOCAL_SERVER_USER用户%s的~/.ssh/authorized_keys。了解更多:http://walle-web.io/docs/troubleshooting.html'
                % (pwd.getpwuid(os.getuid())[0],
                   current_app.config.get('LOCAL_SERVER_USER')),
            })

        # LOCAL_SERVER_USER => git

        # LOCAL_SERVER_USER => target_servers
        for server_info in self.servers:
            waller = Waller(host=server_info['host'],
                            user=server_info['user'],
                            port=server_info['port'])
            result = waller.run('id', exception=False, wenv=self.config())
            if result.failed:
                errors.append({
                    'title':
                    u'远程目标机器免密码登录失败',
                    'why':
                    u'远程目标机器:%s 错误:%s' % (server_info['host'], result.stdout),
                    'how':
                    u'在宿主机中配置免密码登录,把宿主机用户%s的~/.ssh/ssh_rsa.pub添加到远程目标机器用户%s的~/.ssh/authorized_keys。了解更多:http://walle-web.io/docs/troubleshooting.html'
                    % (current_app.config.get('LOCAL_SERVER_USER'),
                       server_info['host']),
                })

                # 检查 webroot 父目录是否存在,是否为软链
            command = '[ -L "%s" ] && echo "true" || echo "false"' % (
                self.project_info['target_root'])
            result = waller.run(command, exception=False, wenv=self.config())
            if result.stdout == 'false':
                errors.append({
                    'title':
                    u'远程目标机器webroot不能是已建好的目录',
                    'why':
                    u'远程目标机器%s webroot不能是已存在的目录,必须为软链接,你不必新建,walle会自行创建。' %
                    (server_info['host']),
                    'how':
                    u'手工删除远程目标机器:%s webroot目录:%s' %
                    (server_info['host'], self.project_info['target_root']),
                })

        # remote release directory
        return errors
示例#9
0
    def __init__(self, task_id=None, project_id=None):
        self.local = Waller(host=current_app.config.get('LOCAL_SERVER_HOST'),
                            user=current_app.config.get('LOCAL_SERVER_USER'),
                            port=current_app.config.get('LOCAL_SERVER_PORT'))
        self.TaskRecord = TaskModel.TaskRecordModel()

        if task_id:
            self.task_id = task_id
            self.taskMdl = TaskModel.TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.task = self.taskMdl.get('target_user')
            self.project_info = self.taskMdl.get('project_info')
        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()
示例#10
0
    def walle_deploy(self):
        self.start()
        self.prev_deploy()
        self.deploy()
        self.post_deploy()

        # server = '172.16.0.231'
        # try:
        #     self.connections[server] = Waller(host=server, user=self.project_info['target_user'])
        #     self.prev_release(self.connections[server])
        #     self.release(self.connections[server])
        #     self.post_release(self.connections[server])
        # except Exception as e:
        #     current_app.logger.exception(e)
        #     self.errors[server] = e.message

        all_servers_success = True
        for server_info in self.servers:
            server = server_info['host']
            try:
                self.connections[server] = Waller(
                    host=server, user=self.project_info['target_user'])
                self.prev_release(self.connections[server])
                self.release(self.connections[server])
                self.post_release(self.connections[server])
            except Exception as e:
                all_servers_success = False
                self.errors[server] = e.message

        self.end(all_servers_success)
        return {'success': self.success, 'errors': self.errors}
示例#11
0
    def walle_deploy(self):
        self.start()

        try:
            self.prev_deploy()
            self.deploy()
            self.post_deploy()

            is_all_servers_success = True
            for server_info in self.servers:
                host = server_info['host']
                try:
                    self.connections[host] = Waller(host=host, user=server_info['user'], port=server_info['port'])
                    self.prev_release(self.connections[host])
                    self.release(self.connections[host])
                    self.post_release(self.connections[host])
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.error(e)
                    self.errors[host] = e.message
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)

        return {'success': self.success, 'errors': self.errors}
示例#12
0
    def walle_deploy(self):
        self.start()

        try:
            self.prev_deploy()
            self.deploy()
            self.post_deploy()

            is_all_servers_success = True
            for server_info in self.servers:
                server = server_info['host']
                try:
                    self.connections[server] = Waller(
                        host=server, user=self.project_info['target_user'])
                    self.prev_release(self.connections[server])
                    self.release(self.connections[server])
                    self.post_release(self.connections[server])
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.error(e)
                    self.errors[server] = e.message
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)
            emit('fail', {
                'event': 'console',
                'data': {
                    'message': Code.code_msg[Code.deploy_fail]
                }
            },
                 room=self.task_id)

        return {'success': self.success, 'errors': self.errors}
示例#13
0
    def walle_deploy(self):
        self.prev_deploy()
        self.deploy()
        self.post_deploy()

        server = '172.16.0.231'
        try:
            self.connections[server] = Waller(
                host=server, user=self.project_info['target_user'])
            self.prev_release(self.connections[server])
            self.release(self.connections[server])
            self.post_release(self.connections[server])
        except Exception as e:
            current_app.logger.exception(e)
            self.errors[server] = e.message

        # for server_info in self.servers:
        #     server = server_info.host
        #     try:
        #         self.connections[server] = Waller(host=server, user=self.project_info['target_user'])
        #         self.prev_release(self.connections[server])
        #         self.release(self.connections[server])
        #         self.post_release(self.connections[server])
        #     except Exception, e:
        #         self.errors[server] = e.message

        return {'success': self.success, 'errors': self.errors}
示例#14
0
    def __init__(self, task_id=None, project_id=None, console=False):
        self.local_codebase = current_app.config.get('CODE_BASE').rstrip('/') + '/'
        self.localhost = Waller(host='127.0.0.1')
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            current_app.logger.info(self.task_id)
            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.project_info = self.taskMdl.get('project_info')

            # copy to a local version
            self.release_version = '{project_id}_{task_id}_{timestamp}'.format(
                project_id=self.project_info['id'],
                task_id=self.task_id,
                timestamp=time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())),
            )
            current_app.logger.info(self.taskMdl)

            self.custom_global_env = {
                'WEBROOT': str(self.project_info['target_root']),
                'VERSION': str(self.release_version),
                'CURRENT_RELEASE': str(self.project_info['target_releases']),
                'BRANCH': str(self.taskMdl.get('branch')),
                'TAG': str(self.taskMdl.get('tag')),
                'COMMIT_ID': str(self.taskMdl.get('commit_id')),
                'PROJECT_NAME': str(self.project_info['name']).replace('"', '').replace("'", '').replace(" ", '_'),
                'PROJECT_ID': str(self.project_info['id']),
                'TASK_NAME': str(self.taskMdl.get('name')).replace('"', '').replace("'", '').replace(" ", '_'),
                'TASK_ID': str(self.task_id),
                'DEPLOY_USER': str(self.taskMdl.get('user_name')),
                'DEPLOY_TIME': str(time.strftime('%Y%m%d-%H:%M:%S', time.localtime(time.time()))),
            }
            if self.project_info['task_vars']:
                task_vars = [i.strip() for i in self.project_info['task_vars'].split('\n') if i.strip() and not i.strip().startswith('#')]
                for var in task_vars:
                    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)

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()
            self.servers = self.project_info['servers_info']

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.local_codebase + str(self.project_name)

        # self.init_repo()

        # start to deploy
        self.console = console
示例#15
0
 def test(self):
     server = '172.20.95.43'
     # server = '172.16.0.231'
     try:
         self.connections[server] = Waller(host=server, user='******')
         self.post_release_service(self.connections[server])
     except Exception as e:
         current_app.logger.exception(e)
         self.errors[server] = e.message
示例#16
0
 def test(self):
     # server = '172.20.95.43'
     server = '172.16.0.231'
     ws_dict = {
         'user': '******',
         'host': 'ip',
         'cmd': 'Going to sleep !!!!!',
         'status': 0,
         'stage': 1,
         'sequence': 1,
         'success': 'Going to sleep !!!!!',
         'error': '',
     }
     emit('console', {
         'event': 'task:console',
         'data': ws_dict
     },
          room=self.task_id)
     socketio.sleep(60)
     ws_dict = {
         'user': '******',
         'host': 'ip',
         'cmd': 'sleep 60',
         'status': 0,
         'stage': 1,
         'sequence': 1,
         'success': 'sleep 60....',
         'error': '',
     }
     emit('console', {
         'event': 'task:console',
         'data': ws_dict
     },
          room=self.task_id)
     socketio.sleep(10)
     ws_dict = {
         'user': '******',
         'host': 'ip',
         'cmd': 'sleep 10',
         'status': 0,
         'stage': 1,
         'sequence': 1,
         'success': 'sleep 10....',
         'error': '',
     }
     emit('console', {
         'event': 'task:console',
         'data': ws_dict
     },
          room=self.task_id)
     try:
         self.connections[server] = Waller(host=server, user='******')
         self.post_release_service(self.connections[server])
     except Exception as e:
         current_app.logger.exception(e)
         self.errors[server] = e.message
示例#17
0
 def __init__(self, task_id=None, project_id=None, websocket=None):
     self.local = Waller(host=current_app.config.get('LOCAL_SERVER_HOST'),
                         user=current_app.config.get('LOCAL_SERVER_USER'),
                         port=current_app.config.get('LOCAL_SERVER_PORT'))
     self.TaskRecord = TaskModel.TaskRecordModel()
     current_app.logger.info('DeploySocketIO.__init__ before')
     emit('message', {'msg': 'DeploySocketIO.__init__'})
     current_app.logger.info('DeploySocketIO.__init__ after')
     # if websocket:
     #     websocket.send_updates(__name__)
     #     self.websocket = websocket
     if task_id:
         self.task_id = task_id
         self.taskMdl = TaskModel.TaskModel().item(self.task_id)
         self.user_id = self.taskMdl.get('user_id')
         self.servers = self.taskMdl.get('servers_info')
         self.task = self.taskMdl.get('target_user')
         self.project_info = self.taskMdl.get('project_info')
     if project_id:
         self.project_id = project_id
         self.project_info = ProjectModel(id=project_id).item()
示例#18
0
    def __init__(self, task_id=None, project_id=None, console=False):
        self.local = Waller(host=current_app.config.get('LOCAL_SERVER_HOST'),
                            user=current_app.config.get('LOCAL_SERVER_USER'),
                            port=current_app.config.get('LOCAL_SERVER_PORT'))
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            TaskModel(id=self.task_id).update(status=TaskModel.status_doing)

            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.task = self.taskMdl.get('target_user')
            self.project_info = self.taskMdl.get('project_info')

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.dir_codebase + str(self.project_name)
示例#19
0
    def walle_deploy(self):
        self.prev_deploy()
        self.deploy()
        self.post_deploy()

        server = '172.16.0.231'
        try:
            self.connections[server] = Waller(
                host=server, user=self.project_info['target_user'])
            self.prev_release(self.connections[server])
            self.release(self.connections[server])
            self.post_release(self.connections[server])
        except Exception, e:
            current_app.logger.exception(e)
            self.errors[server] = e.message
示例#20
0
    def __init__(self, task_id=None, project_id=None, console=False):
        self.local = Waller(host=current_app.config.get('LOCAL_SERVER_HOST'),
                            user=current_app.config.get('LOCAL_SERVER_USER'),
                            port=current_app.config.get('LOCAL_SERVER_PORT'))
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            TaskModel(id=self.task_id).update(status=TaskModel.status_doing)

            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.task = self.taskMdl.get('target_user')
            self.project_info = self.taskMdl.get('project_info')

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.dir_codebase + str(self.project_name)
示例#21
0
class Deployer:
    '''
    序列号
    '''
    stage = '0'

    sequence = 0
    stage_prev_deploy = 'prev_deploy'
    stage_deploy = 'deploy'
    stage_post_deploy = 'post_deploy'

    stage_prev_release = 'prev_release'
    stage_release = 'release'
    stage_post_release = 'post_release'

    task_id = '0'
    user_id = '0'
    taskMdl = None
    TaskRecord = None

    console = False

    version = datetime.now().strftime('%Y%m%d%H%M%s')

    local_codebase, dir_codebase_project, project_name = None, None, None
    dir_release, dir_webroot = None, None

    connections, success, errors = {}, {}, {}
    release_version_tar, release_version = None, None
    local = None

    def __init__(self, task_id=None, project_id=None, console=False):
        self.local_codebase = current_app.config.get('CODE_BASE')
        self.local = Waller(
            host=current_app.config.get('LOCAL_SERVER_HOST'),
            user=current_app.config.get('LOCAL_SERVER_USER'),
            port=current_app.config.get('LOCAL_SERVER_PORT'),
        )
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            current_app.logger.info(self.task_id)
            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.task = self.taskMdl.get('target_user')
            self.project_info = self.taskMdl.get('project_info')

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.local_codebase + str(
            self.project_name)

        self.init_repo()

        # start to deploy
        self.console = console

    def config(self):
        return {
            'task_id': self.task_id,
            'user_id': self.user_id,
            'stage': self.stage,
            'sequence': self.sequence,
            'console': self.console
        }

    def start(self):
        TaskModel().get_by_id(self.task_id).update(
            {'status': TaskModel.status_doing})
        self.taskMdl = TaskModel().item(self.task_id)

    # ===================== fabric ================
    # SocketHandler
    def prev_deploy(self):
        '''
        # TODO
        socketio.sleep(0.001)
        1.代码检出前要做的基础工作
        - 检查 当前用户
        - 检查 python 版本
        - 检查 git 版本
        - 检查 目录是否存在
        - 用户自定义命令

        :return:
        '''
        self.stage = self.stage_prev_deploy
        self.sequence = 1

        # 检查 当前用户
        command = 'whoami'
        current_app.logger.info(command)
        result = self.local.run(command, wenv=self.config())

        # 检查 python 版本
        command = 'python --version'
        result = self.local.run(command, wenv=self.config())

        # 检查 git 版本
        command = 'git --version'
        result = self.local.run(command, wenv=self.config())

        # 检查 目录是否存在
        self.init_repo()

        # TODO to be removed
        command = 'mkdir -p %s' % (self.dir_codebase_project)
        result = self.local.run(command, wenv=self.config())

        # 用户自定义命令
        command = self.project_info['prev_deploy']
        current_app.logger.info(command)
        with self.local.cd(self.dir_codebase_project):
            result = self.local.run(command, wenv=self.config())

    def deploy(self):
        '''
        2.检出代码

        :param project_name:
        :return:
        '''
        self.stage = self.stage_deploy
        self.sequence = 2

        current_app.logger.info('git dir: %s',
                                self.dir_codebase_project + '/.git')
        # 如果项目底下有 .git 目录则认为项目完整,可以直接检出代码
        # TODO 不标准
        if os.path.exists(self.dir_codebase_project + '/.git'):
            with self.local.cd(self.dir_codebase_project):
                command = 'pwd && git pull'
                result = self.local.run(command, wenv=self.config())

        else:
            # 否则当作新项目检出完整代码
            with self.local.cd(self.dir_codebase_project):
                command = 'pwd && git clone %s .' % (
                    self.project_info['repo_url'])
                current_app.logger.info('cd %s  command: %s  ',
                                        self.dir_codebase_project, command)

                result = self.local.run(command, wenv=self.config())

        # copy to a local version
        self.release_version = '%s_%s_%s' % (
            self.project_name, self.task_id,
            time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())))

        with self.local.cd(self.local_codebase):
            command = 'cp -rf %s %s' % (self.dir_codebase_project,
                                        self.release_version)
            current_app.logger.info('cd %s  command: %s  ',
                                    self.dir_codebase_project, command)

            result = self.local.run(command, wenv=self.config())

        # 更新到指定 commit_id
        with self.local.cd(self.local_codebase + self.release_version):
            command = 'git reset -q --hard %s' % (
                self.taskMdl.get('commit_id'))
            result = self.local.run(command, wenv=self.config())

            if result.exited != Code.Ok:
                raise WalleError(Code.shell_git_fail, message=result.stdout)

    def post_deploy(self):
        '''
        3.检出代码后要做的任务
        - 用户自定义操作命令
        - 代码编译
        - 清除日志文件及无用文件
        -
        - 压缩打包
        - 传送到版本库 release
        :return:
        '''
        self.stage = self.stage_post_deploy
        self.sequence = 3

        # 用户自定义命令
        command = self.project_info['post_deploy']
        with self.local.cd(self.local_codebase + self.release_version):
            result = self.local.run(command, wenv=self.config())

        # 压缩打包
        self.release_version_tar = '%s.tgz' % (self.release_version)
        with self.local.cd(self.local_codebase):
            command = 'tar zcf %s %s' % (self.release_version_tar,
                                         self.release_version)
            result = self.local.run(command, wenv=self.config())

    def prev_release(self, waller):
        '''
        4.部署代码到目标机器前做的任务
        - 检查 webroot 父目录是否存在
        :return:
        '''
        self.stage = self.stage_prev_release
        self.sequence = 4

        # 检查 target_releases 父目录是否存在
        command = 'mkdir -p %s' % (self.project_info['target_releases'])
        result = waller.run(command, wenv=self.config())

        # TODO 检查 webroot 父目录是否存在,是否为软链
        # command = 'mkdir -p %s' % (self.project_info['target_root'])
        # result = waller.run(command)
        # current_app.logger.info('command: %s', dir(result))

        # 用户自定义命令
        command = self.project_info['prev_release']
        current_app.logger.info(command)
        with waller.cd(self.project_info['target_releases']):
            result = waller.run(command, wenv=self.config())

        # TODO md5
        # 传送到版本库 release
        current_app.logger.info('/tmp/walle/codebase/' +
                                self.release_version_tar)
        result = waller.put('/tmp/walle/codebase/' + self.release_version_tar,
                            remote=self.project_info['target_releases'],
                            wenv=self.config())
        current_app.logger.info('command: %s', dir(result))

        # 解压
        self.release_untar(waller)

    def release(self, waller):
        '''
        5.部署代码到目标机器做的任务
        - 打包代码 local
        - scp local => remote
        - 解压 remote
        :return:
        '''
        self.stage = self.stage_release
        self.sequence = 5

        with waller.cd(self.project_info['target_releases']):
            # 1. create a tmp link dir
            current_link_tmp_dir = '%s/current-tmp-%s' % (
                self.project_info['target_releases'], self.task_id)
            command = 'ln -sfn %s/%s %s' % (
                self.project_info['target_releases'], self.release_version,
                current_link_tmp_dir)
            result = waller.run(command, wenv=self.config())

            # 2. make a soft link from release to tmp link

            # 3. move tmp link to webroot
            current_link_tmp_dir = '%s/current-tmp-%s' % (
                self.project_info['target_releases'], self.task_id)
            command = 'mv -fT %s %s' % (current_link_tmp_dir,
                                        self.project_info['target_root'])
            result = waller.run(command, wenv=self.config())

    def release_untar(self, waller):
        '''
        解压版本包
        :return:
        '''
        with waller.cd(self.project_info['target_releases']):
            command = 'tar zxf %s' % (self.release_version_tar)
            result = waller.run(command, wenv=self.config())

    def post_release(self, waller):
        '''
        6.部署代码到目标机器后要做的任务
        - 切换软链
        - 重启 nginx
        :return:
        '''
        self.stage = self.stage_post_release
        self.sequence = 6

        # 用户自定义命令
        command = self.project_info['post_release']
        current_app.logger.info(command)
        with waller.cd(self.project_info['target_root']):
            result = waller.run(command, wenv=self.config())

        self.post_release_service(waller)

    def post_release_service(self, waller):
        '''
        代码部署完成后,服务启动工作,如: nginx重启
        :param connection:
        :return:
        '''
        with waller.cd(self.project_info['target_root']):
            command = 'sudo service nginx restart'
            result = waller.run(command, wenv=self.config())

    def list_tag(self):
        with self.local.cd(self.dir_codebase_project):
            command = 'git tag -l'
            result = self.local.run(command, pty=False, wenv=self.config())
            tags = result.stdout.strip()
            tags = tags.split('\n')
            return [color_clean(tag.strip()) for tag in tags]

        return None

    def list_branch(self):
        with self.local.cd(self.dir_codebase_project):
            command = 'git pull'
            result = self.local.run(command, wenv=self.config())

            if result.exited != Code.Ok:
                raise WalleError(Code.shell_git_pull_fail,
                                 message=result.stdout)

            current_app.logger.info(self.dir_codebase_project)

            command = 'git branch -r'
            result = self.local.run(command, pty=False, wenv=self.config())

            # if result.exited != Code.Ok:
            #     raise WalleError(Code.shell_run_fail)

            # TODO 三种可能: false, error, success
            branches = result.stdout.strip()
            branches = branches.split('\n')
            # 去除 origin/HEAD -> 当前指向
            # 去除远端前缀
            branches = [
                branch.strip().lstrip('origin/') for branch in branches
                if not branch.strip().startswith('origin/HEAD')
            ]
            return branches

        return None

    def list_commit(self, branch):
        with self.local.cd(self.dir_codebase_project):
            command = 'git checkout %s && git pull' % (branch)
            self.local.run(command, wenv=self.config())

            command = 'git log -50 --pretty="%h #@_@# %an #@_@# %s"'
            result = self.local.run(command, pty=False, wenv=self.config())
            current_app.logger.info(result.stdout)

            commit_log = result.stdout.strip()
            current_app.logger.info(commit_log)
            commit_list = commit_log.split('\n')
            commits = []
            for commit in commit_list:
                if not re.search('^.+ #@_@# .+ #@_@# .*$', commit):
                    continue

                commit_dict = commit.split(' #@_@# ')
                current_app.logger.info(commit_dict)
                commits.append({
                    'id': commit_dict[0],
                    'name': commit_dict[1],
                    'message': commit_dict[2],
                })

            return commits

        # TODO
        return None

    def init_repo(self):
        if not os.path.exists(self.dir_codebase_project):
            # 检查 目录是否存在
            command = 'mkdir -p %s' % (self.dir_codebase_project)
            # TODO remove
            current_app.logger.info(command)
            self.local.run(command, wenv=self.config())

        with self.local.cd(self.dir_codebase_project):
            is_git_dir = self.local.run('git status',
                                        exception=False,
                                        wenv=self.config())

        if is_git_dir.exited != Code.Ok:
            # 否则当作新项目检出完整代码
            # 检查 目录是否存在
            command = 'rm -rf %s' % (self.dir_codebase_project)
            self.local.run(command, wenv=self.config())

            command = 'git clone %s %s' % (self.project_info['repo_url'],
                                           self.dir_codebase_project)
            current_app.logger.info('cd %s  command: %s  ',
                                    self.dir_codebase_project, command)

            result = self.local.run(command, wenv=self.config())
            if result.exited != Code.Ok:
                raise WalleError(Code.shell_git_init_fail,
                                 message=result.stdout)

    def end(self, success=True):
        status = TaskModel.status_success if success else TaskModel.status_fail
        current_app.logger.info('success:%s, status:%s' % (success, status))
        TaskModel().get_by_id(self.task_id).update({'status': status})

    def walle_deploy(self):
        self.start()

        try:
            self.prev_deploy()
            self.deploy()
            self.post_deploy()

            is_all_servers_success = True
            for server_info in self.servers:
                server = server_info['host']
                try:
                    self.connections[server] = Waller(
                        host=server, user=self.project_info['target_user'])
                    self.prev_release(self.connections[server])
                    self.release(self.connections[server])
                    self.post_release(self.connections[server])
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.error(e)
                    self.errors[server] = e.message
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)
            emit('fail', {
                'event': 'console',
                'data': {
                    'message': Code.code_msg[Code.deploy_fail]
                }
            },
                 room=self.task_id)

        return {'success': self.success, 'errors': self.errors}
示例#22
0
class Deployer:
    '''
    序列号
    '''
    stage = 'init'

    sequence = 0
    stage_prev_deploy = 'prev_deploy'
    stage_deploy = 'deploy'
    stage_post_deploy = 'post_deploy'

    stage_prev_release = 'prev_release'
    stage_release = 'release'
    stage_post_release = 'post_release'

    task_id = '0'
    user_id = '0'
    taskMdl = None
    TaskRecord = None

    console = False
    custom_global_env = {}

    version = datetime.now().strftime('%Y%m%d%H%M%S')

    local_codebase, dir_codebase_project, project_name = None, None, None
    dir_release, dir_webroot = None, None

    connections, success, errors = {}, {}, {}
    release_version_tar, previous_release_version, release_version = None, None, None
    local = None

    def __init__(self, task_id=None, project_id=None, console=False):
        self.local_codebase = current_app.config.get('CODE_BASE').rstrip('/') + '/'
        self.localhost = Waller(host='127.0.0.1')
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            current_app.logger.info(self.task_id)
            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.project_info = self.taskMdl.get('project_info')

            # copy to a local version
            self.release_version = '{project_id}_{task_id}_{timestamp}'.format(
                project_id=self.project_info['id'],
                task_id=self.task_id,
                timestamp=time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())),
            )
            current_app.logger.info(self.taskMdl)

            self.custom_global_env = {
                'WEBROOT': str(self.project_info['target_root']),
                'CURRENT_RELEASE': str(self.release_version),
                'BRANCH': str(self.taskMdl.get('branch')),
                'TAG': str(self.taskMdl.get('tag')),
                'COMMIT_ID': str(self.taskMdl.get('commit_id')),
                'PROJECT_NAME': '"{}"'.format(self.project_info['name']),
                'PROJECT_ID': str(self.project_info['id']),
                'TASK_NAME': '"{}"'.format(self.taskMdl.get('name')),
                'TASK_ID': str(self.task_id),
                'DEPLOY_USER': str(self.taskMdl.get('user_name')),
                'DEPLOY_TIME': str(time.strftime('%Y%m%d-%H:%M:%S', time.localtime(time.time()))),
            }
            if self.project_info['task_vars']:
                task_vars = [i.strip() for i in self.project_info['task_vars'].split('\n') if i.strip() and not i.strip().startswith('#')]
                for var in task_vars:
                    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)

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()
            self.servers = self.project_info['servers_info']

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.local_codebase + str(self.project_name)

        # self.init_repo()

        # start to deploy
        self.console = console

    def config(self, console=None):
        return {
            'task_id': self.task_id,
            'user_id': self.user_id,
            'stage': self.stage,
            'sequence': self.sequence,
            'console': console if console is not None else self.console
        }

    def start(self):
        RecordModel().query.filter_by(task_id=self.task_id).delete()
        TaskModel().get_by_id(self.task_id).update({'status': TaskModel.status_doing})
        self.taskMdl = TaskModel().item(self.task_id)

    # ===================== fabric ================
    # SocketHandler
    def prev_deploy(self):
        '''
        # TODO
        socketio.sleep(0.001)
        1.代码检出前要做的基础工作
        - 检查 当前用户
        - 检查 python 版本
        - 检查 git 版本
        - 检查 目录是否存在
        - 用户自定义命令

        :return:
        '''
        self.stage = self.stage_prev_deploy
        self.sequence = 1

        # 检查 目录是否存在
        self.init_repo()

        # 用户自定义命令
        commands = self.project_info['prev_deploy']
        if commands:
            for command in commands.split('\n'):
                if command.strip().startswith('#') or not command.strip():
                    continue
                with self.localhost.cd(self.dir_codebase_project):
                    result = self.localhost.local(command, wenv=self.config())

    def deploy(self):
        '''
        2.检出代码

        :param project_name:
        :return:
        '''
        self.stage = self.stage_deploy
        self.sequence = 2
#
#        # copy to a local version
#        self.release_version = '%s_%s_%s' % (
#            self.project_name, self.task_id, time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())))

        with self.localhost.cd(self.local_codebase):
            command = 'cp -rf %s %s' % (self.dir_codebase_project, self.release_version)
            current_app.logger.info('cd %s  command: %s  ', self.dir_codebase_project, command)

            result = self.localhost.local(command, wenv=self.config())

        # 更新到指定 commit_id
        with self.localhost.cd(self.local_codebase + self.release_version):
            command = 'git reset -q --hard %s' % (self.taskMdl.get('commit_id'))
            result = self.localhost.local(command, wenv=self.config())

            if result.exited != Code.Ok:
                raise WalleError(Code.shell_git_fail, message=result.stdout)

    def post_deploy(self):

        '''
        3.检出代码后要做的任务
        - 用户自定义操作命令
        - 代码编译
        - 清除日志文件及无用文件
        -
        - 压缩打包
        - 传送到版本库 release
        :return:
        '''
        self.stage = self.stage_post_deploy
        self.sequence = 3

        # 用户自定义命令
        commands = self.project_info['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_codebase + self.release_version):
                    result = self.localhost.local(command, wenv=self.config())

        # 压缩打包
        # 排除文件发布
        self.release_version_tar = '%s.tgz' % (self.release_version)
        with self.localhost.cd(self.local_codebase):
            if self.project_info['is_include']:
                files = includes_format(self.release_version, self.project_info['excludes'])
            else:
                files = excludes_format(self.release_version, self.project_info['excludes'])
            command = 'tar zcf %s/%s %s' % (self.local_codebase.rstrip('/'), self.release_version_tar, files)
            result = self.localhost.local(command, wenv=self.config())

    def prev_release(self, waller):
        '''
        4.部署代码到目标机器前做的任务
        - 检查 webroot 父目录是否存在
        :return:
        '''
        self.stage = self.stage_prev_release
        self.sequence = 4

        # 检查 target_releases 父目录是否存在
        command = 'mkdir -p %s' % (self.project_info['target_releases'])
        result = waller.run(command, wenv=self.config())

        # TODO md5
        # 传送到版本库 release
        result = waller.put(self.local_codebase + self.release_version_tar,
                            remote=self.project_info['target_releases'], wenv=self.config())
        current_app.logger.info('command: %s', dir(result))

        # 解压
        self.release_untar(waller)

        # 用户自定义命令
        self.prev_release_custom(waller)

    def prev_release_custom(self, waller):
        # 用户自定义命令
        commands = self.project_info['prev_release']
        if commands:
            for command in commands.split('\n'):
                if command.strip().startswith('#') or not command.strip():
                    continue
                # TODO
                target_release_version = "%s/%s" % (self.project_info['target_releases'], self.release_version)
                with waller.cd(target_release_version):
                    result = waller.run(command, wenv=self.config())

    def release(self, waller):
        '''
        5.部署代码到目标机器做的任务
        - 打包代码 local
        - scp local => remote
        - 解压 remote
        :return:
        '''
        self.stage = self.stage_release
        self.sequence = 5

        with waller.cd(self.project_info['target_releases']):
            # 0. get previous link
            command = '[ -L %s ] && readlink %s || echo ""' % (self.project_info['target_root'], self.project_info['target_root'])
            result = waller.run(command, wenv=self.config(console=False))
            self.previous_release_version = os.path.basename(result.stdout).strip()

            # 1. create a tmp link dir
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'ln -sfn %s/%s %s' % (
                self.project_info['target_releases'], self.release_version, current_link_tmp_dir)
            result = waller.run(command, wenv=self.config())

            # 2. make a soft link from release to tmp link

            # 3. move tmp link to webroot
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
            result = waller.run(command, wenv=self.config())

    def rollback(self, waller):
        '''
        5.部署代码到目标机器做的任务
        - 恢复旧版本
        :return:
        '''
        self.stage = self.stage_release
        self.sequence = 5

        with waller.cd(self.project_info['target_releases']):
            # 0. get previous link
            command = '[ -L %s ] && readlink %s || echo ""' % (self.project_info['target_root'], self.project_info['target_root'])
            result = waller.run(command, wenv=self.config(console=False))
            self.previous_release_version = os.path.basename(result.stdout)

            # 1. create a tmp link dir
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'ln -sfn %s/%s %s' % (
                self.project_info['target_releases'], self.release_version, current_link_tmp_dir)
            result = waller.run(command, wenv=self.config())

            # 2. make a soft link from release to tmp link

            # 3. move tmp link to webroot
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
            result = waller.run(command, wenv=self.config())

    def release_untar(self, waller):
        '''
        解压版本包
        :return:
        '''
        with waller.cd(self.project_info['target_releases']):
            command = 'tar zxf %s' % (self.release_version_tar)
            result = waller.run(command, wenv=self.config())

    def post_release(self, waller):
        '''
        6.部署代码到目标机器后要做的任务
        - 切换软链
        - 重启 nginx
        :return:
        '''
        self.stage = self.stage_post_release
        self.sequence = 6
        # 用户自定义命令
        commands = self.project_info['post_release']
        if commands:
            for command in commands.split('\n'):
                if command.strip().startswith('#') or not command.strip():
                    continue
                # TODO
                with waller.cd(self.project_info['target_root']):
                    pty = False if command.find('nohup') >= 0 else True
                    result = waller.run(command, wenv=self.config(), pty=pty)
        # 个性化,用户重启的不一定是NGINX,可能是tomcat, apache, php-fpm等
        # self.post_release_service(waller)

    def post_release_service(self, waller):
        '''
        代码部署完成后,服务启动工作,如: nginx重启
        :param connection:
        :return:
        '''
        with waller.cd(self.project_info['target_root']):
            command = 'sudo service nginx restart'
            result = waller.run(command, wenv=self.config())


    def project_detection(self):
        errors = []
        # LOCAL_SERVER_USER => git

        # LOCAL_SERVER_USER => target_servers
        for server_info in self.servers:
            waller = Waller(host=server_info['host'], user=server_info['user'], port=server_info['port'])
            result = waller.run('id', exception=False, wenv=self.config())
            if result.failed:
                errors.append({
                    'title': '远程目标机器免密码登录失败',
                    'why': '远程目标机器:%s 错误:%s' % (server_info['host'], result.stdout),
                    'how': '在宿主机中配置免密码登录,把宿主机用户%s的~/.ssh/ssh_rsa.pub添加到远程目标机器用户%s的~/.ssh/authorized_keys。了解更多:http://walle-web.io/docs/troubleshooting.html' % (
                    pwd.getpwuid(os.getuid())[0], server_info['host']),
                })

            # maybe this is no webroot's parent dir
            command = '[ -d {webroot} ] || mkdir -p {webroot}'.format(webroot=os.path.basename(self.project_info['target_root']))
            result = waller.run(command, exception=False, wenv=self.config(console=False))

                # 检查 webroot 父目录是否存在,是否为软链
            command = '[ -L "%s" ] && echo "true" || echo "false"' % (self.project_info['target_root'])
            result = waller.run(command, exception=False, wenv=self.config())
            if result.stdout == 'false':
                errors.append({
                    'title': '远程目标机器webroot不能是已建好的目录',
                    'why': '远程目标机器%s webroot不能是已存在的目录,必须为软链接,你不必新建,walle会自行创建。' % (server_info['host']),
                    'how': '手工删除远程目标机器:%s webroot目录:%s' % (server_info['host'], self.project_info['target_root']),
                })

        # remote release directory
        return errors

    def list_tag(self):
        self.init_repo()

        with self.localhost.cd(self.dir_codebase_project):
            command = 'git tag -l'
            result = self.localhost.local(command, pty=False, wenv=self.config())
            tags = result.stdout.strip()
            tags = tags.split('\n')
            return [color_clean(tag.strip()) for tag in tags]

        return None

    def list_branch(self):
        self.init_repo()

        with self.localhost.cd(self.dir_codebase_project):
            command = 'git pull'
            result = self.localhost.local(command, wenv=self.config())

            if result.exited != Code.Ok:
                raise WalleError(Code.shell_git_pull_fail, message=result.stdout)

            current_app.logger.info(self.dir_codebase_project)

            command = 'git branch -r'
            result = self.localhost.local(command, pty=False, wenv=self.config())

            # if result.exited != Code.Ok:
            #     raise WalleError(Code.shell_run_fail)

            # TODO 三种可能: false, error, success
            branches = result.stdout.strip()
            branches = branches.split('\n')
            # 去除 origin/HEAD -> 当前指向
            # 去除远端前缀
            branches = [branch.strip().lstrip('origin/') for branch in branches if
                        not branch.strip().startswith('origin/HEAD')]
            return branches

        return None

    def list_commit(self, branch):
        self.init_repo()
        with self.localhost.cd(self.dir_codebase_project):
            command = 'git checkout %s && git pull' % (branch)
            self.localhost.local(command, wenv=self.config())

            command = 'git log -50 --pretty="%h #@_@# %an #@_@# %s"'
            result = self.localhost.local(command, pty=False, wenv=self.config())
            current_app.logger.info(result.stdout)

            commit_log = result.stdout.strip()
            current_app.logger.info(commit_log)
            commit_list = commit_log.split('\n')
            commits = []
            for commit in commit_list:
                if not re.search('^.+ #@_@# .+ #@_@# .*$', commit):
                    continue

                commit_dict = commit.split(' #@_@# ')
                current_app.logger.info(commit_dict)
                commits.append({
                    'id': commit_dict[0],
                    'name': commit_dict[1],
                    'message': commit_dict[2],
                })

            return commits

        # TODO
        return None

    def init_repo(self):
        if not os.path.exists(self.dir_codebase_project):
            # 检查 目录是否存在
            command = 'mkdir -p %s' % (self.dir_codebase_project)
            self.localhost.local(command, wenv=self.config())

        with self.localhost.cd(self.dir_codebase_project):
            is_git_dir = self.localhost.local('[ -d ".git" ] && git status', exception=False, wenv=self.config())

        if is_git_dir.exited != Code.Ok:
            # 否则当作新项目检出完整代码
            # 检查 目录是否存在
            command = 'rm -rf %s' % (self.dir_codebase_project)
            self.localhost.local(command, wenv=self.config())

            command = 'git clone %s %s' % (self.project_info['repo_url'], self.dir_codebase_project)
            current_app.logger.info('cd %s  command: %s  ' % (self.dir_codebase_project, command))

            result = self.localhost.local(command, wenv=self.config())
            if result.exited != Code.Ok:
                raise WalleError(Code.shell_git_init_fail, message=result.stdout)

    def logs(self):
        return RecordModel().fetch(task_id=self.task_id)

    def end(self, success=True, update_status=True):
        if update_status:
            status = TaskModel.status_success if success else TaskModel.status_fail
            current_app.logger.info('success:%s, status:%s' % (success, status))
            TaskModel().get_by_id(self.task_id).update({
                'status': status,
                'link_id': self.release_version,
                'ex_link_id': self.previous_release_version,
            })

            notice_info = {
                'title': '',
                'username': current_user.username,
                'project_name': self.project_info['name'],
                'task_name': '%s ([%s](%s))' % (self.taskMdl.get('name'), self.task_id, Notice.task_url(project_name=self.project_info['name'], task_id=self.task_id)),
                'branch': self.taskMdl.get('branch'),
                'commit': self.taskMdl.get('commit_id'),
                'is_branch': self.project_info['repo_mode'],
            }
            notice = Notice.create(self.project_info['notice_type'])
            if success:
                notice_info['title'] = '上线部署成功'
                notice.deploy_task(project_info=self.project_info, notice_info=notice_info)
            else:
                notice_info['title'] = '上线部署失败'
                notice.deploy_task(project_info=self.project_info, notice_info=notice_info)

        if success:
            emit('success', {'event': 'finish', 'data': {'message': '部署完成,辛苦了,为你的努力喝彩!'}}, room=self.task_id)
        else:
            emit('fail', {'event': 'finish', 'data': {'message': Code.code_msg[Code.deploy_fail]}}, room=self.task_id)

    def walle_deploy(self):
        self.start()

        try:
            self.prev_deploy()
            self.deploy()
            self.post_deploy()

            is_all_servers_success = True
            for server_info in self.servers:
                host = server_info['host']
                try:
                    waller = Waller(host=host, user=server_info['user'], port=server_info['port'], inline_ssh_env=True)
                    waller.init_env(env=self.custom_global_env)

                    self.connections[host] = waller
                    self.prev_release(self.connections[host])
                    self.release(self.connections[host])
                    self.post_release(self.connections[host])
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_success, host=host,
                                              user=server_info['user'], command='')
                    emit('success', {'event': 'finish', 'data': {'host': host, 'message': host + ' 部署完成!'}}, room=self.task_id)
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.error(e)
                    self.errors[host] = e.message
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_fail, host=host,
                                              user=server_info['user'], command='')
                    emit('fail', {'event': 'finish', 'data': {'host': host, 'message': host + Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)

        return {'success': self.success, 'errors': self.errors}

    def walle_rollback(self):
        self.start()

        try:
            is_all_servers_success = True
            self.release_version = self.taskMdl.get('link_id')
            for server_info in self.servers:
                host = server_info['host']
                try:
                    waller = Waller(host=host, user=server_info['user'], port=server_info['port'], inline_ssh_env=True)
                    waller.init_env(env=self.custom_global_env)

                    self.connections[host] = waller                   
                    self.prev_release_custom(self.connections[host])
                    self.release(self.connections[host])
                    self.post_release(self.connections[host])
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_success, host=host,
                                              user=server_info['user'], command='')
                    emit('success', {'event': 'finish', 'data': {'host': host, 'message': host + ' 部署完成!'}}, room=self.task_id)
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.error(e)
                    self.errors[host] = e.message
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_fail, host=host,
                                              user=server_info['user'], command='')
                    emit('fail', {'event': 'finish', 'data': {'host': host, 'message': host + Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)

        return {'success': self.success, 'errors': self.errors}
示例#23
0
class Deployer:
    '''
    序列号
    '''
    stage = 'init'

    sequence = 0
    stage_prev_deploy = 'prev_deploy'
    stage_deploy = 'deploy'
    stage_post_deploy = 'post_deploy'

    stage_prev_release = 'prev_release'
    stage_release = 'release'
    stage_post_release = 'post_release'

    task_id = '0'
    user_id = '0'
    taskMdl = None
    TaskRecord = None

    console = False

    version = datetime.now().strftime('%Y%m%d%H%M%S')

    local_codebase, dir_codebase_project, project_name = None, None, None
    dir_release, dir_webroot = None, None

    connections, success, errors = {}, {}, {}
    release_version_tar, previous_release_version, release_version = None, None, None
    local = None

    def __init__(self, task_id=None, project_id=None, console=False):
        self.local_codebase = current_app.config.get('CODE_BASE')
        self.localhost = Waller(host='127.0.0.1')
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            current_app.logger.info(self.task_id)
            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.project_info = self.taskMdl.get('project_info')

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()
            self.servers = self.project_info['servers_info']

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.local_codebase + str(self.project_name)

        # self.init_repo()

        # start to deploy
        self.console = console

    def config(self, console=None):
        return {
            'task_id': self.task_id,
            'user_id': self.user_id,
            'stage': self.stage,
            'sequence': self.sequence,
            'console': console if console is not None else self.console
        }

    def start(self):
        RecordModel().query.filter_by(task_id=self.task_id).delete()
        TaskModel().get_by_id(self.task_id).update({'status': TaskModel.status_doing})
        self.taskMdl = TaskModel().item(self.task_id)

    # ===================== fabric ================
    # SocketHandler
    def prev_deploy(self):
        '''
        # TODO
        socketio.sleep(0.001)
        1.代码检出前要做的基础工作
        - 检查 当前用户
        - 检查 python 版本
        - 检查 git 版本
        - 检查 目录是否存在
        - 用户自定义命令

        :return:
        '''
        self.stage = self.stage_prev_deploy
        self.sequence = 1

        # 检查 目录是否存在
        self.init_repo()

        # 用户自定义命令
        command = self.project_info['prev_deploy']
        if command:
            current_app.logger.info(command)
            with self.localhost.cd(self.dir_codebase_project):
                result = self.localhost.local(command, wenv=self.config())

    def deploy(self):
        '''
        2.检出代码

        :param project_name:
        :return:
        '''
        self.stage = self.stage_deploy
        self.sequence = 2

        # copy to a local version
        self.release_version = '%s_%s_%s' % (
            self.project_name, self.task_id, time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())))

        with self.localhost.cd(self.local_codebase):
            command = 'cp -rf %s %s' % (self.dir_codebase_project, self.release_version)
            current_app.logger.info('cd %s  command: %s  ', self.dir_codebase_project, command)

            result = self.localhost.local(command, wenv=self.config())

        # 更新到指定 commit_id
        with self.localhost.cd(self.local_codebase + self.release_version):
            command = 'git reset -q --hard %s' % (self.taskMdl.get('commit_id'))
            result = self.localhost.local(command, wenv=self.config())

            if result.exited != Code.Ok:
                raise WalleError(Code.shell_git_fail, message=result.stdout)

    def post_deploy(self):

        '''
        3.检出代码后要做的任务
        - 用户自定义操作命令
        - 代码编译
        - 清除日志文件及无用文件
        -
        - 压缩打包
        - 传送到版本库 release
        :return:
        '''
        self.stage = self.stage_post_deploy
        self.sequence = 3

        # 用户自定义命令
        command = self.project_info['post_deploy']
        if command:
            with self.localhost.cd(self.local_codebase + self.release_version):
                result = self.localhost.local(command, wenv=self.config())

        # 压缩打包
        # 排除文件发布
        self.release_version_tar = '%s.tgz' % (self.release_version)
        with self.localhost.cd(self.local_codebase):
            excludes = excludes_format(self.project_info['excludes'])
            command = 'tar zcf  %s %s %s' % (self.release_version_tar, excludes, self.release_version)
            result = self.localhost.local(command, wenv=self.config())

        # # 指定文件发布
        # self.release_version_tar = '%s.tgz' % (self.release_version)
        # with self.localhost.cd(self.local_codebase):
        #     excludes = suffix_format(self.dir_codebase_project, self.project_info['excludes'])
        #     command = 'tar zcf  %s %s %s' % (self.release_version_tar, excludes, self.release_version)
        #     result = self.local.run(command, wenv=self.config())

    def prev_release(self, waller):
        '''
        4.部署代码到目标机器前做的任务
        - 检查 webroot 父目录是否存在
        :return:
        '''
        self.stage = self.stage_prev_release
        self.sequence = 4

        # 检查 target_releases 父目录是否存在
        command = 'mkdir -p %s' % (self.project_info['target_releases'])
        result = waller.run(command, wenv=self.config())

        # TODO md5
        # 传送到版本库 release
        result = waller.put(self.local_codebase + self.release_version_tar,
                            remote=self.project_info['target_releases'], wenv=self.config())
        current_app.logger.info('command: %s', dir(result))

        # 解压
        self.release_untar(waller)

        # 用户自定义命令
        self.prev_release_custom(waller)

    def prev_release_custom(self, waller):
        # 用户自定义命令
        command = self.project_info['prev_release']
        if command:
            current_app.logger.info(command)
            # TODO
            target_release_version = "%s/%s" % (self.project_info['target_releases'], self.release_version)
            with waller.cd(target_release_version):
                result = waller.run(command, wenv=self.config())

    def release(self, waller):
        '''
        5.部署代码到目标机器做的任务
        - 打包代码 local
        - scp local => remote
        - 解压 remote
        :return:
        '''
        self.stage = self.stage_release
        self.sequence = 5

        with waller.cd(self.project_info['target_releases']):
            # 0. get previous link
            command = '[ -L %s ] && readlink %s || echo ""' % (self.project_info['target_root'], self.project_info['target_root'])
            result = waller.run(command, wenv=self.config(console=False))
            self.previous_release_version = os.path.basename(result.stdout).strip()

            # 1. create a tmp link dir
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'ln -sfn %s/%s %s' % (
                self.project_info['target_releases'], self.release_version, current_link_tmp_dir)
            result = waller.run(command, wenv=self.config())

            # 2. make a soft link from release to tmp link

            # 3. move tmp link to webroot
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
            result = waller.run(command, wenv=self.config())

    def rollback(self, waller):
        '''
        5.部署代码到目标机器做的任务
        - 恢复旧版本
        :return:
        '''
        self.stage = self.stage_release
        self.sequence = 5

        with waller.cd(self.project_info['target_releases']):
            # 0. get previous link
            command = '[ -L %s ] && readlink %s || echo ""' % (self.project_info['target_root'], self.project_info['target_root'])
            result = waller.run(command, wenv=self.config(console=False))
            self.previous_release_version = os.path.basename(result.stdout)

            # 1. create a tmp link dir
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'ln -sfn %s/%s %s' % (
                self.project_info['target_releases'], self.release_version, current_link_tmp_dir)
            result = waller.run(command, wenv=self.config())

            # 2. make a soft link from release to tmp link

            # 3. move tmp link to webroot
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
            result = waller.run(command, wenv=self.config())

    def release_untar(self, waller):
        '''
        解压版本包
        :return:
        '''
        with waller.cd(self.project_info['target_releases']):
            command = 'tar zxf %s' % (self.release_version_tar)
            result = waller.run(command, wenv=self.config())

    def post_release(self, waller):
        '''
        6.部署代码到目标机器后要做的任务
        - 切换软链
        - 重启 nginx
        :return:
        '''
        self.stage = self.stage_post_release
        self.sequence = 6

        # 用户自定义命令
        command = self.project_info['post_release']
        if command:
            current_app.logger.info(command)
            with waller.cd(self.project_info['target_root']):
                result = waller.run(command, wenv=self.config())

        # 个性化,用户重启的不一定是NGINX,可能是tomcat, apache, php-fpm等
        # self.post_release_service(waller)

    def post_release_service(self, waller):
        '''
        代码部署完成后,服务启动工作,如: nginx重启
        :param connection:
        :return:
        '''
        with waller.cd(self.project_info['target_root']):
            command = 'sudo service nginx restart'
            result = waller.run(command, wenv=self.config())


    def project_detection(self):
        errors = []
        # LOCAL_SERVER_USER => git

        # LOCAL_SERVER_USER => target_servers
        for server_info in self.servers:
            waller = Waller(host=server_info['host'], user=server_info['user'], port=server_info['port'])
            result = waller.run('id', exception=False, wenv=self.config())
            if result.failed:
                errors.append({
                    'title': '远程目标机器免密码登录失败',
                    'why': '远程目标机器:%s 错误:%s' % (server_info['host'], result.stdout),
                    'how': '在宿主机中配置免密码登录,把宿主机用户%s的~/.ssh/ssh_rsa.pub添加到远程目标机器用户%s的~/.ssh/authorized_keys。了解更多:http://walle-web.io/docs/troubleshooting.html' % (
                    pwd.getpwuid(os.getuid())[0], server_info['host']),
                })

            # maybe this is no webroot's parent dir
            command = '[ -d {webroot} ] || mkdir -p {webroot}'.format(webroot=os.path.basename(self.project_info['target_root']))
            result = waller.run(command, exception=False, wenv=self.config(console=False))

                # 检查 webroot 父目录是否存在,是否为软链
            command = '[ -L "%s" ] && echo "true" || echo "false"' % (self.project_info['target_root'])
            result = waller.run(command, exception=False, wenv=self.config())
            if result.stdout == 'false':
                errors.append({
                    'title': '远程目标机器webroot不能是已建好的目录',
                    'why': '远程目标机器%s webroot不能是已存在的目录,必须为软链接,你不必新建,walle会自行创建。' % (server_info['host']),
                    'how': '手工删除远程目标机器:%s webroot目录:%s' % (server_info['host'], self.project_info['target_root']),
                })

        # remote release directory
        return errors

    def list_tag(self):
        self.init_repo()

        with self.localhost.cd(self.dir_codebase_project):
            command = 'git tag -l'
            result = self.localhost.local(command, pty=False, wenv=self.config())
            tags = result.stdout.strip()
            tags = tags.split('\n')
            return [color_clean(tag.strip()) for tag in tags]

        return None

    def list_branch(self):
        self.init_repo()

        with self.localhost.cd(self.dir_codebase_project):
            command = 'git pull'
            result = self.localhost.local(command, wenv=self.config())

            if result.exited != Code.Ok:
                raise WalleError(Code.shell_git_pull_fail, message=result.stdout)

            current_app.logger.info(self.dir_codebase_project)

            command = 'git branch -r'
            result = self.localhost.local(command, pty=False, wenv=self.config())

            # if result.exited != Code.Ok:
            #     raise WalleError(Code.shell_run_fail)

            # TODO 三种可能: false, error, success
            branches = result.stdout.strip()
            branches = branches.split('\n')
            # 去除 origin/HEAD -> 当前指向
            # 去除远端前缀
            branches = [branch.strip().lstrip('origin/') for branch in branches if
                        not branch.strip().startswith('origin/HEAD')]
            return branches

        return None

    def list_commit(self, branch):
        self.init_repo()
        with self.localhost.cd(self.dir_codebase_project):
            command = 'git checkout %s && git pull' % (branch)
            self.localhost.local(command, wenv=self.config())

            command = 'git log -50 --pretty="%h #@_@# %an #@_@# %s"'
            result = self.localhost.local(command, pty=False, wenv=self.config())
            current_app.logger.info(result.stdout)

            commit_log = result.stdout.strip()
            current_app.logger.info(commit_log)
            commit_list = commit_log.split('\n')
            commits = []
            for commit in commit_list:
                if not re.search('^.+ #@_@# .+ #@_@# .*$', commit):
                    continue

                commit_dict = commit.split(' #@_@# ')
                current_app.logger.info(commit_dict)
                commits.append({
                    'id': commit_dict[0],
                    'name': commit_dict[1],
                    'message': commit_dict[2],
                })

            return commits

        # TODO
        return None

    def init_repo(self):
        if not os.path.exists(self.dir_codebase_project):
            # 检查 目录是否存在
            command = 'mkdir -p %s' % (self.dir_codebase_project)
            self.localhost.local(command, wenv=self.config())

        with self.localhost.cd(self.dir_codebase_project):
            is_git_dir = self.localhost.local('[ -d ".git" ] && git status', exception=False, wenv=self.config())

        if is_git_dir.exited != Code.Ok:
            # 否则当作新项目检出完整代码
            # 检查 目录是否存在
            command = 'rm -rf %s' % (self.dir_codebase_project)
            self.localhost.local(command, wenv=self.config())

            command = 'git clone %s %s' % (self.project_info['repo_url'], self.dir_codebase_project)
            current_app.logger.info('cd %s  command: %s  ' % (self.dir_codebase_project, command))

            result = self.localhost.local(command, wenv=self.config())
            if result.exited != Code.Ok:
                raise WalleError(Code.shell_git_init_fail, message=result.stdout)

    def logs(self):
        return RecordModel().fetch(task_id=self.task_id)

    def end(self, success=True, update_status=True):
        if update_status:
            status = TaskModel.status_success if success else TaskModel.status_fail
            current_app.logger.info('success:%s, status:%s' % (success, status))
            TaskModel().get_by_id(self.task_id).update({
                'status': status,
                'link_id': self.release_version,
                'ex_link_id': self.previous_release_version,
            })

            notice_info = {
                'title': '',
                'username': current_user.username,
                'project_name': self.project_info['name'],
                'task_name': '%s ([%s](%s))' % (self.taskMdl.get('name'), self.task_id, Notice.task_url(project_name=self.project_info['name'], task_id=self.task_id)),
                'branch': self.taskMdl.get('branch'),
                'commit': self.taskMdl.get('commit_id'),
                'is_branch': self.project_info['repo_mode'],
            }
            notice = Notice.create(self.project_info['notice_type'])
            if success:
                notice_info['title'] = '上线部署成功'
                notice.deploy_task(project_info=self.project_info, notice_info=notice_info)
            else:
                notice_info['title'] = '上线部署失败'
                notice.deploy_task(project_info=self.project_info, notice_info=notice_info)

        if success:
            emit('success', {'event': 'finish', 'data': {'message': '部署完成,辛苦了,为你的努力喝彩!'}}, room=self.task_id)
        else:
            emit('fail', {'event': 'finish', 'data': {'message': Code.code_msg[Code.deploy_fail]}}, room=self.task_id)

    def walle_deploy(self):
        self.start()

        try:
            self.prev_deploy()
            self.deploy()
            self.post_deploy()

            is_all_servers_success = True
            for server_info in self.servers:
                host = server_info['host']
                try:
                    self.connections[host] = Waller(host=host, user=server_info['user'], port=server_info['port'])
                    self.prev_release(self.connections[host])
                    self.release(self.connections[host])
                    self.post_release(self.connections[host])
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_success, host=host,
                                              user=server_info['user'], command='')
                    emit('success', {'event': 'finish', 'data': {'host': host, 'message': host + ' 部署完成!'}}, room=self.task_id)
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.error(e)
                    self.errors[host] = e.message
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_fail, host=host,
                                              user=server_info['user'], command='')
                    emit('fail', {'event': 'finish', 'data': {'host': host, 'message': host + Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)

        return {'success': self.success, 'errors': self.errors}

    def walle_rollback(self):
        self.start()

        try:
            is_all_servers_success = True
            self.release_version = self.taskMdl.get('link_id')
            for server_info in self.servers:
                host = server_info['host']
                try:
                    self.connections[host] = Waller(host=host, user=server_info['user'], port=server_info['port'])
                    self.prev_release_custom(self.connections[host])
                    self.release(self.connections[host])
                    self.post_release(self.connections[host])
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_success, host=host,
                                              user=server_info['user'], command='')
                    emit('success', {'event': 'finish', 'data': {'host': host, 'message': host + ' 部署完成!'}}, room=self.task_id)
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.error(e)
                    self.errors[host] = e.message
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_fail, host=host,
                                              user=server_info['user'], command='')
                    emit('fail', {'event': 'finish', 'data': {'host': host, 'message': host + Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)

        return {'success': self.success, 'errors': self.errors}
示例#24
0
class Deployer:
    '''
    序列号
    '''
    stage = 'init'

    sequence = 0
    stage_prev_deploy = 'prev_deploy'
    stage_deploy = 'deploy'
    stage_post_deploy = 'post_deploy'

    stage_prev_release = 'prev_release'
    stage_release = 'release'
    stage_post_release = 'post_release'

    task_id = '0'
    user_id = '0'
    taskMdl = None
    TaskRecord = None

    console = False
    custom_global_env = {}

    version = datetime.now().strftime('%Y%m%d%H%M%S')

    local_codebase, dir_codebase_project, project_name = None, None, None
    dir_release, dir_webroot = None, None

    connections, success, errors = {}, {}, {}
    release_version_tar, previous_release_version, release_version = None, None, None
    local = None

    def __init__(self, task_id=None, project_id=None, console=False):
        self.local_codebase = current_app.config.get('CODE_BASE').rstrip('/') + '/'
        self.localhost = Waller(host='127.0.0.1')
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            current_app.logger.info(self.task_id)
            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.project_info = self.taskMdl.get('project_info')

            # copy to a local version
            self.release_version = self.taskMdl.get('link_id') if (self.taskMdl.get("is_rollback")) else \
                '{project_id}_{task_id}_{timestamp}'.format(
                    project_id=self.project_info['id'],
                    task_id=self.task_id,
                    timestamp=time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())),
                )
            current_app.logger.info(self.taskMdl)

            # 将环境变量包在 "" 里,防止特殊字符报错
            format_export = lambda val: '"%s"' % str(val).replace('"', '').replace("'", '')

            self.custom_global_env = {
                'WEBROOT': str(self.project_info['target_root']),
                'VERSION': str(self.release_version),
                'CURRENT_RELEASE': str(self.project_info['target_releases']),
                'BRANCH': format_export(self.taskMdl.get('branch')),
                'TAG': str(self.taskMdl.get('tag')),
                'COMMIT_ID': str(self.taskMdl.get('commit_id')),
                'PROJECT_NAME': format_export(self.project_info['name']),
                'PROJECT_ID': str(self.project_info['id']),
                'TASK_NAME': format_export(self.taskMdl.get('name')),
                'TASK_ID': str(self.task_id),
                'DEPLOY_USER': str(self.taskMdl.get('user_name')),
                'DEPLOY_TIME': str(time.strftime('%Y%m%d-%H:%M:%S', time.localtime(time.time()))),
            }

            if self.project_info['task_vars']:
                task_vars = [i.strip() for i in self.project_info['task_vars'].split('\n') if i.strip() and not i.strip().startswith('#')]
                for var in task_vars:
                    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)

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()
            self.servers = self.project_info['servers_info']

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.local_codebase + str(self.project_name)

        # self.init_repo()

        # start to deploy
        self.console = console

    def config(self, console=None):
        return {
            'task_id': self.task_id,
            'user_id': self.user_id,
            'stage': self.stage,
            'sequence': self.sequence,
            'console': console if console is not None else self.console
        }

    def start(self):
        RecordModel().query.filter_by(task_id=self.task_id).delete()
        TaskModel().get_by_id(self.task_id).update({'status': TaskModel.status_doing})
        self.taskMdl = TaskModel().item(self.task_id)

    # ===================== fabric ================
    # SocketHandler
    def prev_deploy(self):
        '''
        # TODO
        socketio.sleep(0.001)
        1.代码检出前要做的基础工作
        - 检查 当前用户
        - 检查 python 版本
        - 检查 git 版本
        - 检查 目录是否存在
        - 用户自定义命令

        :return:
        '''
        self.stage = self.stage_prev_deploy
        self.sequence = 1

        # 检查 目录是否存在
        self.init_repo()

        # 用户自定义命令
        commands = self.project_info['prev_deploy']
        if commands:
            for command in commands.split('\n'):
                if command.strip().startswith('#') or not command.strip():
                    continue
                with self.localhost.cd(self.dir_codebase_project):
                    result = self.localhost.local(command, wenv=self.config())

    def deploy(self):
        '''
        2.检出代码

        :param project_name:
        :return:
        '''
        self.stage = self.stage_deploy
        self.sequence = 2
#
#        # copy to a local version
#        self.release_version = '%s_%s_%s' % (
#            self.project_name, self.task_id, time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())))

        with self.localhost.cd(self.local_codebase):
            command = 'cp -rf %s %s' % (self.dir_codebase_project, self.release_version)
            current_app.logger.info('cd %s  command: %s  ', self.dir_codebase_project, command)

            result = self.localhost.local(command, wenv=self.config())

        # 更新到指定 branch/commit_id 或 tag
        repo = Repo(self.local_codebase + self.release_version)
        if self.project_info['repo_mode'] == ProjectModel.repo_mode_branch:
            repo.checkout_2_commit(branch=self.taskMdl['branch'], commit=self.taskMdl['commit_id'])
        else:
            repo.checkout_2_tag(tag=self.taskMdl['tag'])

    def post_deploy(self):

        '''
        3.检出代码后要做的任务
        - 用户自定义操作命令
        - 代码编译
        - 清除日志文件及无用文件
        -
        - 压缩打包
        - 传送到版本库 release
        :return:
        '''
        self.stage = self.stage_post_deploy
        self.sequence = 3

        # 用户自定义命令
        commands = self.project_info['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_codebase + self.release_version):
                    result = self.localhost.local(command, wenv=self.config())

        # 压缩打包
        # 排除文件发布
        self.release_version_tar = '%s.tgz' % (self.release_version)
        with self.localhost.cd(self.local_codebase):
            if self.project_info['is_include']:
                files = includes_format(self.release_version, self.project_info['excludes'])
            else:
                files = excludes_format(self.release_version, self.project_info['excludes'])
            command = 'tar zcf %s/%s %s' % (self.local_codebase.rstrip('/'), self.release_version_tar, files)
            result = self.localhost.local(command, wenv=self.config())

    def prev_release(self, waller):
        '''
        4.部署代码到目标机器前做的任务
        - 检查 webroot 父目录是否存在
        :return:
        '''
        self.stage = self.stage_prev_release
        self.sequence = 4

        # 检查 target_releases 父目录是否存在
        command = 'mkdir -p %s' % (self.project_info['target_releases'])
        result = waller.run(command, wenv=self.config())

        # TODO md5
        # 传送到版本库 release
        result = waller.put(self.local_codebase + self.release_version_tar,
                            remote=self.project_info['target_releases'], wenv=self.config())
        current_app.logger.info('command: %s', dir(result))

        # 解压
        self.release_untar(waller)

        # 用户自定义命令
        self.prev_release_custom(waller)

    def prev_release_custom(self, waller):
        # 用户自定义命令
        commands = self.project_info['prev_release']
        if commands:
            for command in commands.split('\n'):
                if command.strip().startswith('#') or not command.strip():
                    continue
                # TODO
                target_release_version = "%s/%s" % (self.project_info['target_releases'], self.release_version)
                with waller.cd(target_release_version):
                    result = waller.run(command, wenv=self.config())

    def release(self, waller):
        '''
        5.部署代码到目标机器做的任务
        - 打包代码 local
        - scp local => remote
        - 解压 remote
        :return:
        '''
        self.stage = self.stage_release
        self.sequence = 5

        with waller.cd(self.project_info['target_releases']):
            # 0. get previous link
            command = '[ -L %s ] && readlink %s || echo ""' % (self.project_info['target_root'], self.project_info['target_root'])
            result = waller.run(command, wenv=self.config(console=False))
            self.previous_release_version = os.path.basename(result.stdout).strip()

            # 1. create a tmp link dir
            current_link_tmp_dir = 'current-tmp-%s' % (self.task_id)
            command = 'ln -sfn {library}/{version} {library}/{current_tmp}'.format(
                library=self.project_info['target_releases'], version=self.release_version,
                current_tmp=current_link_tmp_dir)
            result = waller.run(command, wenv=self.config())

            # 2. make a soft link from release to tmp link

            # 3. move tmp link to webroot
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
            result = waller.run(command, wenv=self.config())

    def rollback(self, waller):
        '''
        5.部署代码到目标机器做的任务
        - 恢复旧版本
        :return:
        '''
        self.stage = self.stage_release
        self.sequence = 5

        with waller.cd(self.project_info['target_releases']):
            # 0. get previous link
            command = '[ -L %s ] && readlink %s || echo ""' % (self.project_info['target_root'], self.project_info['target_root'])
            result = waller.run(command, wenv=self.config(console=False))
            self.previous_release_version = os.path.basename(result.stdout)

            # 1. create a tmp link dir
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'ln -sfn %s/%s %s' % (
                self.project_info['target_releases'], self.release_version, current_link_tmp_dir)
            result = waller.run(command, wenv=self.config())

            # 2. make a soft link from release to tmp link

            # 3. move tmp link to webroot
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
            result = waller.run(command, wenv=self.config())

    def release_untar(self, waller):
        '''
        解压版本包
        :return:
        '''
        with waller.cd(self.project_info['target_releases']):
            command = 'tar zxf %s' % (self.release_version_tar)
            result = waller.run(command, wenv=self.config())

    def post_release(self, waller):
        '''
        6.部署代码到目标机器后要做的任务
        - 切换软链
        - 重启 nginx
        :return:
        '''
        self.stage = self.stage_post_release
        self.sequence = 6
        # 用户自定义命令
        commands = self.project_info['post_release']
        if commands:
            for command in commands.split('\n'):
                if command.strip().startswith('#') or not command.strip():
                    continue
                # TODO
                with waller.cd(self.project_info['target_root']):
                    pty = False if command.find('nohup') >= 0 else True
                    result = waller.run(command, wenv=self.config(), pty=pty)
        # 个性化,用户重启的不一定是NGINX,可能是tomcat, apache, php-fpm等
        # self.post_release_service(waller)

        # 清理现场
        self.cleanup_remote(waller)

    def post_release_service(self, waller):
        '''
        代码部署完成后,服务启动工作,如: nginx重启
        :param connection:
        :return:
        '''
        with waller.cd(self.project_info['target_root']):
            command = 'sudo service nginx restart'
            result = waller.run(command, wenv=self.config())


    def project_detection(self):
        errors = []
        # LOCAL_SERVER_USER => git

        # LOCAL_SERVER_USER => target_servers
        for server_info in self.servers:
            waller = Waller(host=server_info['host'], user=server_info['user'], port=server_info['port'])
            result = waller.run('id', exception=False, wenv=self.config())
            if result.failed:
                errors.append({
                    'title': '远程目标机器免密码登录失败',
                    'why': '远程目标机器:%s 错误:%s' % (server_info['host'], result.stdout),
                    'how': '在宿主机中配置免密码登录,把宿主机用户%s的~/.ssh/ssh_rsa.pub添加到远程目标机器用户%s的~/.ssh/authorized_keys。了解更多:http://walle-web.io/docs/troubleshooting.html' % (
                    pwd.getpwuid(os.getuid())[0], server_info['host']),
                })

            # maybe this is no webroot's parent dir
            command = '[ -d {webroot} ] || mkdir -p {webroot}'.format(webroot=os.path.basename(self.project_info['target_root']))
            result = waller.run(command, exception=False, wenv=self.config(console=False))

                # 检查 webroot 父目录是否存在,是否为软链
            command = '[ -L "%s" ] && echo "true" || echo "false"' % (self.project_info['target_root'])
            result = waller.run(command, exception=False, wenv=self.config())
            if result.stdout == 'false':
                errors.append({
                    'title': '远程目标机器webroot不能是已建好的目录',
                    'why': '远程目标机器%s webroot不能是已存在的目录,必须为软链接,你不必新建,walle会自行创建。' % (server_info['host']),
                    'how': '手工删除远程目标机器:%s webroot目录:%s' % (server_info['host'], self.project_info['target_root']),
                })

        # remote release directory
        return errors

    def list_tag(self):
        repo = Repo(self.dir_codebase_project)
        repo.init(url=self.project_info['repo_url'])

        return repo.tags()

    def list_branch(self):
        repo = Repo(self.dir_codebase_project)
        repo.init(url=self.project_info['repo_url'])

        return repo.branches()

    def list_commit(self, branch):
        repo = Repo(self.dir_codebase_project)
        repo.init(url=self.project_info['repo_url'])
        return repo.commits(branch)

    def init_repo(self):
        repo = Repo(self.dir_codebase_project)
        repo.init(url=self.project_info['repo_url'])
        # @todo 没有做emit

    def cleanup_local(self):
        # clean local package
        command = 'rm -rf {project_id}_{task_id}_*'.format(project_id=self.project_info['id'], task_id=self.task_id)
        with self.localhost.cd(self.local_codebase):
            result = self.localhost.local(command, wenv=self.config())

    def cleanup_remote(self, waller):
        command = 'rm -rf {project_id}_{task_id}_*.tgz'.format(project_id=self.project_info['id'], task_id=self.task_id)
        with waller.cd(self.project_info['target_releases']):
            result = waller.run(command, wenv=self.config())

        command = 'find ./ -name "{project_id}_*" -print | ls -t | tail -n +{keep_version_num} | xargs rm -rf'.format(
            project_id=self.project_info['id'], keep_version_num=int(self.project_info['keep_version_num']) + 1)
        with waller.cd(self.project_info['target_releases']):
            result = waller.run(command, wenv=self.config())

    def logs(self):
        return RecordModel().fetch(task_id=self.task_id)

    def end(self, success=True, update_status=True):
        if update_status:
            status = TaskModel.status_success if success else TaskModel.status_fail
            current_app.logger.info('success:%s, status:%s' % (success, status))
            TaskModel().get_by_id(self.task_id).update({
                'status': status,
                'link_id': self.release_version,
                'ex_link_id': self.previous_release_version,
            })

            notice_info = {
                'title': '',
                'username': current_user.username,
                'project_name': self.project_info['name'],
                'task_name': '%s ([%s](%s))' % (self.taskMdl.get('name'), self.task_id, Notice.task_url(project_name=self.project_info['name'], task_id=self.task_id)),
                'branch': self.taskMdl.get('branch'),
                'commit': self.taskMdl.get('commit_id'),
                'tag': self.taskMdl.get('tag'),
                'repo_mode': self.project_info['repo_mode'],
            }
            notice = Notice.create(self.project_info['notice_type'])
            if success:
                notice_info['title'] = '上线部署成功'
                notice.deploy_task(project_info=self.project_info, notice_info=notice_info)
            else:
                notice_info['title'] = '上线部署失败'
                notice.deploy_task(project_info=self.project_info, notice_info=notice_info)

        # 清理本地
        self.cleanup_local()
        if success:
            emit('success', {'event': 'finish', 'data': {'message': '部署完成,辛苦了,为你的努力喝彩!'}}, room=self.task_id)
        else:
            emit('fail', {'event': 'finish', 'data': {'message': Code.code_msg[Code.deploy_fail]}}, room=self.task_id)

    def walle_deploy(self):
        self.start()

        try:
            self.prev_deploy()
            self.deploy()
            self.post_deploy()

            is_all_servers_success = True
            for server_info in self.servers:
                host = server_info['host']
                try:
                    waller = Waller(host=host, user=server_info['user'], port=server_info['port'], inline_ssh_env=True)
                    waller.init_env(env=self.custom_global_env)

                    self.connections[self.task_id] = waller
                    self.prev_release(self.connections[self.task_id])
                    self.release(self.connections[self.task_id])
                    self.post_release(self.connections[self.task_id])
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_success, host=host,
                                              user=server_info['user'], command='')
                    emit('success', {'event': 'finish', 'data': {'host': host, 'message': host + ' 部署完成!'}}, room=self.task_id)
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.exception(e)
                    self.errors[host] = e.message
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_fail, host=host,
                                              user=server_info['user'], command='')
                    emit('fail', {'event': 'finish', 'data': {'host': host, 'message': host + Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)

        return {'success': self.success, 'errors': self.errors}

    def walle_rollback(self):
        self.start()

        try:
            is_all_servers_success = True
            for server_info in self.servers:
                host = server_info['host']
                try:
                    waller = Waller(host=host, user=server_info['user'], port=server_info['port'], inline_ssh_env=True)
                    waller.init_env(env=self.custom_global_env)

                    self.connections[self.task_id] = waller
                    self.prev_release_custom(self.connections[self.task_id])
                    self.release(self.connections[self.task_id])
                    self.post_release(self.connections[self.task_id])
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_success, host=host,
                                              user=server_info['user'], command='')
                    emit('success', {'event': 'finish', 'data': {'host': host, 'message': host + ' 部署完成!'}}, room=self.task_id)
                except Exception as e:
                    is_all_servers_success = False
                    current_app.logger.exception(e)
                    self.errors[host] = e.message
                    RecordModel().save_record(stage=RecordModel.stage_end, sequence=0, user_id=current_user.id,
                                              task_id=self.task_id, status=RecordModel.status_fail, host=host,
                                              user=server_info['user'], command='')
                    emit('fail', {'event': 'finish', 'data': {'host': host, 'message': host + Code.code_msg[Code.deploy_fail]}}, room=self.task_id)
            self.end(is_all_servers_success)

        except Exception as e:
            self.end(False)

        return {'success': self.success, 'errors': self.errors}
示例#25
0
class DeploySocketIO:
    '''
    序列号
    '''
    stage = '0'

    sequence = 0
    stage_prev_deploy = 'prev_deploy'
    stage_deploy = 'deploy'
    stage_post_deploy = 'post_deploy'

    stage_prev_release = 'prev_release'
    stage_release = 'release'
    stage_post_release = 'post_release'

    task_id = '0'
    user_id = '0'
    taskMdl = None
    TaskRecord = None

    version = datetime.now().strftime('%Y%m%d%H%M%s')
    project_name = 'walden'
    dir_codebase = '/tmp/walle/codebase/'
    dir_codebase_project = dir_codebase + project_name

    # 定义远程机器
    # env.hosts = ['172.16.0.231', '172.16.0.177']

    dir_release = None
    dir_webroot = None

    connections, success, errors = {}, {}, {}
    release_version_tar, release_version = None, None
    local, websocket = None, None

    def __init__(self, task_id=None, project_id=None, websocket=None):
        self.local = Waller(host=current_app.config.get('LOCAL_SERVER_HOST'),
                            user=current_app.config.get('LOCAL_SERVER_USER'),
                            port=current_app.config.get('LOCAL_SERVER_PORT'))
        self.TaskRecord = TaskModel.TaskRecordModel()
        current_app.logger.info('DeploySocketIO.__init__ before')
        emit('message', {'msg': 'DeploySocketIO.__init__'})
        current_app.logger.info('DeploySocketIO.__init__ after')
        # if websocket:
        #     websocket.send_updates(__name__)
        #     self.websocket = websocket
        if task_id:
            self.task_id = task_id
            self.taskMdl = TaskModel.TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.task = self.taskMdl.get('target_user')
            self.project_info = self.taskMdl.get('project_info')
        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()

    def config(self):
        return {
            'task_id': self.task_id,
            'user_id': self.user_id,
            'stage': self.stage,
            'sequence': self.sequence,
            'websocket': self.websocket
        }

    # ===================== fabric ================
    # SocketHandler
    def deploy(self):
        '''
        1.代码检出前要做的基础工作
        - 检查 当前用户
        - 检查 python 版本
        - 检查 git 版本
        - 检查 目录是否存在
        - 用户自定义命令

        :return:
        '''
        current_app.logger.info('deploy ing')
        emit('message', {'msg': 'deploy ing'})
        current_app.logger.info('deploy end')

        self.stage = self.stage_prev_deploy
        self.sequence = 1

        # TODO remove
        # result = self.local.run('sleep 30', wenv=self.config())

        # 检查 当前用户
        command = 'whoami'
        current_app.logger.info(command)
        emit('message', {'msg': command})

        result = self.local.run(command, wenv=self.config())

        # 检查 python 版本
        command = 'python --version'
        result = self.local.run(command, wenv=self.config())
        current_app.logger.info(command)

        # 检查 git 版本
        command = 'git --version'
        result = self.local.run(command, wenv=self.config())
        current_app.logger.info(command)

        # 检查 目录是否存在
        command = 'mkdir -p %s' % (self.dir_codebase_project)
        # TODO remove
        current_app.logger.info(command)
        result = self.local.run(command, wenv=self.config())

        # 用户自定义命令
        command = self.project_info['prev_deploy']
        current_app.logger.info(command)
        with self.local.cd(self.dir_codebase_project):
            result = self.local.run(command, wenv=self.config())
示例#26
0
class Deployer:
    '''
    序列号
    '''
    stage = '0'

    sequence = 0
    stage_prev_deploy = 'prev_deploy'
    stage_deploy = 'deploy'
    stage_post_deploy = 'post_deploy'

    stage_prev_release = 'prev_release'
    stage_release = 'release'
    stage_post_release = 'post_release'

    task_id = '0'
    user_id = '0'
    taskMdl = None
    TaskRecord = None

    version = datetime.now().strftime('%Y%m%d%H%M%s')
    project_name = 'walden'
    dir_codebase = '/tmp/walle/codebase/'
    dir_codebase_project = dir_codebase + project_name

    # 定义远程机器
    # env.hosts = ['172.16.0.231', '172.16.0.177']

    dir_release = None
    dir_webroot = None

    connections, success, errors = {}, {}, {}
    release_version_tar, release_version = None, None
    local, websocket = None, None

    def __init__(self, task_id=None, project_id=None, websocket=None):
        self.local = Waller(host=current_app.config.get('LOCAL_SERVER_HOST'),
                            user=current_app.config.get('LOCAL_SERVER_USER'),
                            port=current_app.config.get('LOCAL_SERVER_PORT'))
        self.TaskRecord = TaskModel.TaskRecordModel()
        if websocket:
            websocket.send_updates(__name__)
            self.websocket = websocket
        if task_id:
            self.task_id = task_id
            self.taskMdl = TaskModel.TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.task = self.taskMdl.get('target_user')
            self.project_info = self.taskMdl.get('project_info')
        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()

    def config(self):
        return {
            'task_id': self.task_id,
            'user_id': self.user_id,
            'stage': self.stage,
            'sequence': self.sequence,
            'websocket': self.websocket
        }

    # ===================== fabric ================
    # SocketHandler
    def prev_deploy(self):
        '''
        1.代码检出前要做的基础工作
        - 检查 当前用户
        - 检查 python 版本
        - 检查 git 版本
        - 检查 目录是否存在
        - 用户自定义命令

        :return:
        '''
        self.stage = self.stage_prev_deploy
        self.sequence = 1

        # TODO remove
        # result = self.local.run('sleep 30', wenv=self.config())

        # 检查 当前用户
        command = 'whoami'
        self.websocket.send_updates(command)
        current_app.logger.info(command)

        result = self.local.run(command, wenv=self.config())

        # 检查 python 版本
        command = 'python --version'
        result = self.local.run(command, wenv=self.config())
        current_app.logger.info(command)

        # 检查 git 版本
        command = 'git --version'
        result = self.local.run(command, wenv=self.config())
        current_app.logger.info(command)

        # 检查 目录是否存在
        command = 'mkdir -p %s' % (self.dir_codebase_project)
        # TODO remove
        current_app.logger.info(command)
        result = self.local.run(command, wenv=self.config())

        # 用户自定义命令
        command = self.project_info['prev_deploy']
        current_app.logger.info(command)
        with self.local.cd(self.dir_codebase_project):
            result = self.local.run(command, wenv=self.config())

            # SocketHandler.send_to_all({
            #     'type': 'user',
            #     'id': 33,
            #     'host': env.host_string,
            #     'command': command,
            #     'message': result.stdout,
            # })

    def deploy(self):
        '''
        2.检出代码

        :param project_name:
        :return:
        '''
        self.stage = self.stage_deploy
        self.sequence = 2

        current_app.logger.info('git dir: %s',
                                self.dir_codebase_project + '/.git')
        # 如果项目底下有 .git 目录则认为项目完整,可以直接检出代码
        # TODO 不标准
        if os.path.exists(self.dir_codebase_project + '/.git'):
            with self.local.cd(self.dir_codebase_project):
                command = 'pwd && git pull'
                result = self.local.run(command, wenv=self.config())

        else:
            # 否则当作新项目检出完整代码
            with self.local.cd(self.dir_codebase_project):
                command = 'pwd && git clone %s .' % (
                    self.project_info['repo_url'])
                current_app.logger.info('cd %s  command: %s  ',
                                        self.dir_codebase_project, command)

                result = self.local.run(command, wenv=self.config())

        # copy to a local version
        self.release_version = '%s_%s_%s' % (
            self.project_name, self.task_id,
            time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())))
        with self.local.cd(self.dir_codebase):
            command = 'cp -rf %s %s' % (self.dir_codebase_project,
                                        self.release_version)
            current_app.logger.info('cd %s  command: %s  ',
                                    self.dir_codebase_project, command)

            result = self.local.run(command, wenv=self.config())

        # 更新到指定 commit_id
        with self.local.cd(self.dir_codebase + self.release_version):
            command = 'git reset -q --hard %s' % (
                self.taskMdl.get('commit_id'))
            result = self.local.run(command, wenv=self.config())

            # SocketHandler.send_to_all({
            #     'type': 'user',
            #     'id': 33,
            #     'host': env.host_string,
            #     'command': command,
            #     'message': result.stdout,
            # })

            # 用户自定义命令
            # command = self.project_info['deploy']
            # current_app.logger.info(command)
            # with self.local.cd(self.dir_codebase):
            #     result = self.local.run(command)

        pass

    def post_deploy(self):
        '''
        3.检出代码后要做的任务
        - 用户自定义操作命令
        - 代码编译
        - 清除日志文件及无用文件
        -
        - 压缩打包
        - 传送到版本库 release
        :return:
        '''
        self.stage = self.stage_post_deploy
        self.sequence = 3

        # 用户自定义命令
        command = self.project_info['post_deploy']
        current_app.logger.info(command)
        with self.local.cd(self.dir_codebase + self.release_version):
            result = self.local.run(command, wenv=self.config())

        # 压缩打包
        self.release_version_tar = '%s.tgz' % (self.release_version)
        with self.local.cd(self.dir_codebase):
            command = 'tar zcvf %s %s' % (self.release_version_tar,
                                          self.release_version)
            result = self.local.run(command, wenv=self.config())

    def prev_release(self, waller):
        '''
        4.部署代码到目标机器前做的任务
        - 检查 webroot 父目录是否存在
        :return:
        '''
        self.stage = self.stage_prev_release
        self.sequence = 4

        # 检查 target_releases 父目录是否存在
        command = 'mkdir -p %s' % (self.project_info['target_releases'])
        result = waller.run(command, wenv=self.config())

        # TODO 检查 webroot 父目录是否存在,是否为软链
        # command = 'mkdir -p %s' % (self.project_info['target_root'])
        # result = waller.run(command)
        # current_app.logger.info('command: %s', dir(result))

        # TODO md5
        # 传送到版本库 release
        current_app.logger.info('/tmp/walle/codebase/' +
                                self.release_version_tar)
        result = waller.put('/tmp/walle/codebase/' + self.release_version_tar,
                            remote=self.project_info['target_releases'])
        current_app.logger.info('command: %s', dir(result))

        # 解压
        self.release_untar(waller)

    def release(self, waller):
        '''
        5.部署代码到目标机器做的任务
        - 打包代码 local
        - scp local => remote
        - 解压 remote
        :return:
        '''
        self.stage = self.stage_release
        self.sequence = 5

        with waller.cd(self.project_info['target_releases']):
            # 1. create a tmp link dir
            current_link_tmp_dir = '%s/current-tmp-%s' % (
                self.project_info['target_releases'], self.task_id)
            command = 'ln -sfn %s/%s %s' % (
                self.project_info['target_releases'], self.release_version,
                current_link_tmp_dir)
            result = waller.run(command, wenv=self.config())

            # 2. make a soft link from release to tmp link

            # 3. move tmp link to webroot
            current_link_tmp_dir = '%s/current-tmp-%s' % (
                self.project_info['target_releases'], self.task_id)
            command = 'mv -fT %s %s' % (current_link_tmp_dir,
                                        self.project_info['target_root'])
            result = waller.run(command, wenv=self.config())

    def release_untar(self, waller):
        '''
        解压版本包
        :return:
        '''
        with waller.cd(self.project_info['target_releases']):
            command = 'tar zxf %s' % (self.release_version_tar)
            result = waller.run(command, wenv=self.config())

    def post_release(self, waller):
        '''
        6.部署代码到目标机器后要做的任务
        - 切换软链
        - 重启 nginx
        :return:
        '''
        self.stage = self.stage_post_release
        self.sequence = 6

        self.post_release_service(waller)

    def post_release_service(self, waller):
        '''
        代码部署完成后,服务启动工作,如: nginx重启
        :param connection:
        :return:
        '''

        with waller.cd(self.project_info['target_root']):
            command = 'sudo service nginx restart'
            result = waller.run(command, wenv=self.config())

    def list_tag(self):
        with self.local.cd(self.dir_codebase_project):
            command = 'git tag -l'
            current_app.logger.info('cd %s  command: %s  ',
                                    self.dir_codebase_project, command)
            result = self.local.run(command, wenv=self.config())
            current_app.logger.info(dir(result))
            return result

        return None

    def list_branch(self):
        with self.local.cd(self.dir_codebase_project):
            command = 'git pull'
            # result = self.local.run(command, wenv=self.config())

            current_app.logger.info(self.dir_codebase_project)

            command = 'git branch -r'
            result = self.local.run(command, wenv=self.config())

            # TODO 三种可能: false, error, success

            branches = result.stdout.strip().split('\n')
            # 去除 origin/HEAD -> 当前指向
            # 去除远端前缀
            branches = [
                branch.strip().lstrip('origin/') for branch in branches
                if not branch.startswith('origin/HEAD')
            ]
            return branches

        return None

    def list_commit(self, branch):
        with self.local.cd(self.dir_codebase_project):
            command = 'git checkout %s && git pull' % (branch)
            result = self.local.run(command, wenv=self.config())

            # TODO 10是需要前端传的
            command = 'git log -10 --pretty="%h #_# %an #_# %s"'
            result = self.local.run(command, wenv=self.config())
            commit_list = result.stdout.strip().split('\n')
            commits = []
            for commit in commit_list:
                commit_dict = commit.split(' #_# ')
                commits.append({
                    'id': commit_dict[0],
                    'name': commit_dict[1],
                    'message': commit_dict[2],
                })
            return commits

        return None

    def walle_deploy(self):

        self.prev_deploy()
        self.deploy()
        self.post_deploy()

        server = '172.16.0.231'
        try:
            self.connections[server] = Waller(
                host=server, user=self.project_info['target_user'])
            self.prev_release(self.connections[server])
            self.release(self.connections[server])
            self.post_release(self.connections[server])
        except Exception, e:
            current_app.logger.exception(e)
            self.errors[server] = e.message

        # for server_info in self.servers:
        #     server = server_info.host
        #     try:
        #         self.connections[server] = Waller(host=server, user=self.project_info['target_user'])
        #         self.prev_release(self.connections[server])
        #         self.release(self.connections[server])
        #         self.post_release(self.connections[server])
        #     except Exception, e:
        #         self.errors[server] = e.message

        return {'success': self.success, 'errors': self.errors}
示例#27
0
class Deployer:

    '''
    序列号
    '''
    stage = '0'

    sequence = 0
    stage_prev_deploy = 'prev_deploy'
    stage_deploy = 'deploy'
    stage_post_deploy = 'post_deploy'

    stage_prev_release = 'prev_release'
    stage_release = 'release'
    stage_post_release = 'post_release'

    task_id = '0'
    user_id = '0'
    taskMdl = None
    TaskRecord = None

    console = False

    version = datetime.now().strftime('%Y%m%d%H%M%s')
    project_name = None
    dir_codebase = '/tmp/walle/codebase/'
    dir_codebase_project = ''

    # 定义远程机器
    # env.hosts = ['172.16.0.231', '172.16.0.177']

    dir_release = None
    dir_webroot = None

    connections, success, errors = {}, {}, {}
    release_version_tar, release_version = None, None
    local = None

    def __init__(self, task_id=None, project_id=None, console=False):
        self.local = Waller(host=current_app.config.get('LOCAL_SERVER_HOST'),
                            user=current_app.config.get('LOCAL_SERVER_USER'),
                            port=current_app.config.get('LOCAL_SERVER_PORT'))
        self.TaskRecord = RecordModel()

        if task_id:
            self.task_id = task_id
            # task start
            TaskModel(id=self.task_id).update(status=TaskModel.status_doing)

            self.taskMdl = TaskModel().item(self.task_id)
            self.user_id = self.taskMdl.get('user_id')
            self.servers = self.taskMdl.get('servers_info')
            self.task = self.taskMdl.get('target_user')
            self.project_info = self.taskMdl.get('project_info')

        if project_id:
            self.project_id = project_id
            self.project_info = ProjectModel(id=project_id).item()

        self.project_name = self.project_info['id']
        self.dir_codebase_project = self.dir_codebase + str(self.project_name)

        # start to deploy


    def config(self):
        return {'task_id': self.task_id, 'user_id': self.user_id, 'stage': self.stage, 'sequence': self.sequence, 'console': self.console}

    # ===================== fabric ================
    # SocketHandler
    def prev_deploy(self):
        '''
        # TODO
        socketio.sleep(0.001)
        1.代码检出前要做的基础工作
        - 检查 当前用户
        - 检查 python 版本
        - 检查 git 版本
        - 检查 目录是否存在
        - 用户自定义命令

        :return:
        '''
        self.stage = self.stage_prev_deploy
        self.sequence = 1

        # TODO remove
        # result = self.local.run('sleep 30', wenv=self.config())

        # 检查 当前用户
        command = 'whoami'
        current_app.logger.info(command)
        result = self.local.run(command, wenv=self.config())

        # 检查 python 版本
        command = 'python --version'
        result = self.local.run(command, wenv=self.config())
        current_app.logger.info(command)

        # 检查 git 版本
        command = 'git --version'
        result = self.local.run(command, wenv=self.config())
        current_app.logger.info(command)

        # 检查 目录是否存在
        self.init_repo()

        # TODO to be removed
        command = 'mkdir -p %s' % (self.dir_codebase_project)
        # TODO remove
        current_app.logger.info(command)
        result = self.local.run(command, wenv=self.config())

        # 用户自定义命令
        command = self.project_info['prev_deploy']
        current_app.logger.info(command)
        with self.local.cd(self.dir_codebase_project):
            result = self.local.run(command, wenv=self.config())

            # SocketHandler.send_to_all({
            #     'type': 'user',
            #     'id': 33,
            #     'host': env.host_string,
            #     'command': command,
            #     'message': result.stdout,
            # })

    def deploy(self):
        '''
        2.检出代码

        :param project_name:
        :return:
        '''
        # TODO
        socketio.sleep(0.001)
        self.stage = self.stage_deploy
        self.sequence = 2

        current_app.logger.info('git dir: %s', self.dir_codebase_project + '/.git')
        # 如果项目底下有 .git 目录则认为项目完整,可以直接检出代码
        # TODO 不标准
        if os.path.exists(self.dir_codebase_project + '/.git'):
            with self.local.cd(self.dir_codebase_project):
                command = 'pwd && git pull'
                result = self.local.run(command, wenv=self.config())

        else:
            # 否则当作新项目检出完整代码
            with self.local.cd(self.dir_codebase_project):
                command = 'pwd && git clone %s .' % (self.project_info['repo_url'])
                current_app.logger.info('cd %s  command: %s  ', self.dir_codebase_project, command)

                result = self.local.run(command, wenv=self.config())

        # copy to a local version
        self.release_version = '%s_%s_%s' % (
            self.project_name, self.task_id, time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time())))
        with self.local.cd(self.dir_codebase):
            command = 'cp -rf %s %s' % (self.dir_codebase_project, self.release_version)
            current_app.logger.info('cd %s  command: %s  ', self.dir_codebase_project, command)

            result = self.local.run(command, wenv=self.config())

        # 更新到指定 commit_id
        with self.local.cd(self.dir_codebase + self.release_version):
            command = 'git reset -q --hard %s' % (self.taskMdl.get('commit_id'))
            result = self.local.run(command, wenv=self.config())


            # SocketHandler.send_to_all({
            #     'type': 'user',
            #     'id': 33,
            #     'host': env.host_string,
            #     'command': command,
            #     'message': result.stdout,
            # })

            # 用户自定义命令
            # command = self.project_info['deploy']
            # current_app.logger.info(command)
            # with self.local.cd(self.dir_codebase):
            #     result = self.local.run(command)

        pass

    def post_deploy(self):

        '''
        3.检出代码后要做的任务
        - 用户自定义操作命令
        - 代码编译
        - 清除日志文件及无用文件
        -
        - 压缩打包
        - 传送到版本库 release
        :return:
        '''
        # TODO
        socketio.sleep(0.001)
        self.stage = self.stage_post_deploy
        self.sequence = 3

        # 用户自定义命令
        command = self.project_info['post_deploy']
        current_app.logger.info(command)
        current_app.logger.info(self.dir_codebase)
        current_app.logger.info(self.release_version)
        with self.local.cd(self.dir_codebase + self.release_version):
            result = self.local.run(command, wenv=self.config())

        # 压缩打包
        self.release_version_tar = '%s.tgz' % (self.release_version)
        with self.local.cd(self.dir_codebase):
            command = 'tar zcvf %s %s' % (self.release_version_tar, self.release_version)
            result = self.local.run(command, wenv=self.config())

    def prev_release(self, waller):
        '''
        4.部署代码到目标机器前做的任务
        - 检查 webroot 父目录是否存在
        :return:
        '''
        # TODO
        socketio.sleep(0.001)
        self.stage = self.stage_prev_release
        self.sequence = 4

        # 检查 target_releases 父目录是否存在
        command = 'mkdir -p %s' % (self.project_info['target_releases'])
        result = waller.run(command, wenv=self.config())

        # TODO 检查 webroot 父目录是否存在,是否为软链
        # command = 'mkdir -p %s' % (self.project_info['target_root'])
        # result = waller.run(command)
        # current_app.logger.info('command: %s', dir(result))


        # TODO md5
        # 传送到版本库 release
        current_app.logger.info('/tmp/walle/codebase/' + self.release_version_tar)
        result = waller.put('/tmp/walle/codebase/' + self.release_version_tar,
                            remote=self.project_info['target_releases'], wenv=self.config())
        current_app.logger.info('command: %s', dir(result))

        # 解压
        self.release_untar(waller)

    def release(self, waller):
        '''
        5.部署代码到目标机器做的任务
        - 打包代码 local
        - scp local => remote
        - 解压 remote
        :return:
        '''
        # TODO
        socketio.sleep(0.001)
        self.stage = self.stage_release
        self.sequence = 5

        with waller.cd(self.project_info['target_releases']):
            # 1. create a tmp link dir
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'ln -sfn %s/%s %s' % (
                self.project_info['target_releases'], self.release_version, current_link_tmp_dir)
            result = waller.run(command, wenv=self.config())

            # 2. make a soft link from release to tmp link

            # 3. move tmp link to webroot
            current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
            command = 'mv -fT %s %s' % (current_link_tmp_dir, self.project_info['target_root'])
            result = waller.run(command, wenv=self.config())

    def release_untar(self, waller):
        '''
        解压版本包
        :return:
        '''
        # TODO
        socketio.sleep(0.001)
        with waller.cd(self.project_info['target_releases']):
            command = 'tar zxf %s' % (self.release_version_tar)
            result = waller.run(command, wenv=self.config())

    def post_release(self, waller):
        '''
        6.部署代码到目标机器后要做的任务
        - 切换软链
        - 重启 nginx
        :return:
        '''
        self.stage = self.stage_post_release
        self.sequence = 6

        self.post_release_service(waller)

    def post_release_service(self, waller):
        '''
        代码部署完成后,服务启动工作,如: nginx重启
        :param connection:
        :return:
        '''

        with waller.cd(self.project_info['target_root']):
            command = 'sudo service nginx restart'
            result = waller.run(command, wenv=self.config())

    def list_tag(self):
        self.init_repo()

        with self.local.cd(self.dir_codebase_project):
            command = 'git tag -l'
            current_app.logger.info('cd %s  command: %s  ', self.dir_codebase_project, command)
            result = self.local.run(command, wenv=self.config())
            current_app.logger.info(dir(result))
            return result

        return None

    def list_branch(self):
        self.init_repo()

        with self.local.cd(self.dir_codebase_project):
            command = 'git pull'
            result = self.local.run(command, wenv=self.config())

            current_app.logger.info(self.dir_codebase_project)

            command = 'git branch -r'
            result = self.local.run(command, wenv=self.config())

            # TODO 三种可能: false, error, success

            branches = result.stdout.strip().split('\n')
            # 去除 origin/HEAD -> 当前指向
            # 去除远端前缀
            branches = [branch.strip().lstrip('origin/') for branch in branches if not branch.startswith('origin/HEAD')]
            return branches

        return None

    def list_commit(self, branch):
        self.init_repo()

        with self.local.cd(self.dir_codebase_project):
            command = 'git checkout %s && git pull' % (branch)
            result = self.local.run(command, wenv=self.config())

            # TODO 10是需要前端传的
            command = 'git log -10 --pretty="%h #_# %an #_# %s"'
            result = self.local.run(command, wenv=self.config())
            commit_list = result.stdout.strip().split('\n')
            commits = []
            for commit in commit_list:
                commit_dict = commit.split(' #_# ')
                commits.append({
                    'id': commit_dict[0],
                    'name': commit_dict[1],
                    'message': commit_dict[2],
                })
            return commits

        return None

    def init_repo(self):

        current_app.logger.info('git dir: %s', self.dir_codebase_project + '/.git')
        # 如果项目底下有 .git 目录则认为项目完整,可以直接检出代码
        # TODO 不标准
        if not os.path.exists(self.dir_codebase_project):
            # 检查 目录是否存在
            command = 'mkdir -p %s' % (self.dir_codebase_project)
            # TODO remove
            current_app.logger.info(command)
            result = self.local.run(command, wenv=self.config())

        if not os.path.exists(self.dir_codebase_project + '/.git'):
            # 否则当作新项目检出完整代码
            with self.local.cd(self.dir_codebase_project):
                command = 'pwd && git clone %s .' % (self.project_info['repo_url'])
                current_app.logger.info('cd %s  command: %s  ', self.dir_codebase_project, command)

                result = self.local.run(command, wenv=self.config())


    def walle_deploy(self):
        self.prev_deploy()
        self.deploy()
        self.post_deploy()

        server = '172.16.0.231'
        try:
            self.connections[server] = Waller(host=server, user=self.project_info['target_user'])
            self.prev_release(self.connections[server])
            self.release(self.connections[server])
            self.post_release(self.connections[server])
        except Exception as e:
            current_app.logger.exception(e)
            self.errors[server] = e.message

        # for server_info in self.servers:
        #     server = server_info.host
        #     try:
        #         self.connections[server] = Waller(host=server, user=self.project_info['target_user'])
        #         self.prev_release(self.connections[server])
        #         self.release(self.connections[server])
        #         self.post_release(self.connections[server])
        #     except Exception, e:
        #         self.errors[server] = e.message

        return {'success': self.success, 'errors': self.errors}