背景
最近在項目開發中,需要針對 Jenkins 項目進行配置,Jenkins 的 job 配置采用的是 xml,在維護配置模板的過程中就遇到了問題,因為逐步發現配置靈活性超出了字符串的范疇,本文旨在簡單介紹 Python 下模板引擎模塊 Jinja2 的使用。
什么是 Jinja2?
Jinja2 是一個 Python 的功能齊全的模板引擎。它有完整的 unicode 支持,一個可選的集成沙箱執行環境,被廣泛使用,以 BSD 許可證授權。
以上是官方說明,簡單來說,它提供了替換功能(變量替換)和一些強大的特性(控制流、繼承等),可以快速生成數據文件,使得業務與數據分離開來,滿足一些靈活多變的配置需求。
為什么要使用 Jinja2?
拿 Jenkins 配置來說,如果要實現不同平台、不同項目的配置,有以下幾種方案:
- 直接進行字符串替換,使用 Python 字符串語法即可
- 使用 XML 解析和生成模塊,比較簡便的是 ElementTree
- 使用模板引擎,例如 Jinja2
起初,我選用的是方案一,特點是簡單粗暴,不需要額外的配置,例如,兩個項目的 git 倉庫配置可以采用如下代碼實現:
# 略去不相關代碼
template = b"""<configVersion>2</configVersion>
<userRemoteConfigs>
<hudson.plugins.git.UserRemoteConfig>
<url>%(git_url)s</url>
</hudson.plugins.git.UserRemoteConfig>
</userRemoteConfigs>
<branches>
<hudson.plugins.git.BranchSpec>
<name>%(git_branch)s</name>
</hudson.plugins.git.BranchSpec>
</branches>"""
xml = template % {'git_url':self.__git_url, 'git_branch':self.__git_branch}
這樣,只需要外部傳入地址及分支,即可獲得不同的項目配置 xml,但缺點也很明顯如果兩個配置節點數目不相同或者里面的字段差別很大,那么就需要使用 template2
、template3
、template4
等等,因此此方案僅適合配置需求較為簡單,字段不是很復雜的場景。
緊接着,開始調研快速生成不同 xml 數據的方案,找到了 https://stackoverflow.com/questions/1055108/fast-and-easy-way-to-template-xml-files-in-python, 這里提到了兩種方案,也就是上述的2、3。
方案2的特點也很明顯,可以完整控制 xml 中每個字段和屬性,靈活性確實夠了,能夠滿足我們的需求,但是有點大炮打蚊子的感覺,而且頻繁的讀寫 xml 也比較消耗性能,業務邏輯也會隨着配置的靈活性和多樣性急遽膨脹,比如 Jenkins 項目配置,倉庫地址、分支信息、ssh 信息、tag 字段、打包腳本等等,如果使用 xml 解析的話,針對這些節點都必須創建對應的業務邏輯,不是很推薦。
那么我們的選擇也比較簡單了,Jinja 可以快速的替換生成我們業務需要的各種各樣的數據,而且接入成本低,業務邏輯不會變化太大,下面簡單介紹下本次項目中的使用。
Jinja2的簡單使用教程
安裝
這個比較簡單,看下文檔即可,執行命令
pip install jinja2
基本概念
Jinja2中有一個名為Environment
的對象,用於存儲配置、全局變量,並且用於從文件系統中加載模板。即使你通過Template
用字符串創建一個模板,Jinja2 也會自動為你創建一個環境。推薦使用這種方式進行模板配置而不是使用字符串,大多數程序使用一個環境即可。
用法
- 創建一個環境:
from jinja2 import Environment, PackageLoader, select_autoescape
# 初始化 jinja 環境
self.__env = Environment(
loader=PackageLoader('模塊名稱', '模板文件夾'),
autoescape=select_autoescape(['html', 'xml'])
)
加載一個模板:
self.__job_template = self.__env.get_template('job_template.xml')
接下來就是傳遞一些業務參數了,
self.__config = self.__job_template.render( git_url=self.__git_remote_url,
git_branch=self.__git_branch,
git_tag=self.__git_tag
)
以上都還是比較簡單的 API 調用,核心使用部分在與如何在模板中通過 Jinja 支持的語法靈活的進行數據配置。
基本變量替換
最基礎的功能就是進行變量替換了,語法為{{變量名}}
, 例如將 url 傳遞到 xml 中,模板中寫法為(省略了不相關內容):
<userRemoteConfigs>
<hudson.plugins.git.UserRemoteConfig>
<url>{{git_url}}</url>
</hudson.plugins.git.UserRemoteConfig>
</userRemoteConfigs>
然后在代碼中向上面一樣調用 template.render(git_url='xxx')
即可
控制流
Jinja 支持基本的if
、for
、宏等語法,這也是它最強大的地方,目前項目中我只用到了判斷就夠了。用法如下:
{% if push_tag %}
<tagsToPush>
<hudson.plugins.git.GitPublisher_-TagToPush>
<targetRepoName>origin</targetRepoName>
<tagName>{{git_tag}}</tagName>
<tagMessage></tagMessage>
<createTag>true</createTag>
<updateTag>true</updateTag>
</hudson.plugins.git.GitPublisher_-TagToPush>
</tagsToPush>
{% endif %}
上面這段 xml 的邏輯就是如果傳入了 push_tag
變量且為True
則在 Jenkins 配置中增加推送 git tag 節點,否則不做任何處理。
除此之外,Jinja2 還支持循環,
<dl>
{% for key, value in my_dict.iteritems() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
</dl>
詳細說明可以參考 http://docs.jinkan.org/docs/jinja2/templates.html#for
繼承
這部分由於目前項目配置還比較簡單,暫時沒用到,可以參考 http://docs.jinkan.org/docs/jinja2/templates.html#id23
結語
對於簡單的項目來說,上面提到的一些基本替換加控制流基本都能滿足使用了, 稍微復雜一點的程序可以考慮使用繼承和擴展。
總的來說,Jinja2 既強大又簡便,完美符合項目中一些HTML、XML 的業務配置需求,目前項目中本身有的三四個 xml 文件用了 Jinja2 之后縮減成一個,同時改動工作量大約只花了幾個小時,真的很方便。
參考資料: