Kaynağa Gözat

walle 2.0 alpha - 增加上线单回滚以及相关优化

walle 6 yıl önce
ebeveyn
işleme
044cdc8b7d

migrations/versions/00adfdca30bf_server.py → migrations/versions/00adfdca30bf_03_server.py


migrations/versions/2bca06a823a0_init_walle_database.py → migrations/versions/2bca06a823a0_01_init_walle_database.py


migrations/versions/52a2df18b1d4_add_index.py → migrations/versions/52a2df18b1d4_02_add_index.py


migrations/versions/91c4d13540c3_task_username.py → migrations/versions/91c4d13540c3_05_task_username.py


migrations/versions/9532a372b5aa_preject_remove_server.py → migrations/versions/9532a372b5aa_04_preject_remove_server.py


+ 11 - 1
walle/api/task.py

@@ -146,14 +146,24 @@ class TaskAPI(SecurityResource):
         """
 
         task = TaskModel.get_by_id(task_id).to_dict()
-        ex_task = TaskModel().query.filter_by(link_id=task['ex_link_id']).first().to_dict()
+        filters = {
+            TaskModel.link_id == task['ex_link_id'],
+            TaskModel.id < task_id,
+        }
+        ex_task = TaskModel().query.filter(*filters).first()
+
+        if not ex_task:
+            raise WalleError(code=Code.rollback_error)
 
         task['id'] = None
         task['name'] = task['name'] + u' - 回滚此次上线'
         task['link_id'] = task['ex_link_id']
         task['ex_link_id'] = ''
+        task['is_rollback'] = 1
+        task['status'] = TaskModel.task_default_status(task['project_id'])
 
         # rewrite commit/tag/branch
+        ex_task = ex_task.to_dict()
         task['commit_id'] = ex_task['commit_id']
         task['branch'] = ex_task['branch']
         task['tag'] = ex_task['tag']

+ 1 - 3
walle/form/task.py

@@ -40,7 +40,6 @@ class TaskForm(Form):
         task_status = TaskModel.status_new if project_info['task_audit'] == ProjectModel.task_audit_true else TaskModel.status_pass
         return {
             'name': self.name.data if self.name.data else '',
-            # todo
             'user_id': current_user.id,
             'user_name': current_user.username,
             'project_id': self.project_id.data,
@@ -55,6 +54,5 @@ class TaskForm(Form):
             'branch': self.branch.data if self.branch.data else '',
             'file_transmission_mode': self.file_transmission_mode.data if self.file_transmission_mode.data else 0,
             'file_list': self.file_list.data if self.file_list.data else '',
-            'enable_rollback': 1,
-
+            'is_rollback': 0,
         }

+ 18 - 3
walle/model/task.py

@@ -54,7 +54,7 @@ class TaskModel(SurrogatePK, Model):
     tag = db.Column(String(100))
     file_transmission_mode = db.Column(Integer)
     file_list = db.Column(Text)
-    enable_rollback = db.Column(Integer)
+    is_rollback = db.Column(Integer)
     created_at = db.Column(DateTime, default=current_time)
     updated_at = db.Column(DateTime, default=current_time, onupdate=current_time)
 
@@ -186,7 +186,7 @@ class TaskModel(SurrogatePK, Model):
             'tag': self.tag,
             'file_transmission_mode': self.file_transmission_mode,
             'file_list': self.file_list,
-            'enable_rollback': self.enable_rollback,
+            'is_rollback': self.is_rollback,
             'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
             'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S'),
         }
@@ -200,6 +200,12 @@ class TaskModel(SurrogatePK, Model):
             self.rollback_count[self.project_id] = 0
         if self.status in [self.status_doing, self.status_fail, self.status_success]:
             self.rollback_count[self.project_id] += 1
+        if self.rollback_count[self.project_id] <= self.keep_version_num \
+            and self.status in [self.status_doing, self.status_fail, self.status_success] \
+            and self.ex_link_id:
+            enable_rollback = True
+        else:
+            enable_rollback = False
 
         return {
             'enable_view': True if self.status in [self.status_doing, self.status_fail, self.status_success] else False,
@@ -208,5 +214,14 @@ class TaskModel(SurrogatePK, Model):
             'enable_create': False,
             'enable_online': (permission.enable_uid(self.user_id) or permission.role_upper_developer() or is_project_master) and (self.status in [self.status_pass, self.status_fail, self.status_doing]),
             'enable_audit': (permission.role_upper_developer() or is_project_master) and (self.status in [self.status_new]),
-            'enable_rollback': True if self.rollback_count[self.project_id] <= self.keep_version_num and self.status in [self.status_doing, self.status_fail, self.status_success] else False
+            'enable_rollback': enable_rollback
         }
+
+    @classmethod
+    def task_default_status(cls, project_id):
+        ProjectModel = model.project.ProjectModel
+        project_info = ProjectModel.query.filter_by(id=project_id).first()
+        if project_info.task_audit == ProjectModel.task_audit_true:
+            return TaskModel.status_new
+        else:
+            return TaskModel.status_pass

+ 4 - 0
walle/service/code.py

@@ -41,6 +41,9 @@ class Code():
     #: 表单错误
     form_error = 2001
 
+    #: 不能生成回滚上线单,可能是第一上线,或目标机器上版本库里已失去该版本备份
+    rollback_error = 2002
+
     #: ----------------------- 3xxx shell 相关错误 -----------------
     #: 3xxx shell相关错误
     #: 不知道怎么归类的错误
@@ -80,6 +83,7 @@ class Code():
 
         params_error: '参数错误',
         form_error: '表单错误',
+        rollback_error: '不能生成回滚上线单,可能是第一上线,或目标机器上版本库里已失去该版本备份',
 
         shell_run_fail: '命令运行错误,请联系管理员',
         shell_dir_not_exists: '路径不存在,请联系管理员',

+ 77 - 36
walle/service/deployer.py

@@ -82,9 +82,14 @@ class Deployer:
         # 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 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()
@@ -120,10 +125,6 @@ class Deployer:
         # 检查 目录是否存在
         self.init_repo()
 
-        # TODO to be removed
-        command = 'mkdir -p %s' % (self.dir_codebase_project)
-        result = self.localhost.local(command, wenv=self.config())
-
         # 用户自定义命令
         command = self.project_info['prev_deploy']
         if command:
@@ -200,13 +201,6 @@ class Deployer:
         command = 'mkdir -p %s' % (self.project_info['target_releases'])
         result = waller.run(command, wenv=self.config())
 
-        # 用户自定义命令
-        command = self.project_info['prev_release']
-        if command:
-            current_app.logger.info(command)
-            with waller.cd(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,
@@ -216,6 +210,19 @@ class Deployer:
         # 解压
         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.部署代码到目标机器做的任务
@@ -230,11 +237,36 @@ class Deployer:
         with waller.cd(self.project_info['target_releases']):
             # 0. get previous link
             command = 'readlink ' + 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 = 'readlink ' + self.project_info['target_root']
+            result = waller.run(command, wenv=self.config(console=False))
             self.previous_release_version = os.path.basename(result.stdout)
-            current_app.logger.info(command)
-            current_app.logger.info(self.previous_release_version)
-            current_app.logger.info(result.stdout)
 
             # 1. create a tmp link dir
             current_link_tmp_dir = '%s/current-tmp-%s' % (self.project_info['target_releases'], self.task_id)
@@ -270,12 +302,10 @@ class Deployer:
 
         # 用户自定义命令
         command = self.project_info['post_release']
-        if not command:
-            return None
-
-        current_app.logger.info(command)
-        with waller.cd(self.project_info['target_root']):
-            result = waller.run(command, wenv=self.config())
+        if command:
+            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)
 
@@ -291,19 +321,6 @@ class Deployer:
 
     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.localhost.local(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
@@ -485,3 +502,27 @@ class Deployer:
             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])
+                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}

+ 0 - 1
walle/service/waller.py

@@ -46,7 +46,6 @@ class Waller(Connection):
             else:
                 result = super(Waller, self).run(command, pty=pty, warn=True, watchers=[say_yes()], **kwargs)
 
-
             if result.failed:
                 exitcode, stdout, stderr = result.exited, '', result.stdout
                 if exception:

+ 4 - 1
walle/service/websocket.py

@@ -47,7 +47,10 @@ class WalleSocketIO(Namespace):
     def on_deploy(self, message):
         if self.task_info['status'] in [TaskModel.status_pass, TaskModel.status_fail]:
             wi = Deployer(task_id=self.room, console=True)
-            ret = wi.walle_deploy()
+            if self.task_info['is_rollback']:
+                wi.walle_rollback()
+            else:
+                wi.walle_deploy()
         else:
             emit('console', {'event': 'forbidden', 'data': self.task_info}, room=self.room)