# 面向白嫖(Baipiao-oriented)的DevOps：将定时更新任务扔给Github Actions去做

> 作者：zning（https://ning.ae） · 发布：2021/08/13 18:29 下午 (UTC+8) · 原文：https://zning.me/16288505655950.html

最近有个需求，由于最近在某平台更新内容，其提供的订阅机制为RSS订阅分发，不过其中带有的一些额外的信息有点尴尬，并不能直接发送到其他同类型内容平台分发，因此想到了通过程序先将不适宜信息处理后，定期更新RSS文件到自己的Github Pages上，然后其他同类型内容平台获取的RSS为自己的Github Pages上托管的RSS文件。

于是，需求比较明确了：

* 可以托管并执行程序，不仅可以获取RSS文件内容，又可以通过程序修改；
* 可以定时更新任务，并且根据更新任务内容push到仓库指定分支。

我第一时间想起了Github Actions这个工具，众所周知，这个工具自2019年内测到向公众开放后，众多从业者将自己的博客的编译工作从本地线下扔到了Github Actions上；一些从业者所写的前端小玩意儿，都可以很轻松的通过Github Actions将工程迅速部署，提高了效率以及节约了成本。甚至一些有后端的工程，只需要寻（bai）找（piao）一个后端存储数据的地儿，就可以低成本甚至零成本搭建个人主页，这简直是一个十分有效提高生产率的东西啊！

不过说起来，其实Github Actions与Jenkins的功能是一致的，都是一个CI/CD的DevOps生产力工具，只不过Github Actions以其简单易用一经推出就受到了十分的欢迎。

虽然一直有知道这个功能，但是苦于没什么场景可以使用，今天这个需求正好让我能够使用Github Actions，零成本的解决一下需求。

首先，准备一下更新RSS的主程序`convertRSS.py`和`requirements.txt`：

```python
import requests
import os, sys
import argparse

# the main get content codes
def get_url(url):
    # some server requests need headers, otherwise return empty
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36',
    }
    page = requests.get(url, headers = headers).content
    # convert to str within utf-8 encoding
    page = page.decode('utf-8')
    return page

# the main convert codes
def convert_content(content):
    content = content.replace("ZNing源创库","张宁网·源创库")
    return content

if __name__ == '__main__':
    # need system args input
    arg_prsr = argparse.ArgumentParser()
    arg_prsr.add_argument('--url', required=True, type=str, default="", help = 'Required. Which one RSS Xml do you want to convert.')
    args = arg_prsr.parse_args(sys.argv[1:])
    url = args.url

    # place a flag to judge should write file to disk or not
    flag = False

    url_content = convert_content(get_url(url))
    url_list = url.split("/")

    if os.path.exists(url_list[-1]):
        with open(url_list[-1], 'r', encoding='utf-8') as file:
            if(file.read()!=url_content):
                flag = True
    else:
        # if the file dose not exist, the flag should placed True to convert first time
        flag = True

    if flag:
        # writing to disk codes
        with open(url_list[-1], 'w', encoding='utf-8') as file:
            file.write(url_content)
        print("The RSS Xml had update, convert done.")
        sys.exit(0)
    else:
        print("The RSS Xml have no change, convert abort.")
        sys.exit(1)
```

这里解释下我的程序的逻辑：通过系统执行传参`--url`将需要处理的RSS XML URL传值进入，后通过函数`get_url()`完成源文件的获取并转换成字符串，紧接着通过`convert_content()`函数处理替换源文件字符串。完成后紧接着判断本地是否有该文件，如果没有则直接创建，如果有则判断本地文件是否与处理替换后文件内容是否相同，如果不相同则覆盖修改本地文件完成更新，并以`0`返回值正常退出，如果相同则无需更新，以`1`返回值返回退出程序。

至于为什么设计这两个返回值，文末会讲到。

下面是该程序的`requirements.txt`，显然得，不再赘述：

```text
requests==2.25.1
```

之后Github Actions创建一个仓库，把这俩文件放进去。

另外是需要去Settings-Developer settings-Personal access tokens（链接：<https://github.com/settings/tokens>）里面生成一个密钥，将这个密钥添加到仓库设置的Actions secrets（链接：<https://github.com/${username}/${reponame}/settings/secrets/actions>），在Actions secrets的名字即是配置文件引入的变量名，这里我的仓库叫`CONVERT_TOKEN`。

![FireShot Capture 016 - New personal access token - github.com](media/16288505655950/FireShot%20Capture%20016%20-%20New%20personal%20access%20token%20-%20github.com.png)

![FireShot Capture 017 - Actions secrets - github.com](media/16288505655950/FireShot%20Capture%20017%20-%20Actions%20secrets%20-%20github.com.png)

文件和密钥均备妥后，点击菜单标签Actions，后点击`set up a workflow yourself`，进入创建自己的工作流。

工作流创建文件使用的是YAML语法，具体使用文档可以查阅：https://docs.github.com/cn/actions 这里不再赘述更多用法，直接贴出本示例项目创建的工作流文件`convert-rss.yml`：

```yaml
name: 'Convert RSS Actions'

on:
  schedule:
    - cron: '0,15,30,45 * * * *'

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - name: Checkout from repo
      uses: actions/checkout@main
      with:
        ref: main

    - name: Install Python latest
      uses: actions/setup-python@main
      with:
        python-version: '3.x'
        architecture: 'x64'

    - name: Install dependencies
      run: |
        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
    - name: Doing fetch
      run: python convertRSS.py --url https://zning.me/atom.xml

    - name: Commit files
      run: |
        git config --local user.email "zhn038@gmail.com"
        git config --local user.name "ZNing"
        git add .
        git commit -m "提交RSS XML更新 $(date "+%Y-%m-%d %H:%M:%S")"
    - name: Push changes
      uses: ad-m/github-push-action@master
      with:
        github_token: ${{ secrets.CONVERT_TOKEN }}
        branch: main
```

Github会在该分支仓库里创建`.github/workflow`文件夹，并在里面创建工作流文件`convert-rss.yml`，文件名没有任何要求，Github Actions会对该文件夹下所有`.yml`进行解析与执行。

这里稍微解释一下我自己这个工作流文件所干的事情：

* 工作流名称为`Convert RSS Actions`；
* 工作流为定时计划工作流，工作时间是每隔15分钟一次；
* 工作流build有6个任务，顺序执行，每个步骤的具体是：
    1. 检出main仓库；
    2. 安装最新版Python 3；
    3. 根据仓库是否有`requirements.txt`安装Python依赖；
    4. 执行Python程序，对RSS XML进行处理；
    5. 对更新的RSS XML进行提交；
    6. 对更新的RSS XML进行推送至指定分支。

![FireShot Capture 009 - Actions · zning1994_github-actions-examples - github.com](media/16288505655950/FireShot%20Capture%20009%20-%20Actions%20%C2%B7%20zning1994_github-actions-examples%20-%20github.com.png)

![FireShot Capture 012 - New File - github.com](media/16288505655950/FireShot%20Capture%20012%20-%20New%20File%20-%20github.com.png)

创建好后，就等着Github Actions自己按计划调起Actions工作即可，不过根据实际测试，由于这玩意儿全球从业者估计使用的都挺多的，所以实际执行时间并不会完全按照设定的时间走，一般会晚个8-12分钟。

> 仓库所有内容其实都可以本地创建，其中Actions的定义文件在本地创建`.github/workflow`文件夹后放置提交推送给Github也是可以的，只不过我这边为了演示方便，就不再在本地做操作了，直接在Github网页上直接将所有步骤都解决了。

![FireShot Capture 019 - Actions · zning1994_github-actions-examples - github.com](media/16288505655950/FireShot%20Capture%20019%20-%20Actions%20%C2%B7%20zning1994_github-actions-examples%20-%20github.com.png)

![FireShot Capture 020 - Update convert-rss.yml · zning1994_github-actions-examples@b2e36a9_ - github.com](media/16288505655950/FireShot%20Capture%20020%20-%20Update%20convert-rss.yml%20%C2%B7%20zning1994_github-actions-examples@b2e36a9_%20-%20github.com.png)

这里解释一下刚才Python返回值的问题，因为我需要区分两种状态情况，而Github Actions是可以根据返回值判断该节点运行正常与否（详见：<https://docs.github.com/cn/actions/creating-actions/setting-exit-codes-for-actions>）只要是0就认为是正常，非0则是异常，且异常不会继续执行接下来的节点。

而我的需求其实就正好是，如果有更新则提交并推送仓库，如果没有则终止即可。因此上面的Python程序才会设置如此的返回值。

> 不过可能是我Python程序有点小问题，发现如果内容相同的时候，后台程序也会提交更新，但是这个情况出现的很随机，有的时候又比对相同1值退出，正常返回错误。目前不太清楚这是为什么，欢迎知道的大佬在评论区留言。

![FireShot Capture 022 - 提交RSS XML更新 2021-08-13 07_50_01 · zning1994_github-actions-examples@e_ - github.com](media/16288505655950/FireShot%20Capture%20022%20-%20%E6%8F%90%E4%BA%A4RSS%20XML%E6%9B%B4%E6%96%B0%202021-08-13%2007_50_01%20%C2%B7%20zning1994_github-actions-examples@e_%20-%20github.com.png)

![FireShot Capture 022 - 提交RSS XML更新 2021-08-13 07_50_01 · zning1994_github-actions-examples@e_ - github.com -2-](media/16288505655950/FireShot%20Capture%20022%20-%20%E6%8F%90%E4%BA%A4RSS%20XML%E6%9B%B4%E6%96%B0%202021-08-13%2007_50_01%20%C2%B7%20zning1994_github-actions-examples@e_%20-%20github.com%20-2-.png)

最后，本文示例程序正常开源，协议MIT，欢迎自取使用：

Github：https://github.com/zning1994/github-actions-examples

Gitee镜像：https://gitee.com/zning/github-actions-examples
