Sfoglia il codice sorgente

Merge branch 'master' into 2.1/bugfix/编辑空间出现错误

walle-web.io 5 anni fa
parent
commit
56e9156a23
40 ha cambiato i file con 228 aggiunte e 95 eliminazioni
  1. 2 2
      .github/ISSUE_TEMPLATE/bug---.md
  2. 1 1
      .travis.yml
  3. 14 4
      README.md
  4. 20 3
      admin.sh
  5. 1 1
      fe/index.html
  6. 10 0
      fe/static/js/1.1937b80d952ec9ba8012.js
  7. BIN
      fe/static/js/1.1937b80d952ec9ba8012.js.gz
  8. 10 0
      fe/static/js/1.d47e2dcd0b1e74a9f703.js
  9. BIN
      fe/static/js/1.d47e2dcd0b1e74a9f703.js.gz
  10. 1 0
      fe/static/js/11.8d458c7a426ca33d1f4b.js
  11. 1 0
      fe/static/js/11.ad09d7600b0017fc9a8b.js
  12. 1 0
      fe/static/js/12.30bb65651fdc0973b404.js
  13. 1 0
      fe/static/js/13.99c687afb273897c0fb3.js
  14. 1 0
      fe/static/js/app.6bb8d1994240946e5f1b.js
  15. BIN
      fe/static/js/app.6bb8d1994240946e5f1b.js.gz
  16. 1 0
      fe/static/js/app.fe68a2cf72c802327a5a.js
  17. BIN
      fe/static/js/app.fe68a2cf72c802327a5a.js.gz
  18. 1 0
      fe/static/js/manifest.6d724d91b62598c00b3d.js
  19. 1 0
      fe/static/js/manifest.a0af51ee57e7900644cf.js
  20. 1 1
      requirements/prod.txt
  21. BIN
      screenshot/weixin-jiangyongping.jpg
  22. BIN
      screenshot/weixin-xupengfei.jpg
  23. BIN
      screenshot/weixin-zouhongxue.jpg
  24. 1 0
      walle/api/api.py
  25. 10 2
      walle/api/passport.py
  26. 12 6
      walle/api/space.py
  27. 4 1
      walle/api/task.py
  28. 1 1
      walle/api/user.py
  29. 5 1
      walle/app.py
  30. 4 1
      walle/config/settings.py
  31. 3 0
      walle/config/settings_dev.py
  32. 7 0
      walle/config/settings_prod.py
  33. 3 0
      walle/config/settings_test.py
  34. 2 2
      walle/form/user.py
  35. 3 3
      walle/model/environment.py
  36. 33 2
      walle/model/member.py
  37. 4 1
      walle/model/task.py
  38. 27 22
      walle/service/deployer.py
  39. 41 40
      walle/service/git/repo.py
  40. 1 1
      walle/service/websocket.py

+ 2 - 2
.github/ISSUE_TEMPLATE/bug---.md

@@ -3,7 +3,7 @@ name: Bug-报告
 about: 更详细的描述更利用问题解决
 title: BUG
 labels: bug
-assignees: meolu
+assignees: henyihanwobushi, meolu, aSmallPing, ZouHongxue
 
 ---
 
@@ -27,4 +27,4 @@ error...
 
 **运行环境**
  - OS: [e.g. Centos 7.x]
- - 版本 [e.g. 2.0.0]
+ - 版本 [e.g. aba77b36] walle首页信息处显示

+ 1 - 1
.travis.yml

@@ -23,7 +23,7 @@ install:
 before_script:
   - echo "#before_script#"
   # stop the build if there are Python syntax errors or undefined names
-  - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
+  - flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics
   # exit-zero treats all errors as warnings.  The GitHub editor is 127 chars wide
   - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
   - mkdir -p /tmp/walle/{logs,library,webroot,releases}

+ 14 - 4
README.md

@@ -2,7 +2,12 @@
 
 Walle 2.0 - [官方主页](https://www.walle-web.io)
 =========================
-[![Build Status](https://travis-ci.org/meolu/walle-web.svg?branch=master)](https://travis-ci.org/meolu/walle-web)
+<p align="left">
+    <a href='https://travis-ci.org/meolu/walle-web'><img src='https://travis-ci.org/meolu/walle-web.svg?branch=master' alt="Build Status"></a>  
+    <a href='https://gitter.im/meolu/walle-web'><img src='https://badges.gitter.im/Join%20Chat.svg'></a>
+</p>
+
+**字节跳动内推**:效率工程招聘大数据研发、数据分析师,机会极佳,请勿错过。请各位朋友扩散下有需要的同学,请加我微信wu-shuiyong,备注来意,帮助内推,君子成人之美,谢谢。
 
 功能强大,且免费开源的`walle-web 瓦力`终于更新`2.0.0`了!!!
 
@@ -63,8 +68,7 @@ Roadmap
 - **2.1.0**  2019-03-22
     - 超管权限完善
     - `Dashboard` 1.0(全新的玩法,欢迎提issue)
-    - 3月24日开源中国苏州源创会-[开源综合技术主题](https://www.oschina.net/event/2303765)《开源构建多空间可视化一键部署Devops平台》
-    - 冲刺`github` 10000 `star`(靠你们和你们的同事们了)
+    - ~~3月24日开源中国苏州源创会-[开源综合技术主题](https://www.oschina.net/event/2303765)《开源构建多空间可视化一键部署Devops平台》~~
 - **2.2.0**  2019-04-22
     - webhook (gitlab)
     - 上线时间记录、命令与结果拆分、实时console
@@ -101,8 +105,14 @@ Discussing
 
 <img src="https://raw.githubusercontent.com/meolu/walle-web/master/screenshot/weixin-ye.jpg" width="244" height="314" alt="叶歆昊微信" align=left />
 
+<img src="https://raw.githubusercontent.com/meolu/walle-web/master/screenshot/weixin-zouhongxue.jpg" width="244" height="314" alt="keep learn微信" align=left />
+
+<img src="https://raw.githubusercontent.com/meolu/walle-web/master/screenshot/weixin-xupengfei.jpg" width="244" height="314" alt="Flying 微信" align=left />
+
+<img src="https://raw.githubusercontent.com/meolu/walle-web/master/screenshot/weixin-jiangyongping.jpg" width="244" height="314" alt="小平 微信" align=left />
 
-<br><br><br><br><br><br><br><br><br><br><br><br><br><br>
+<br><br><br><br><br><br><br><br><br><br><br><br><br>
+<br><br><br><br><br><br><br><br><br><br><br><br><br>
 
 新的惊喜
 =========================

+ 20 - 3
admin.sh

@@ -8,7 +8,18 @@
 # Author: alenx <alenx.hai@gmail.com>
 # -->>  新增ubuntu初始化,全面支持Ubuntu环境(16.x/18.x)
 #########################################################################
-#!/bin/bash
+#!/usr/bin/env bash
+
+# ubuntu 高版本 sh 指向的是 dash 而非 bash。 dash 无法使用 function 关键字以及 source 等命令。
+# 如果检测到 sh 指向的是 dash, 那么将使用 bash 重新执行脚本,然后在参数末尾加上一个 flag, 表示此次运行是修正过的, 避免陷入死循环。
+fix_ubuntu_bash="fix-sh-in-ubuntu"
+
+if [ ! -n "`echo $@ | grep $fix_ubuntu_bash$`" ]; then
+    if [ -n "`ls -l /bin/sh | grep "dash"`" ]; then
+        bash $0 $@ $fix_ubuntu_bash
+        exit
+    fi
+fi
 
 APP="waller.py"
 
@@ -47,9 +58,15 @@ function SystemName() {
             ;;
 
         debian|ubuntu|devuan)
-            echo "安装/更新可能缺少的依赖: ibmysqld-dev gcc gcc-c++ python-dev"
+            echo "安装/更新可能缺少的依赖: libmysqld-dev libmysqlclient-dev python-dev python-virtualenv python-pip"
+            sudo apt update -y
+            sudo apt install -y libmysqld-dev libmysqlclient-dev python-dev python-virtualenv python-pip
+            ;;
+
+        raspbian)
+            echo "安装/更新可能缺少的依赖"
             sudo apt update -y
-            sudo apt install -y libmysqld-dev python-dev virtualenv python-pip
+            sudo apt install -y gcc g++ python-dev virtualenv python-pip libpq-dev libffi-dev libssl-dev libmariadbd18 libmariadbd-dev
             ;;
 
         *)

File diff suppressed because it is too large
+ 1 - 1
fe/index.html


File diff suppressed because it is too large
+ 10 - 0
fe/static/js/1.1937b80d952ec9ba8012.js


BIN
fe/static/js/1.1937b80d952ec9ba8012.js.gz


File diff suppressed because it is too large
+ 10 - 0
fe/static/js/1.d47e2dcd0b1e74a9f703.js


BIN
fe/static/js/1.d47e2dcd0b1e74a9f703.js.gz


File diff suppressed because it is too large
+ 1 - 0
fe/static/js/11.8d458c7a426ca33d1f4b.js


File diff suppressed because it is too large
+ 1 - 0
fe/static/js/11.ad09d7600b0017fc9a8b.js


+ 1 - 0
fe/static/js/12.30bb65651fdc0973b404.js

@@ -0,0 +1 @@
+webpackJsonp([12],{"/Fi9":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r={components:{WlAbout:n("hr+2").a}},s={render:function(){var e=this.$createElement,t=this._self._c||e;return t("div",{staticClass:"wl-home"},[t("wl-about")],1)},staticRenderFns:[]};var l=n("VU/8")(r,s,!1,function(e){n("d/BB")},null,null);t.default=l.exports},"d/BB":function(e,t){}});

File diff suppressed because it is too large
+ 1 - 0
fe/static/js/13.99c687afb273897c0fb3.js


File diff suppressed because it is too large
+ 1 - 0
fe/static/js/app.6bb8d1994240946e5f1b.js


BIN
fe/static/js/app.6bb8d1994240946e5f1b.js.gz


File diff suppressed because it is too large
+ 1 - 0
fe/static/js/app.fe68a2cf72c802327a5a.js


BIN
fe/static/js/app.fe68a2cf72c802327a5a.js.gz


File diff suppressed because it is too large
+ 1 - 0
fe/static/js/manifest.6d724d91b62598c00b3d.js


File diff suppressed because it is too large
+ 1 - 0
fe/static/js/manifest.a0af51ee57e7900644cf.js


+ 1 - 1
requirements/prod.txt

@@ -38,6 +38,6 @@ Flask-Bcrypt==0.7.1
 anyjson==0.3.3
 celery==3.1.18
 pycrypto==2.6.1
-pytz==2015.6
+pytz==2015.7
 requests==2.20.0
 GitPython==2.1.11

BIN
screenshot/weixin-jiangyongping.jpg


BIN
screenshot/weixin-xupengfei.jpg


BIN
screenshot/weixin-zouhongxue.jpg


+ 1 - 0
walle/api/api.py

@@ -73,6 +73,7 @@ class SecurityResource(ApiResource):
 
     def __init__(self):
         if current_user.is_authenticated:
+            current_user.fresh_session()
             self.space_id = None if current_user.role == SUPER else session['space_id']
 
     # @login_required

+ 10 - 2
walle/api/passport.py

@@ -16,6 +16,7 @@ from walle.api.api import ApiResource
 from walle.form.user import LoginForm
 from walle.model.user import UserModel
 from walle.service.code import Code
+from walle.service.error import WalleError
 
 class PassportAPI(ApiResource):
     actions = ['login', 'logout']
@@ -46,8 +47,15 @@ class PassportAPI(ApiResource):
             user = UserModel.query.filter_by(email=form.email.data).first()
 
             if user is not None and user.verify_password(form.password.data):
-                login_user(user)
-                user.fresh_session()
+                try:
+                    remember = False
+                    if current_app.config.get("COOKIE_ENABLE"):
+                        remember = True
+                    current_app.logger.info("remember me(记住我)功能是否开启,{}".format(remember))
+                    login_user(user, remember=remember)
+                    user.fresh_session()
+                except WalleError as e:
+                    return self.render_json(code=e.code, data=Code.code_msg[e.code])
                 return self.render_json(data=current_user.to_json())
 
         return self.render_json(code=Code.error_pwd, data=form.errors)

+ 12 - 6
walle/api/space.py

@@ -125,16 +125,22 @@ class SpaceAPI(SecurityResource):
             space = SpaceModel().get_by_id(space_id)
             data = form.form2dict()
             current_app.logger.info(data)
-
+            member_model = MemberModel(group_id=space_id)
+            old_owner = space.user_id
+            new_owner = data['user_id']
             # a new type to update a model
-            ret = space.update(data)
+            space.update(data)
+
+            if str(old_owner) != str(new_owner):
+                # owner has changed
+                member_model.change_owner(old_owner, new_owner)
+
             # create group
-            member = {"user_id": data['user_id'], "role": OWNER}
-            members = []
+            current_owner = {"user_id": new_owner, "role": OWNER}
             if 'members' in request.form:
                 members = json.loads(request.form['members'])
-                members.append(member)
-            MemberModel(group_id=space_id).update_group(members=members)
+                members.append(current_owner)
+                member_model.update_group(members=members)
             return self.render_json(data=space.item())
         else:
             return self.render_error(code=Code.form_error, message=form.errors)

+ 4 - 1
walle/api/task.py

@@ -39,7 +39,10 @@ class TaskAPI(SecurityResource):
         size = int(request.args.get('size', 10))
         kw = request.values.get('kw', '')
 
-        task_list, count = TaskModel().list(page=page, size=size, kw=kw, space_id=self.space_id)
+        user_id = request.values.get('user_id', '')
+        user_id = user_id.split(',') if user_id else []
+
+        task_list, count = TaskModel().list(page=page, size=size, kw=kw, space_id=self.space_id, user_id=user_id)
         return self.list_json(list=task_list, count=count, enable_create=permission.role_upper_reporter() and current_user.role != SUPER)
 
     def item(self, task_id):

+ 1 - 1
walle/api/user.py

@@ -103,7 +103,7 @@ class UserAPI(SecurityResource):
             try:
                 emails.send_email(user.email, 'Welcome to walle', message, '')
             except Exception as e:
-                # todo, responses error message to the front.
+                # todo, response error message to the front.
                 current_app.logger.warning(e)
 
             return self.render_json(data=user.item(user_id=user.id))

+ 5 - 1
walle/app.py

@@ -6,6 +6,8 @@ gevent.monkey.patch_all()
 import logging
 import sys
 import os
+import threading
+
 from flask import Flask, render_template, current_app
 from flask_restful import Api
 from walle import commands
@@ -187,7 +189,9 @@ def register_socketio(app):
         return app
     socketio.init_app(app, async_mode='gevent')
     socketio.on_namespace(WalleSocketIO(namespace='/walle'))
-    socketio.run(app, debug=app.config.get('DEBUG'), host=app.config.get('HOST'), port=app.config.get('PORT'))
+    socket_args = {"debug": app.config.get('DEBUG'), "host": app.config.get('HOST'), "port": app.config.get('PORT')}
+    socket_thread = threading.Thread(target=socketio.run, name="socket_thread", args=(app, ), kwargs=socket_args)
+    socket_thread.start()
     return app
 
 

+ 4 - 1
walle/config/settings.py

@@ -6,7 +6,7 @@ from datetime import timedelta
 
 class Config(object):
     """Base configuration."""
-    VERSION = '2.0.0'
+    VERSION = '2.0.1'
 
     SECRET_KEY = os.environ.get('WALLE_SECRET', 'secret-key')
     APP_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
@@ -49,3 +49,6 @@ class Config(object):
     # 轮转数量是 10 个
     LOG_FILE_BACKUP_COUNT = 10
     LOG_FORMAT = "%(asctime)s %(thread)d %(message)s"
+
+    # 登录cookie 防止退出浏览器重新登录
+    COOKIE_ENABLE = False

+ 3 - 0
walle/config/settings_dev.py

@@ -33,3 +33,6 @@ class DevConfig(Config):
     CODE_BASE = '/tmp/walle/codebase/'
 
     SQLALCHEMY_ECHO = True
+
+    # 登录cookie 防止退出浏览器重新登录
+    COOKIE_ENABLE = False

+ 7 - 0
walle/config/settings_prod.py

@@ -29,6 +29,10 @@ class ProdConfig(Config):
 
     # 数据库设置 @TODO
     SQLALCHEMY_DATABASE_URI = 'mysql://user:password@localhost:3306/walle?charset=utf8'
+    # 阿里云RDS强制释放空闲连接导致经常报错 mysql server has gone way
+    # 适当修改该参数即可 单位为秒
+    # N秒不用的连接自动释放
+    # SQLALCHEMY_POOL_RECYCLE = 30
 
     # 本地代码检出路径(用户查询分支, 编译, 打包) #TODO
     CODE_BASE = '/tmp/walle/codebase/'
@@ -49,3 +53,6 @@ class ProdConfig(Config):
     MAIL_DEFAULT_SENDER = 'service@walle-web.io'
     MAIL_USERNAME = 'service@walle-web.io'
     MAIL_PASSWORD = 'Ki9y&3U82'
+
+    # 登录cookie 防止退出浏览器重新登录
+    COOKIE_ENABLE = False

+ 3 - 0
walle/config/settings_test.py

@@ -32,3 +32,6 @@ class TestConfig(Config):
 
     # 本地代码检出路径(用户查询分支, 编译, 打包) #TODO
     CODE_BASE = '/tmp/walle/codebase/'
+
+    # 登录cookie 防止退出浏览器重新登录
+    COOKIE_ENABLE = True

+ 2 - 2
walle/form/user.py

@@ -52,7 +52,7 @@ class RegistrationForm(UserForm):
 
     def validate_username(self, field):
         """ username muse be unique """
-        if UserModel.query.filter(UserModel.username == field.data, UserModel.username != -1).count():
+        if UserModel.query.filter(UserModel.username == field.data, UserModel.status != -1).count():
             raise ValidationError('此用户名已经被注册')
 
 
@@ -68,4 +68,4 @@ class UserUpdateForm(FlaskForm):
 class LoginForm(FlaskForm):
     email = StringField('email', [validators.Length(min=6, max=35),
                                   Regexp(r'^(.+)@(.+)\.(.+)', message='邮箱格式不正确')])
-    password = PasswordField('Password', [validators.Length(min=6, max=35)])
+    password = PasswordField('Password', [validators.Length(min=6)])

+ 3 - 3
walle/model/environment.py

@@ -10,7 +10,7 @@ from datetime import datetime
 
 from sqlalchemy import String, Integer, DateTime
 from walle import model
-from walle.model.database import db, Model
+from walle.model.database import db, Model, or_
 from walle.service.extensions import permission
 
 
@@ -39,13 +39,13 @@ class EnvironmentModel(Model):
         :param kw:
         :return:
         """
+        SpaceModel = model.space.SpaceModel
         query = self.query.filter(EnvironmentModel.status.notin_([self.status_remove]))
         if kw:
-            query = query.filter(EnvironmentModel.name.like('%' + kw + '%'))
+            query = query.filter(or_(EnvironmentModel.name.like('%' + kw + '%'), SpaceModel.name.like('%' + kw + '%')))
         if space_id:
             query = query.filter(EnvironmentModel.space_id == space_id)
 
-        SpaceModel = model.space.SpaceModel
         query = query.join(SpaceModel, SpaceModel.id == EnvironmentModel.space_id)
         query = query.add_columns(SpaceModel.name)
         count = query.count()

+ 33 - 2
walle/model/member.py

@@ -114,11 +114,19 @@ class MemberModel(SurrogatePK, Model):
         }
         MemberModel.query.filter(*filters).delete()
 
+        member_role = []
+
         current_app.logger.info(members)
         # insert all
         for member in members:
             current_app.logger.info(member)
             current_app.logger.info(member['role'])
+
+            # 过滤重复数据 同一空间下同一用户不能有同样的角色
+            if (int(member['user_id']), str(member['role']).upper()) in member_role:
+                continue
+            member_role.append((member['user_id'], member['role'].upper()))
+
             update = {
                 'user_id': member['user_id'],
                 'source_id': self.group_id,
@@ -133,6 +141,29 @@ class MemberModel(SurrogatePK, Model):
 
         return ret
 
+    def change_owner(self, old_owner_id, new_owner_id):
+        # change owner for space
+        if not new_owner_id or str(new_owner_id) == str(old_owner_id):
+            return
+
+        filters = {
+            MemberModel.source_id == self.group_id,
+            MemberModel.source_type == self.source_type_group,
+            MemberModel.user_id.in_([old_owner_id, new_owner_id])
+        }
+        MemberModel.query.filter(*filters).delete(synchronize_session=False)
+
+        update = {
+            'user_id': new_owner_id,
+            'source_id': self.group_id,
+            'source_type': self.source_type_group,
+            'access_level': OWNER,
+            'status': self.status_available,
+        }
+        m = MemberModel(**update)
+        db.session.add(m)
+        db.session.commit()
+
     def update_project(self, project_id, members, group_name=None):
         space_info = model.project.ProjectModel.query.filter_by(id=project_id).first().to_json()
         space_members, count, user_ids = self.members(group_id=space_info['space_id'], size=-1)
@@ -172,7 +203,7 @@ class MemberModel(SurrogatePK, Model):
 
         return ret
 
-    def members(self, group_id=None, project_id=None, page=0, size=10, kw=None):
+    def members(self, group_id=None, project_id=None, page=0, size=None, kw=None):
         """
         获取单条记录
         :param role_id:
@@ -194,7 +225,7 @@ class MemberModel(SurrogatePK, Model):
 
         count = query.count()
         query = query.order_by(MemberModel.id.asc())
-        if size > 0:
+        if size and size>0:
             query = query.offset(int(size) * int(page)).limit(size)
         data = query.all()
 

+ 4 - 1
walle/model/task.py

@@ -73,7 +73,7 @@ class TaskModel(SurrogatePK, Model):
     #     return dict(project_info, **self.taskMdl)
     #
 
-    def list(self, page=0, size=10, space_id=None, kw=None):
+    def list(self, page=0, size=10, space_id=None, user_id=None, kw=None):
         """
         获取分页列表
         :param page:
@@ -99,6 +99,9 @@ class TaskModel(SurrogatePK, Model):
         if space_id:
             query = query.filter(ProjectModel.space_id == space_id)
 
+        if user_id:
+            query = query.filter(TaskModel.user_id.in_(user_id))
+
         query = query.add_columns(ProjectModel.name, EnvironmentModel.name, ProjectModel.keep_version_num)
         count = query.count()
 

+ 27 - 22
walle/service/deployer.py

@@ -72,27 +72,32 @@ class Deployer:
             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())),
-            )
+            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': str(self.taskMdl.get('branch')),
+                'BRANCH': format_export(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_NAME': format_export(self.project_info['name']),
                 'PROJECT_ID': str(self.project_info['id']),
-                'TASK_NAME': str(self.taskMdl.get('name')).replace('"', '').replace("'", '').replace(" ", '_'),
+                '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:
@@ -277,9 +282,10 @@ class Deployer:
             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)
+            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
@@ -374,7 +380,7 @@ class Deployer:
                 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' % (
+                    'how': '在宿主机中配置免密码登录,把宿主机用户%s的~/.ssh/id_rsa.pub添加到远程目标机器用户%s的~/.ssh/authorized_keys。了解更多:http://walle-web.io/docs/troubleshooting.html' % (
                     pwd.getpwuid(os.getuid())[0], server_info['host']),
                 })
 
@@ -428,7 +434,7 @@ class Deployer:
         with waller.cd(self.project_info['target_releases']):
             result = waller.run(command, wenv=self.config())
 
-        command = 'rm -rf `ls -t {project_id}_* | tail -n +{keep_version_num}`'.format(
+        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())
@@ -486,10 +492,10 @@ class Deployer:
                     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])
+                    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='')
@@ -514,17 +520,16 @@ class Deployer:
 
         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])
+                    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='')

+ 41 - 40
walle/service/git/repo.py

@@ -28,44 +28,45 @@ class Repo:
         self.path = path
 
 
-    def log(self, wenv):
-        '''
-
-        @param wenv:
-
-            @param stage:
-            @param sequence:
-            @param user_id:
-            @param task_id:
-            @param status:
-            @param host:
-            @param user:
-            @param command:
-            @param success:
-            @param error:
-
-        @return:
-        '''
-        RecordModel().save_record(stage=wenv['stage'], sequence=wenv['sequence'], user_id=wenv['user_id'],
-                                  task_id=wenv['task_id'], status=exitcode, host='127.0.0.1', user=getpass.getuser(),
-                                  command=result.command, success=stdout,
-                                  error=stderr)
-
-    def websocket(self):
-        ws_dict = {
-            'user': getpass.getuser(),
-            'host': '127.0.0.1',
-            'cmd': command,
-            'status': exitcode,
-            'stage': wenv['stage'],
-            'sequence': wenv['sequence'],
-            'success': stdout,
-            'error': stderr,
-        }
-        if wenv['console']:
-            emit('console', {'event': 'task:console', 'data': ws_dict}, room=wenv['task_id'])
-
-        pass
+    # What follows is the risk code for flake8.
+    # def log(self, wenv):
+    #     '''
+    #
+    #     @param wenv:
+    #
+    #         @param stage:
+    #         @param sequence:
+    #         @param user_id:
+    #         @param task_id:
+    #         @param status:
+    #         @param host:
+    #         @param user:
+    #         @param command:
+    #         @param success:
+    #         @param error:
+    #
+    #     @return:
+    #     '''
+    #     RecordModel().save_record(stage=wenv['stage'], sequence=wenv['sequence'], user_id=wenv['user_id'],
+    #                               task_id=wenv['task_id'], status=exitcode, host='127.0.0.1', user=getpass.getuser(),
+    #                               command=result.command, success=stdout,
+    #                               error=stderr)
+    #
+    # def websocket(self):
+    #     ws_dict = {
+    #         'user': getpass.getuser(),
+    #         'host': '127.0.0.1',
+    #         'cmd': command,
+    #         'status': exitcode,
+    #         'stage': wenv['stage'],
+    #         'sequence': wenv['sequence'],
+    #         'success': stdout,
+    #         'error': stderr,
+    #     }
+    #     if wenv['console']:
+    #         emit('console', {'event': 'task:console', 'data': ws_dict}, room=wenv['task_id'])
+    #
+    #     pass
 
 
     def is_git_dir(self):
@@ -166,13 +167,13 @@ class Repo:
 
     def tags(self):
         '''
-        获取所有tag
+        获取所有tag,按时间倒序
 
         @param branch:
         @param kwargs:
         @return:
         '''
-        return [str(tag) for tag in PyRepo(self.path).tags]
+        return [str(tag) for tag in PyRepo(self.path).tags][-10:]
 
     def commits(self, branch):
         '''

+ 1 - 1
walle/service/websocket.py

@@ -62,7 +62,7 @@ class WalleSocketIO(Namespace):
             branches = wi.list_branch()
             emit('branches', {'event': 'branches', 'data': branches}, room=self.room)
         except Exception as e:
-            emit('branches', {'event': 'error', 'data': {'message': e.message}}, room=self.room)
+            emit('branches', {'event': 'error', 'data': {'message': e.message or str(e)}}, room=self.room)
 
     def on_tags(self, message=None):
         wi = Deployer(project_id=self.room)