Python github 私有项目通过 buildbot 进行 Review

Sun 22 May 2016

背景

随着公司开发团队的壮大, 团队中每个人的水平参差不齐, 为了保证项目质量我们打算对 提交的代码进行 review, 但是苦于一直没有好的 review 机制. 前段时间我在逛 Rust 社区是发现了他们有一个 review 机器人 Homu 非常不错, 研究一下后我将其应用到我们当前 Python 项目中来配合 review, 我感觉非常棒, 今天抽空就分享给大家.

技术栈

本文涉及的项目和技术有:

0. 隔离 Github 部署 Key

Github 可以添加部署 Key 来实现部署, 但是每个项目必须是不同的部署 key. 这就给 多个私有项目的可持续集成带来一定的困难, 因为 buildbot 是通过轮询来获取 git 分支 变更的, 并且 buildbot 不支持指定私钥.

我们前期想到的是通过 Docker 来分别跑每个项目的 buildbot, 这样部署 key 就 可以很容易进行隔离. 后面我们找到一种更好的方式来指定部署 Key: 通过 ssh_config 来指定别名.

$ cat ~/.ssh/config
Host github-project-a-alias
    Hostname github.com
    IdentityFile "~/.ssh/project_a_deploy_key"

Host github-project-b-alias
    Hostname github.com
    IdentityFile "~/.ssh/project_b_deploy_key"

这样就可以将原有的项目地址做如下转换

- git@github.com:Owner/a
+ git@github-project-a-alias:Owner/a

- git@github.com:Owner/b
+ git@github-project-b-alias:Owner/b

1. 搭建 homu

Homu 是一个构建在 Github 和 buildbot(或 Travis) 之上的工具, 它通过监听 PR 里的评论来触发 buildbot 构建, 并监听构建结果, 如果构建成功则自动合并 PR

Homu 的搭建可以参见其项目地址有简单的介绍, 非常简单这里就不在赘述.

Homu 是针对 Rust 开发, 并没有考虑私有项目, 所以其信息都是公开的, 为了减少 暴漏信息带来的安全性问题, 我们将 Homu 搭建在内网并通过 ssh 端口转发将 Homu 的 端口发送到一台公网服务器:

ssh -o TCPKeepAlive=yes -CfNgR 127.0.0.1:54856:127.0.0.1:54856 user@remote.ip.address

然后通过以下 nginx 配置文件来仅暴漏供 Github webhook 调用的地址:

upstream homu {
    server 127.0.0.1:54856;
}


server {
    listen public.ip.address:54856;

    location /github {
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_pass http://homu;
    }

    location ~* (?:^|/)\. {
        deny all;
    }
}

这样就可以隐藏 Homu 暴露的信息, 其他完整功能可以通过内网访问.

2. 搭建 buildbot

buildbot 其官方文档也比较详细, 可以参考其官方文档进行搭建, 需要注意的是目前 homu 适配的 buildbot 是 0.8, 目前 buildbot-0.9 正在发布 beta, 和 0.8 有很多地方都不兼容.

这里着重说一下结合 pylint 做代码检查.

代码检查

我们希望通过 buildbot 使用 pylint 来做代码检查, 但是考虑到历史遗留问题我们仅 希望对当前提交的变更做检查. 综合一下几个方面 git-pylint-commit-hook 可 以完美的满足需求:

  • git-pylint-commit-hook 配置成 pre-commit 可以对将要被提交(git add 过)的文件进行检查
  • 通过 git reset HEAD^ --soft 可以将上一次提交的文件变成将要被提交的文件
  • Homu 在接受(r+)某一 PR 时会将变更通过 --no-ff(no fast-forward) 的方式合并到 auto 分支
  • 通过 no fast-forward 合并相当于将所涉及的提交打了一个节点, 在通过 git reset HEAD^ --soft 进行重置时会将所涉及的提交的文件都变成将要被提交的文件
$ git checkout -b feature-test
$ # edit file a
$ git add a && git commit -m 'Change file a'
$ # edit file b
$ git add b && git commit -m 'Change file b'
$ git checkout master
$ git merge --no-ff feature-test
$ git reset HEAD^ --soft
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   a
        modified:   b

buildbot 配置文件片段如下:

lint_factory = util.BuildFactory()
lint_factory.addStep(steps.Git(repourl=repo, mode='full', branch='auto', progress=True))
lint_factory.addStep(steps.ShellCommand(command=["pip", "install", "-U", "-r", "requirements.txt"]))
lint_factory.addStep(steps.ShellCommand(command=["git", "reset", "HEAD^", "--soft"]))
lint_factory.addStep(steps.ShellCommand(command=["git-pylint-commit-hook", "--limit=8", "--pylintrc=./.pylintrc"]))

c['builders'].append(
    util.BuilderConfig(name="auto-lint",
      slavenames=["example-slave"],
      factory=lint_factory))

可根据自身需求做变更, 比如 --limit=8 来限制最低分数, 低于这个分数将导致构建 失败.

buildbot 可自定义性很强, homu 是可以支持多个 builder 的. 所以除了代码检查我们 还有测试. Homu 在检测这两个 Builder 都构建成功后才会合并当前 PR.

3. 工作流

实现了如上功能之后我们对工作流进行了调整:

  1. 通过主题分支开发并往长期分支提交 PR
  2. reviewer 在 LGTM 后提交评论 r+ 并 @ Homu 账号(@homu r+)
  3. 之后 Homu 会合并 PR 变更到 auto 分支
  4. buildbot 监听到 auto 分支的变更后就会运行相应构建并在完成后后通知 Homu
  5. Homu 在收到成功的通知后合并 PR 到长期分支

4. 已知问题

Homu 在将 auto 分支合并到长期分支时会采用 fast-forward 的方式进行合并, 所以不会产生新的 commit, buildbot 0.8 对同一提交(sha 相同) 仅触发一次 build. 所以会导致其他一些比如自动部署的 builder 无法触发.

Category: Python Tagged: Python github 私有 可持续集成 homu buildbot review

comments


Python mock 使用心得

Sun 03 April 2016

好久没有更新博客, 趁着清明节小长假和我儿子正在睡觉更新一篇刷刷存在感. 近来变化很多, 儿子也有了, 工作上也有很多收获. 这篇博客就分享一下关于 mock 的使用的心得体会.

很长一段时间以来写单元测试都类似写执行脚本, 运行一下然后看一下结果. 这里面有一部分原因是因为无法规避外部的依赖组件, 比如:

  • 数据库操作
  • 外部接口调用
  • 外部其他不可控因素

这样写测试只关心当前测试的结果, 而不去管其他测试是否 passed.

后面随着团队开始进新人, 由于团队里每个人的标准和水平不同, 开始不得不重视整体项目的质量, 发现没有好的测试就没有统一的标准来衡量提交代码的质量, 当然说到代码质量还有另外一个和测试放在一起的标准就是代码风格, 这不是本文的主题所里这里就暂且不提.

为了能写好测试就不得不面对现实项目的复杂性, 诸如外部接口数据库操作等. 这时开始将目光转向 mock, 因为之前有听过类似概念, 但是还是有误解, 以为把要测的东西都模拟掉了还测试什么呢? 但是真正的了解 mock 之后才完整的理解了单元测试.

单元测试应该只针对当前单元进行测试, 所有的外部依赖应该是稳定的, 在别处进行测试过的. 使用 mock 就可以对外部依赖组件实现进行模拟并且替换掉, 从而隐藏外部组件的实现, 使得单元测试将焦点只放在当前的逻辑(当前单元),

安装

mock 在 Python3 中是内置的, 直接 import …

Category: Python Tagged: Python mock unittest

comments

Read More

Python 内存泄露实战分析

Mon 30 March 2015

引子

之前一直盲目的认为 Python 不会存在内存泄露, 但是眼看着上线的项目随着运行时间的增长 而越来越大的内存占用, 我意识到我写的程序在发生内存泄露, 之前 debug 过 logging 模块导致的内存泄露.

目前看来, 还有别的地方引起的内存泄露. 经过一天的奋战, 终于找到了内存泄露的地方, 目前项目 跑了很长时间, 在业务量较小的时候内存还是能回到刚启动的时候的内存占用.

什么情况下不用这么麻烦

如果你的程序只是跑一下就退出大可不必大费周章的去查找是否有内存泄露, 因为 Python 在退出时 会释放它所分配的所有内存, 如果你的程序需要连续跑很长时间那么就要仔细的查找是否 产生了内存泄露.

场景

如何产生的内存泄露呢, 项目是一个 TCP server, 每当有连接过来时都会创建一个连接实例来进行 管理, 每次断开时连接实例还被占用并没有释放. 没有被释放的原因肯定是因为有某个地方对连接 实例的引用没有释放, 所以随着时间的推移, 连接创建分配内存, 连接断开并没有释放掉内存, 所以 就会产生内存泄露.

调试方法

由于不知道具体是哪里引起的内存泄露, 所以要耐心的一点点调试.

由于知道了断开连接时没有释放, 所以我就不停的模拟创建连接然后发送一些包后断开连接, 然后通过下面一行 shell 来观察内存占用情况 …

Category: Python Tagged: Python 内存 泄露 引用 回收 交叉

comments

Read More

logging 模块误用导致的内存泄露

Sat 31 January 2015

首先介绍下怎么发现的吧, 线上的项目日志是通过 logging 模块打到 syslog 里, 跑了一段时间后发现 syslog 的 UDP 连接超过了 8W, 没错是 8 W. 主要是 logging 模块用的不对

我们之前有这么一个需求, 就是针对每一个连接日志输出当前连接的信息, 所以每一个 连接就创建了一个日志实例, 并分配一个 Formatter, 创建日志实例为了区分其他连接 所以我就简单粗暴的用了当前对象的 id 来作为日志名称:

import logging


class Connection(object):
    def __init__(self):
        self._logger_name = "Connection.{}".format(id(self))
        self.logger = logging.getLogger(self._logger_name)

当然测试环境是开 DEBUG …

Category: Python Tagged: Python logging 内存泄露

comments

Read More

基于 Python 生成器的 Tornado 协程异步

Fri 19 December 2014

Tornado 4.0 已经发布了很长一段时间了, 新版本广泛的应用了协程(Future)特性. 我们目前已经将 Tornado 升级到最新版本, 而且也大量的使用协程特性.

很长时间没有更新博客, 今天就简单介绍下 Tornado 协程实现原理, Tornado 的协程是基于 Python 的生成器实现的, 所以首先来回顾下生成器.

生成器

Python 的生成器可以保存执行状态 并在下次调用的时候恢复, 通过在函数体内使用 yield 关键字 来创建一个生成器, 通过内置函数 next 或生成器的 next 方法来恢复生成器的状态.

def test():
    yield 1

我们调用 test 函数, 此时并不会返回结果, 而是会返回一个生成器

>>> test()
<generator object test at 0x100b3b320>

我们调用其 next …

Category: Python Tagged: Python generator coroutine 协程 生成器 Tornado

comments

Read More

Python 入门指南

Fri 23 May 2014

引子

经常能在 Python 群里看到很多新人在问一些非常基础的问题, 基本每天都在重复的问这些问题, 在这里就总结一下这些问题.

首先声明, 本文不打算教会你 Python, 本文力图陈列一些新手容易遇到的问题, 并企图教会你 如何学习 Python, 在遇到问题的时候如何提问.

关于版本

学习 Python 的第一步需要选择版本, Python 3.x 和 2.x 的断层较大, 3.x 不向后兼容 2.x. Python 现在主流应该还是 Python 2.7, Python 2.7 将会是 Python 2.x 的最后一个版本, 并且 会支持到 2020 年. 但是 Python 3 …

Category: Python Tagged: Python 入门 指南 新手 错误 书籍 工具

comments

Read More

Tornado 多进程实现分析

Fri 11 April 2014

引子

Tornado 是一个网络异步的的web开发框架, 并且可以利用多进程进行提高效率, 下面是创建一个多进程 tornado 程序的例子.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import time

import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.netutil
import tornado.process


class LongHandler(tornado.web.RequestHandler):

    def get(self):
        self.write(str(os.getpid()))
        time.sleep(10)


if __name__ …

Category: Python Tagged: Python fork_processes tornado 多进程 web 提升 效率

comments

Read More

使用 Pygments 对 Vimwiki 进行代码高亮

Fri 27 December 2013

Vimwiki 推荐的代码高亮机制是通过一个 JavaScript 插件来完成的, 那样需要加载很多 js, 所以不想使用, 比较倾向使用 Pygments 在 Vimwiki 生成 HTML 的时候对代码进行高亮.

尝试

使用 custom_wiki2html 选项

仔细的看了 Vimwiki 的帮助文档, 发现有一个 custom_wiki2html (:h vimwiki-option-custom_wiki2html) 的选项可以指定自己 的脚本来处理 wiki2html, 尝试了一下, 发现这个脚本是在生成 HTML 之前调用, 而且如果对 wiki 文件处理之后无法替换回原来的内容(后来发现这个仅仅是对使用 Markdown 语法作为 Wiki 语法设定的), 所以放弃了.

Fork 仓库, 更改代码

后来想想既然原生的没有解决办法, 所以就干脆在 github 上 fork 了仓库 …

Category: Vim Tagged: vim vimwiki pygments python highlight

comments

Read More

PyQt + QML 快速开发GUI总结

Wed 06 November 2013

最近结束一个使用PyQt+QML开发的项目, 在此对一些经验做出总结分享出来. 结合QML确实可以快速的构建出GUI程序, 但是相关资料太少, 特别是中文资料, 而且坑太多, 特别是和后端PyQt结合的时候有很多莫名奇妙的问题. 这篇文章会总结这些问题, 避免以后碰到无从下手.

PS:QML的一些基础问题不会在这里讨论, 本篇文章仅讨论一些经验性的问题, 本篇文章使用PyQt4

请留意文章中间的"注意"

如何和PyQt交互

QML和PyQt交互主要有三种方法: PyQt渲染数据, 信号传递, QML提供接口

使用PyQt显示QML

要想在Python里使用PyQt来调用QML显示, 需要用到 PyQt4.QtDeclarative.QDeclarativeView 实例的 setSource 将一个 PyQt4.QtCore.QUrl 对象传递进去, 然后调用 PyQt4.QtDeclarative.QDeclarativeView 对象的 show 方法, 下面是一个例子:

from PyQt4.QtDeclarative import QDeclarativeView
from PyQt4.QtGui …

Category: PyQt Tagged: QML PyQt Gui Python Qt

comments

Read More
Page 1 of 5

Next »

Fork me on GitHub