Пример #1
0
def stats():
    """
    查看应用状态,接受的参数为`name=n1,n2,n3`

    //查询成功返回示例
    {
        "success":true,
        "data":{
            "name1":-1,
            "name2":0,
            "name3":1
        }
    }
    //查询失败返回示例
    {
        "success":false,
        "message":"无法查询容器状态,请检查 Docker 是否运行"
    }
    :return:
    """
    names = Q('names', "", str).split(",")
    containers = services.list_all_container(True)
    LOG.info("当前所有容器状态:%s", containers)
    data = dict(
        (n, -1 if n not in containers else 1 if containers[n]['stat'] ==
         'running' else 0) for n in names)
    return jsonify(Result.ok(data=data))
Пример #2
0
def uploadNewVersion():
    """
    上传 app 新版本资源

    1. 若 app 存在(request.id > 0)
        从数据库中获取对应的 app

    2. 若 app 不存在(request.id=0)
        则 request.name 不能为空
        创建新的 app

    :return:
    """
    file = __detect_file()

    app = __detect_app()
    auto_create = app.id is None
    if auto_create:
        db.session.add(app)

    # 保存文件到 attachments 目录
    saved_file = getAttachPath(file.filename)
    LOG.info("上传 %s 到 %s" % (file.filename, saved_file))
    file.save(saved_file)
    resource = Resource.fromFile(saved_file, app)
    db.session.add(resource)

    is_update = Q('update', 'true', str).upper() == str(True).upper()

    name, files = services.load_from_file(saved_file, app, is_update)

    db.session.commit()
    return jsonify(Result.ok("%s 应用新版本部署成功" % name, files))
Пример #3
0
def filesystemUpdate(name):
    """

    :param name:
    :return:
    """
    location = Q("location", "", str)

    file = os.path.join(__detect_app_dir(name), location)
    if not os.path.exists(file):
        raise ServiceException("待操作的文件不存在:%s" % location)

    # 优先判断是否为删除文件
    if Q("del", 0, int) == 1:
        os.remove(file)
        LOG.info("删除 %s/%s " % (name, location))
    else:
        content = Q("content")
        if content is None:
            raise ServiceException("请输入更新的内容")

        with open(file, 'w', encoding=ENCODING) as f:
            f.write(content)
            LOG.info("更新 %s/%s 的内容" % (name, location))

    return jsonify(Result.ok())
Пример #4
0
def buildJobs(app, config):
    if hasattr(config, 'JOBS'):
        scheduler = APScheduler()
        scheduler.init_app(app)
        scheduler.start()
    else:
        LOG.info(
            "JOBS is not defined on Config that Scheduler will not start...")
Пример #5
0
def operate(name, op):
    if op not in services.OPERATIONS:
        raise ServiceException("无效的操作类型:{} (可选:{})".format(
            op, services.OPERATIONS))

    LOG.info("即将对容器 %s 执行 %s 操作...", name, op)
    services.do_with_container(name, op)
    return jsonify(Result.ok("{} 执行 {} 操作成功".format(name, op)))
Пример #6
0
def heartbeat(data):
    """
    心跳测试,直接返回参数
    :param data:
    :return:
    """
    LOG.info("heartbeat testing : %s", data)
    return data
Пример #7
0
def delete(aid=None):
    aid = aid if aid is not None else Q('ids', type=int)
    LOG.debug("客户端请求删除 ID=%d 的应用..." % aid)

    app = __loadApp(aid)
    db.session.delete(app)
    db.session.commit()
    LOG.info("删除 ID=%d 的应用成功" % aid)
    return jsonify(Result.ok())
Пример #8
0
def clean(aid):
    LOG.debug("客户端请求清空 ID=%s 的应用数据..." % aid)

    app = __loadApp(aid)
    app_dir = services.detect_app_dir(app)
    if os.path.exists(app_dir):
        shutil.rmtree(app_dir)
        LOG.info("删除 id={} 的应用数据:{}".format(aid, app_dir))

    return jsonify(Result.ok())
Пример #9
0
 def global_error_handler(exception):
     """
     全局的异常处理:
     1. 打印到 logger :以便记录异常信息
     2. 封装成 Result 对象,以 json 格式返回到 Client
     :param exception:
     :return:
     """
     LOG.error("%s\n%s", exception, traceback.format_exc())
     return jsonify(Result.error(exception)), 500
Пример #10
0
def delete(aid=None):
    aid = aid if aid is not None else Q('ids', type=int)
    LOG.info("客户端请求删除 ID=%d 的资源..." % aid)

    app = Resource.query.get(aid)
    if app:
        db.session.delete(app)
        db.session.commit()
        LOG.info("成功删除 ID=%d 的资源" % aid)
        return jsonify(Result.ok())
    else:
        raise ServiceException("ID=%d 的成功不存在故不能执行删除操作..." % aid)
Пример #11
0
def checkDocker():
    with db.app.app_context():
        __tip("开始检测 docker connection...")
        if docker.client is None:
            LOG.debug("检测到 docker.client 未实例化,即将进行初始化...")
            docker.setup(current_app.config)
            LOG.info("^.^ docker.client 初始化成功 ^.^")
        else:
            try:
                LOG.debug("^.^ docker server 通讯正常 : ping=%s ^.^", docker.client.ping())
            except Exception as e:
                LOG.info("调用 docker.client.ping() 时出错:%s", str(e))
                docker.cleanup()
        __tip()
Пример #12
0
def add():
    """
    录入新的应用
    :return:
    """
    ps = request.values
    name = ps.get('name')
    version = ps.get('version', default="1.0.0")

    notEmptyStr(name=name, version=version)

    id = ps.get('id')

    app = Application(name=name,
                      id=id,
                      version=version,
                      remark=ps.get('remark'))

    if id and int(id) > 0:
        oldApp = Application.query.get(id)
        if oldApp is None:
            raise ServiceException("ID=%d 的应用不存在故不能编辑" % id)

        copyEntityBean(app, oldApp)
    else:
        # 判断应用名是否重复
        oldApp = Application.query.getOne(name=name)
        if oldApp:
            raise ServiceException("应用 %s 已经存在,不能重复录入" % name)

        db.session.add(app)

    db.session.commit()

    op = "录入" if id is 0 else "编辑"
    LOG.info("%s应用 %s" % (op, app))
    return jsonify(Result.ok("应用 %s %s成功(版本=%s)" % (name, op, version),
                             app.id))
Пример #13
0
def init_docker(config):
    try:
        network = docker.setup(config)
        LOG.info("docker client setup done: \n %s \n default network: %s",
                 docker.version(), network)
    except Exception as e:
        LOG.error(
            "cannot connection to Docker Server , please check your config: %s",
            str(e))
        print(traceback.format_exc())
        LOG.error("检测到 Docker 配置有误,请重新配置否则无法正常使用相关的功能")
Пример #14
0
def __tip(msg=None):
    """

    :param msg:
    :return:
    """
    LOG.debug("================== SCHEDULE ==================")
    if msg is not None:
        stack = inspect.stack()
        the_method = stack[1][0].f_code.co_name
        LOG.debug("| method: %s", the_method)
        LOG.debug("| %s", msg)
Пример #15
0
 def index():
     """"
     跳转到 static/index.html
     """
     LOG.debug("visit index page %s", config.SERVER_INDEX_PAGE)
     return app.send_static_file(config.SERVER_INDEX_PAGE)
Пример #16
0
 def service_error_handler(exception):
     LOG.error("%s\n%s", exception, traceback.format_exc())
     return jsonify(Result.error(exception)), 500
Пример #17
0
 def internal_error_handler(exception):
     LOG.error("[400] %s\n%s", exception, traceback.format_exc())
     return jsonify(Result.error(exception)), 400
Пример #18
0
def create_app(config_name=None, customs=None):
    config = getConfig(config_name, customs)

    initLogger(config)

    if config.DOCKER_ABLE:
        init_docker(config)

    app = Flask(__name__,
                static_url_path='',
                static_folder=config.SERVER_STATIC_DIR)

    # What it does is prepare the application to work with SQLAlchemy.
    # However that does not now bind the SQLAlchemy object to your application.
    # Why doesn’t it do that? Because there might be more than one application created.
    # >>> from yourapp import create_app
    # >>> app = create_app()
    # >>> app.app_context().push()
    app.app_context().push()

    app.json_encoder = SQLAlchemyEncoder

    app.config.from_object(config)
    config.init_app(app)

    db.app = app
    db.init_app(app)
    try:
        db.create_all()
        LOG.info("call db.create_all() success!")
    except Exception as e:
        LOG.error("error on try to create all tables", e)

    buildBlueprint(app)

    buildJobs(app, config)

    @app.route('/')
    def index():
        """"
        跳转到 static/index.html
        """
        LOG.debug("visit index page %s", config.SERVER_INDEX_PAGE)
        return app.send_static_file(config.SERVER_INDEX_PAGE)

    # @app.route('/<path:file_relative_path_to_root>', methods=['GET'])
    # # @app.route('/static/<path:path>')
    # def static_resource(path):
    #     return send_from_directory(config.SERVER_STATIC_DIR, path)

    @app.errorhandler(404)
    def page_not_found(error):
        return jsonify(Result.error('[404] Page not found!')), 404

    @app.errorhandler(Exception)
    def global_error_handler(exception):
        """
        全局的异常处理:
        1. 打印到 logger :以便记录异常信息
        2. 封装成 Result 对象,以 json 格式返回到 Client
        :param exception:
        :return:
        """
        LOG.error("%s\n%s", exception, traceback.format_exc())
        return jsonify(Result.error(exception)), 500

    @app.errorhandler(ServiceException)
    def service_error_handler(exception):
        LOG.error("%s\n%s", exception, traceback.format_exc())
        return jsonify(Result.error(exception)), 500

    @app.errorhandler(500)
    def internal_error_handler(exception):
        LOG.error("[500] %s\n%s", exception, traceback.format_exc())
        return jsonify(Result.error(exception)), 500

    @app.errorhandler(400)
    def internal_error_handler(exception):
        LOG.error("[400] %s\n%s", exception, traceback.format_exc())
        return jsonify(Result.error(exception)), 400

    # 定位静态文件夹为上级 static,否则无法正常浏览静态资源
    # app.static_folder = '../static'

    return app, config
Пример #19
0
def load_from_file(file_path: str, application: Application, update=False,  **kwargs):
    """
    从指定的目录加载文件(用户上传的文件要求为`zip`格式的压缩文件)
    :param application:
    :param update: True = 迭代更新,False = 全新部署
    :param file_path:
    :param kwargs:
        remove  是否移除同名的旧容器,默认 True

    :return:
    """
    if file_path is None or not os.path.exists(file_path):
        raise ServiceException("参数 file_path 未定义或者找不到相应的文件:%s" % file_path)

    if not file_path.endswith(ZIP):
        raise ServiceException("load_from_file 只支持 %s 结尾的文件" % ZIP)

    app_dir = detect_app_dir(application)

    if update:
        files = update_app_with_zip(file_path, app_dir)
        return application.name, files

    unzip_dir, files = unzip(file_path)
    LOG.info("解压到 %s" % unzip_dir)

    container_name = application.name

    for file in [str(f) for f in files]:
        LOG.debug("processing %s", file)
        if file.endswith(TAR):
            LOG.info("检测到 %s 文件 %s,即将导入该镜像..." % (TAR, file))
            docker.loadImage(os.path.join(unzip_dir, file))
            LOG.info("docker 镜像导入成功")

        if file.endswith(ZIP):
            """对于 zip 格式的文件,解压到程序根目录"""
            unzip(os.path.join(unzip_dir, file), app_dir)
            LOG.info("解压 %s 到 %s" % (file, app_dir))

        if file in ["{}-{}.jar".format(application.name, application.version), "{}.jar".format(application.name)]:
            """
            对于 {application.name}.jar 、 {application.name}-{version}.jar 的文件,直接复制到 app_dir
            通常经过 spring-boot 打包的 jar 可以直接运行
            """
            copyFileToDir(os.path.join(unzip_dir, file), app_dir)

        if file == APP_JSON:
            with open(os.path.join(unzip_dir, file)) as app_json:
                content = __transform_placeholder(app_json.read(), application)
                LOG.info("获取并填充 %s :%s" % (APP_JSON, content))

                app_ps = json.loads(content)
                if 'args' not in app_ps:
                    app_ps['args'] = {}
                if 'image' not in app_ps:
                    raise ServiceException("{} 中必须定义 'image' 属性,否则无法创建容器".format(APP_JSON))

            container_name = detect_app_name(app_ps['args'], app_ps['image'])
            LOG.info("检测到 容器名:%s", container_name)

            # 判断是否需要移除旧的 container
            old_container = None
            if kwargs.pop("remove", True):
                try:
                    old_container = docker.getContainer(container_name)
                    LOG.info("name={} 的容器已经存在:{}, id={}".format(container_name, old_container, old_container.id))
                except Exception:
                    pass

                if old_container is not None:
                    try:
                        old_container.remove()
                        LOG.info("成功删除name=%s 的容器" % container_name)
                    except Exception as e:
                        raise ServiceException("无法删除 name={} 的容器: {}".format(container_name, str(e)))

            network = docker.createDefaultNetwork()
            app_ps['args']['network'] = network.name
            docker.createContainer(app_ps['image'], container_name, app_ps['cmd'], app_ps['args'])
            LOG.info("APP 容器 创建成功(image=%s,name=%s, network=%s)" % (app_ps['image'], container_name, network.name))

    shutil.rmtree(unzip_dir)

    return container_name, files
Пример #20
0
from buter.logger import LOG
from buter.server import create_app

app, config = create_app()

if __name__ == '__main__':
    #
    # 打印 url rule :werkzeug.routing.Rule
    # 格式参考 Spring Mvc:
    # Mapped "{[/manage/account/{id}],methods=[GET]}"
    #   onto public T com.zeus.web.controller.AbstractController.get(java.lang.Long)
    #
    for rule in app.url_map.iter_rules():
        LOG.info("Mapped {:30} methods={:30} onto {}".format(
            rule.rule, ', '.join(rule.methods), rule.endpoint))

    app.run(host=config.SERVER_HOST,
            port=config.SERVER_PORT,
            debug=config.DEBUG,
            use_reloader=config.USE_RELOADER,
            ssl_context=config.HTTPS)