最近在幫忙處理 Team 的雜事,寫好 Script 請 project 的 Infra Team 幫忙在 Jenkins 上 trigger 任務

我選擇用 Python 去寫,最後會需要跑一些跟 Git & Gerrit 有關的命令,在寫的時候查一下資料改一改也還好,但碰到一個卡有點久的問題是,我要如何取得 Push 到 Gerrit 上的 change link,單純取 subprocess 的 stdout 無法達成這件事


Gerrit

Gerrit 就是一個 Git server,跟 GitHub 是差不多的,只是工具跟介面的差異,比較顯為人知的是他是 Google 放 Android repo 主要採用的 server,適合管理比較大型的專案,詳情可以參考官網

Subprocess

subprocess 如其名會開啟一個新的 process 來執行其他命令,使用 run() 就能夠執行(included in python 3.5)

想要檢查命令是不是成功,也有 check_call 能用(python 2.7),等於是 subprocess.run(..., check=True)

需要注意的是 python2 & python3 能用的 API 稍微有些不一樣,最好是能 cover python2 也能使用的,如果確定只會執行在 python3 的環境,就可以盡量用高階一點的 API

Communicate

在使用 subprocess 時,如果需要跟 Process 有互動,也就是需要知道他的 output 或是需要輸入 input,可以用 communicate()Popen() 這兩個較為低階的操作才有辦法取得

communicate() returns a tuple (stdout_data, stderr_data). The data will be strings if streams were opened in text mode; otherwise, bytes.

1
2
3
4
5
6
7
8
def execute_command(command):
process = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(stdout, stderr) = process.communicate()
process_return_code = process.returncode
if (stderr or process_return_code != 0):
print("{} FAILED, RETURN CODE: {}, ERROR: {}".format(command, process_return_code, stderr))
sys.exit(1)
return stdout

執行成功的話 process 的 returncode 通常會是 0,可以藉由這個來判斷,也可以用 communicate() 回傳的 stderr 來看是什麼錯誤

如果只是很單純的想檢查這個指令有沒有輸出可以用 subprocess.check_output,但通常遇到的實際情況都比較複雜一點

這裡我的 stderr 指給的是 STDOUT 而不是 PIPE 的原因是如果是處理類似 npm installing 的指令,會有一些 packages 安裝失敗,就會出現在 stderr stream 中,但在這個指令中的錯誤只是一個安裝的過程並不代表真的失敗

Universal Newline

Push change 到 Gerrit,執行過程中的輸出如下

1
2
3
4
5
6
7
8
9
10
11
12
...
usage: git diff [--no-index] <path> <path>
remote:
remote: Processing changes: refs: 1, new: 1
remote: Processing changes: refs: 1, new: 1
remote: Processing changes: refs: 1, new: 1, done
remote: warning: pushing without Change-Id is deprecated
remote:
remote: SUCCESS
remote:
remote: New Changes:
remote: https://<git-master-url>/<path>/<patch-number> <Commit title>

如果把 stdout 印出來,不會看到以上的結果,後來我先試了 process.stdout.readline() 跑迴圈一行一行把 output 讀出來的方式才有辦法得到,在這過程中還需要 decode(),也就是我們得到的 stdout 是 byte 形式,也許是因為這樣才沒辦法抓到想要的輸出,所以另外找到了更簡潔的方法

在 Popen 加入 universal_newlines=True,會把 stdout, stderr 都轉成 string 的形式,

1
process = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)

Search URL

接著可以使用 regular expression 來檢查得到的每一行中有沒有我們要的 change url

如果 re.search 有找到的話,回傳 reg.group(),就是回傳對應的 reg line

1
2
3
4
5
6
import re
def get_change_url(output_line):
reg = re.search(r'(?P<url>https?://<git-master-url>/<path>/[^\s]+)', output_line)
if (reg):
return reg.group()
return ""

我是回傳空字串來代表沒有找到對應的 url,條件怎麼寫看個人