Showing Posts From
自动化
利用 Github Action 实现博客自动发版
- 24 Jul, 2022
背景 先说下背景需求,在摸鱼周报的整理流程中,最后一步需要生成公众号的原文链接,原文链接指向的是个人博客地址。博客需要发布才能产生外部链接,发布到不费事,但是操作步骤重复,且因为涉及博客推送相关的配置都在我的个人电脑里,所有步骤必须由我来完成。来回多次之后就考虑将这个流程做成自动化了,目标是让周报协作者都可以实现博客推送,用到的实现方式是 Github Action。 实现思路 在开始之前先了解下原先的发布流程,如下图表示:整个过程涉及 3 个仓库:Moyu Repo。管理周报文章的公共仓库,协作者可以通过它拉取和推送内容。Blog Repo。管理博客内容的私有仓库,周报只是其中一部分。Blog Website。博客的网站,它部署在一台腾讯云服务器上,它也是私有的。因为涉及两个私有仓库,普通协作者都没有他们的访问权限,所以发布流程都依赖我来完成。解决方案就是消除发布流程对权限的依赖,理想流程是这样的:这样触发入口就都集中在了共有仓库,协作者也可以参与博客发布。要实现这个流程需要将需求分为两步: 1、Moyu Repo 通过 Github Action 推送 Moyu 内容到 Blog Repo。 2、Blog Repo 通过 Github Action 发布内容到网站。 这其中最关键的是访问私有仓库时如何处理权限的问题。 Github Action 这里先简单了解下 Github Action。它是 Github 提供的为仓库定义自动化流程的方案,类似 Jenkins、GitLab CI/CD。Github Action 有一套自己的流水线配置方式,所有的流程都可以通过一个 yml 文件下发。Gtihub Action 有自己的虚拟机,支持 Windows Server/Linux/Mac,使用者无需关心环境配置问题,可以直接使用。 配置入口如下图所示:点击set up a workflow yourself,即创建了一个用于编排自动化任务的 workflow,它对应一个 yml 文件,所有的配置流程都在这里进行。 自动化任务配置前我们需要先考虑这几个问题:什么时机触发?在什么设备运行?如何执行自动化任务?我们看一个简单的例子来学习下 Github Action 如何定义这些行为: name: GitHub Actions Demo on: [push] jobs: Explore-GitHub-Actions: runs-on: ubuntu-latest steps: - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - name: Check out repository code uses: actions/checkout@v3name 表示当前流水线的名字。 什么时机触发 在什么场景触发,对应的key 是 on。上面Demo里的[push],表示仓库发生push行为时触发任务。on 还有其他几种触发途径:pull_request:提交 PR 时触发 schedule:定时触发,可以按 cron 语法配置定频 workflow_dispatch:手动触发,有用户手动激活触发行为在什么设备运行 对应的关键词是runs-on,Demo里指定值为ubuntu-latest,表示执行设备是一个 ubuntu 设备。Github Action 还支持 macOS 环境,目前有三个 macOS 版本可以支持:虚拟环境 YAML标签macOS Monterey 12 macos-12macOS Big Sur 11 macos-latest或macos-11macOS Catalina 10.15 macos-10.15需要注意:macos-latest 不是最新的 macos 版本,而是 macOS 11。iOS 开发中我们可能还会关心 Xcode 版本,Ruby 版本等。以 macOS 12 虚拟机为例,Xcode 版本:Version Build Path13.4.1 (default) 13F100 /Applications/Xcode_13.4.1.app13.4 13F17a /Applications/Xcode_13.4.app13.3.1 13E500a /Applications/Xcode_13.3.1.app13.2.1 13C100 /Applications/Xcode_13.2.1.app13.1 13A1030d /Applications/Xcode_13.1.appRuby版本:2.7.6/3.0.4/3.1.2。 其他预制环境可以参考这篇文档:macos-12-Readme。 另外 Github Action 还支持将自己的设备定义为运行机,你可以在这里了解:About self-hosted runners;支持联机调试,可以通过这个插件了解:A debugger for actions。 如何执行自动化任务 有两种执行任务的方式,一种是直接在 yml 文件里编辑脚本,关键词是run。像是 Demo 里的 echo 命令,我们可以直接输入 shell 命令进行执行。 另一种方式是插件市场,像下面这种形式: - name: Check out repository code uses: actions/checkout@v3就是使用了 actions/checkout@v3 这个插件。Github 有一个插件市场,可以搜索所需插件。像是 Code review,SSH 登录等都有封装好的插件可以直接使用。实现方案 有了这些 Github Action 知识,我们就可以开始实现我们的需求了。最终效果分成两个需求。 Moyu Repo 向 Blog Repo 推送内容 我们按照前面的三个问题来设计这个功能。 什么时机触发? 发布之前需要经过多次修改,会有多个 PR 和 Push 行为,而 Blog 发布需要等所有内容都准备完成才会执行,一般只有一次。所以考虑使用手动发布的方式,以下是配置内容: # Action name name: Weekly Article Deploy # Controls when the workflow will run on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: inputs: weekly_index: description: 'weekly index for deploy' 手动发布还补充了一个 inputs,用于接收输出参数,weekly_index为参数名,用于表示要发布第几期。执行效果如下所示:在什么设备运行? 这个需要根据执行任务来定,这里只涉及一些文本转换和仓库操作,所以任意机器都满足需求,ubuntu 资源比较多,调度会快那么一点点,所以都可的情况优先选 ubuntu。 # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on runs-on: ubuntu-latestGithub Action 里有几个名词:workflow,job,steps,这里简单捋一下。整个 Yml 文件对应为一个 workflow,它表示一次完整的自动化任务运行过程。当前仓库的整个配置都是一个 workflow。 一个 workflow 可以包含一个或多个 job,这里的 jobs 下面一级内容就是各个 job。不同 job 之间可以串行也可以并行。build为会其中一个 job,也是本 workflow 唯一的 job。 如何执行自动化任务? 这个流程需要做的事情是把 Moyu Repo 内容转成 Blog Repo 的格式,然后推送到 Blog Repo 里。前一步可以封装成一个脚本,后一步往私有仓库推送需要生成一个具有推送私有仓库权限的 token。 token 的生成需要到这里:个人头像 -> Settings -> Developer settings -> Personal access tokens,点击 Generate new token。这一步需要输入密码,然后我们可以选择所需权限去生成一个token。对于私有仓库的推送,我们选中这一个权限就可以了:为了安全考虑,这个token生成之后只会可见一次,因为后面的步骤会使用,所以我们需要做好保存。 注意这个 token 是用户级别的,它可以用于访问修改该账户名下的任意仓库。 为了让 Github Action 可以访问到这个token,需要给它做一个配置。配置路径是:在该仓库下的 Settings(注意这个是仓库下的设置而非个人下的设置) -> Secrets -> Actions 点击 New repository secret。Name 的话可以命名为 ACCESS_TOKEN,Value 为上一步生成的访问 token。这里配置的任意内容都可以通过Github Action 访问到,且是加密的,创建之后只能看到 Name 看不到 Value。下面是具体配置: env: ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}# Steps represent a sequence of tasks that will be executed as part of the job steps: - name: print inputs run: | echo "Weekly Index: ${{ github.event.inputs.weekly_index }}" - name: Git Config run: | git config --global user.email moyuweekly@github.com git config --global user.name moyuweekly # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v3 # Runs a single command using the runners shell - name: Run a one-line script run: ./Script/ci_run.sh ${{ github.event.inputs.weekly_index }} shell: bashsecrets.ACCESS_TOKEN 表示的是该仓库下面 secrets 里 name 为 ACCESS_TOKEN 的内容。${{}}为 action 语法获取变量的写法。 因为我把对私有仓库的获取和推送都放到了执行脚本里,所以这里通过环境变量的形式把这个值传给脚本。 steps 用于表述运行步骤,这里是顺序执行的。上述流程涉及到4个执行步骤: 1、打印外部传参。${{github.event.inputs.weekly_index}} 表示输入的参数。 2、配置 git user 的 email 和 name。因为执行内容涉及提交操作,这里是为了标记自动化流程的提交。 3、uses 语法对应的是插件功能,这里输入插件名即可执行对应插件配置的功能。 actions/checkout@v3 是官方插件,用于拉取本仓库代码的。 4、执行脚本。我把私有仓库的拉取,内容的格式化,私有仓库推送都放到了这个脚本里。 私有仓库的管理需要考虑 Git 链接的形式,Git 链接有两种方式,一种是给 SSH 形式,这对于本地机器比较容易,对于不固定的打包机配置起来较为麻烦。第二种是 HTTPS 形式,HTTPS 是公有链接无法处理权限问题,但 Github 支持把 token 传入链接的形式来管理权限。这里选择使用HTTPS形式,配置的 Git 地址如下: https://{github_token}@github.com/username/repo.git对仓库的操作使用这个链接就可以解决权限问题了,执行结果如下:左边是 job 描述,右边是 steps 描述,每个 steps 都可以展开查看详情。因为这里的步骤只有代码拉取推送和格式处理,所以执行很快。 Blog Repo 发布网站 这个阶段对应的是 Blog Repo 推送内容到腾讯云服务器。还是按上面的流程设计实现方式: 如何触发任务? 这个历程是上一步的承接,前一步已经定好了推送频率,这里可以接收到 push 即触发任务。 push: branches: [ "master" ]在什么机器触发? 这需要考虑到所使用的博客框架,如果是 Hexo/Jekyll 使用 Ubutu 就可以了。因为我将博客框架迁移到了 Publish,Publish 是一个由 Swift 编写的静态博客框架,所以运行机器只能是 macOS。测试时发现 Publish 引用了libswift_Concurrency.dylib 这个库,所以还需要指定版本为 macos-12。 jobs: build: # macOS Monterey 12 runs-on: macos-12如何执行自动化任务? 执行流程大概是这样的:编译 publish -> 使用 publish 把 md 源文件转成静态网站格式 -> 发布到腾讯云。 正常能获取到 publish 执行文件是无需编译的,但因为我为了让它兼容 hexo 的格式,做了一些魔改,所以我使用的 publish 是一个动态的版本,它需要随修改随时编译。 发布至腾讯云,也需要考虑权限问题,个人服务器没有 Github 那种 token 授权形式,只能借助于 SSH 了。 SSH 在开始之前再简单回顾下 SSH 登录的一点原理。SSH 支持密码和密钥两种登录方式,我们一般为省去繁琐的密码输入,会采用密钥登录的形式。密钥登录使用的是对称加密,一般的做法是登录端生成一对公私钥,把公钥放到服务端,私钥保存在本地。对称加密解决的是信息传输不会被篡改的问题,它无法防止中间人攻击,因为它没有HTTPS 那样的 CA 来验证可信性。SSH 选择的是通过手动验证的方式,关于手动验证不知你是否还还记得这段内容: The authenticity of host 'host (172.168.*.*)' can't be established. RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d. Are you sure you want to continue connecting (yes/no)?它就是用来手动验证的,我们需要通过 host ip 验证该链接是来自于可信的服务器还是中间人。确定过一次之后,这个信息会被写到本地的 known_hosts 文件中,之后对于同一 ip 的服务器登录就不会再弹这个验证了。对于自动化流程来说,我们应该将私有服务器的验证信息直接填入known_hosts文件,跳过阻塞式的二次确认。 流程配置 有了以上知识,我们对于密钥的配置流程应该是如下图所示:蓝色的钥匙为 pub_key,红色钥匙为 private_key,带钥匙的文件是 know_hosts。Github Action Runner 的配置流程如果都手动实现比较麻烦,我们可以使用install-ssh-key这个插件快速实现这个功能: - name: Install SSH Key uses: shimataro/ssh-key-action@v2.3.1 with: key: ${{ secrets.SSH_PRIVATE_KEY }} # it's value doesn't matter known_hosts: 'knowen_host_value for ssh-rsa'它要求两个参数,key 为 ssh 的 private key。SSH 私钥可以使用上个章节介绍的 Secrets Actions 进行存储,将其命名为 SSH_PRIVATE_KEY。 known_hosts 是对 Server 端的信任记录,用于免去手动确认的流程。这个内容的获取,有两种方式,你可以查看本地的 known_hosts 文件找到对应的目标服务器的记录,也可以利用 ssh-keyscan 去手动查找。 $ ssh-keyscan -H <host-ip>这个结果会按多种加密算法产生多个结果,我们需要选择类型为 ssh-rsa 的内容,因为Github Action 仅支持这一种加密结果。我们把这一条内容添加 known_hosts 参数即可。当然你也可以选择使用密钥的形式存放。 最重要的步骤已经完成了,下面就可以编译 publish 并发布内容了。 - name: Create Publish run: | git clone https://github.com/zhangferry/Publish.git ./publish cd publish make cd .. - name: Blog Deploy run: | echo "begin deploy" ./publish/.build/release/publish-cli deploy最后执行结果:遗留问题 往腾讯云服务器推送内容时遇到一个腾讯云给我发了告警邮件,说是检测到异地登录,来源IP是美国,危险等级高危。这个登录应该指的是 Github Action Runner 设备的登录,这个目前还没有找到有效的解决办法,因为IP是动态的,无法通过手动加白的形式避免。 另外也可关闭异地登录的报警,在告警设置里关闭异地登录报警选项即可。但这种方式也存在一定的安全风险,因为它不是解决问题而是无视问题。 我暂时没有找到更好的解决方案,如果有人知道更好的处理方式,欢迎告知。
利用 Automator 快速符号化 Crash 文件
背景 起因是最近有接到一个临时协助任务,其中有几个重要的流程:QA 方导出 .crash 文件(必要的) 我方要根据测试提供的 crash 文件的build number,去下载对应的 xx.app.dSYM 把下载的dSYM给合作方 合作方解析crash文件从上的步骤可以看出第一步不可省略。第二、三步完全可以干掉,流程越多越浪费时间。 第四步也可以我们自己做,就可以优化成 QA 直接解析好 crash 文件然后给合作方。 那么就提效了提效 50% 是不是,两个人的事情一个人搞定 (那么就可以卷点别的)初版方案 小插曲一开始第一周我写了个Shell,调试通过之后就没继续,就干其他大活了(这里有个有悲剧) ...... 第二周的时候,不知怎的崩溃出奇的多(应该是合作方更新SDK之后导致的) 当时我正Coding热火朝天,QA和合作方夺命的Call 我就去找那个当时写好的shell脚本,一通翻箱倒柜之后,我悟了,悲剧来了,找不到了 呵呵,被自己强迫症日常清理垃行为给清理了(自己有个日常清理的垃圾的行为,无奈Mac配置就这样)打工人不得不含着泪重新写了一份 (源码在下面),快速应付下那边的夺命Call 然后我就在想这个事,为啥要我来做,也没啥技术含量,为啥不可自动化? Bingo~ 说来就来Shell 源码 crash_txt=$1 crash_log=${crash_txt%%.*}.log # find /Applications/Xcode.app -name symbolicatecrash -type f # cp /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash symbolicatecrash export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer ./symbolicatecrash $1 $2 > $crash_log open $crash_log -a sublime具体如何符号化解析这里就不再唠叨了,网上一大堆:附一个参考链接 使用 Automator 的自动化方案 要使用 Automator 还需要编写 AppleScript 代码。 工具 & 语言工具:Automator Service, 脚本编辑器 语言:AppleScript,Shell脚本编辑器,AppleScript调试用。 好玩的 AppleScript 下面是一些好玩的 AppleScript 代码,唤起你的好奇心: display dialog "你说假如地球没有了空气,我们会怎样... 那么没有工程目录,后面该怎么办?" default answer "会死" buttons {"我知道了"} ¬ default button "我知道了" with title "Handsome ERROR" set theInput to text returned of the result --display dialog text returned of the result if theInput is equal to "会死" then display dialog "没救了" with title "ERROR" buttons {"我知道了"} ¬ default button "我知道了" end if --忽略下面部分say "Hello world"display dialog "Hello World" with title "Alert"display notification "Hello World" with title "Notification"或者直接在终端里面跑 osascript -e "display notification \"Hello World\" with title \"Notification\""-- single comment, # single comment 是单行注释 (* this mutli comment *) 是多行注释 Markdown问题AppleScript脚本里意外出现<p data-line这种代码忽略AppleScript 需要注意的问题 主要还是路径问题 ApeleScript 获取的路径如下: Macintosh HD:Users:xxxxxxx:Documents:xxxxx.app_副本_2.dSYM:这种冒号的路径在shell命令行根本没法用,所以下面代码成了常客: 冒号字符串 打包成数组 set my_array to split(input as string, ":") on split(the_string, the_delimiter) set old_delimiters to AppleScript's text item delimiters set AppleScript's text item delimiters to the_delimiter set the_array to every text item of the_string set AppleScript's text item delimiters to old_delimiters return the_array end split字符串 set target_path to join(my_array, "/"),这里要注意拼接文件与文件夹用的index下标不同: on join(the_array, the_delimiter) set split_str to the_delimiter set target to " " set list_length to the length of the_array set list_length to list_length - 1 set short_list to items 2 through list_length of the_array repeat with dir in short_list set target to target & split_str & dir end repeat return target end join实现过程 思路分析 1、定位dSYM路径 2、定位xx.crash件路径 3、唤起终端,切入指定路径 4、symbolicatecrash解析并重定向输入结果 5、自动打开展示结果 其实这前两步有个大坑:重复下载 dSYM 文件以及导出的 xxx.crash 文件路径会存在空格。在AppleScript调用Shell的时候路径有空格,会报错找不到对应的文件。 解决办法利用 AppleScript 给文件重命名 借助Automator 现有的快捷操作修改期间有周报群群主指点使用 AppleScript 借助 quoted 这个 API 来转义引用空格。 结果是终端识别了,但是symbolicatecrash还是不识别,虽然结果不尽人意,但是学到了新技能。 如果你看过AppleScript API,除了想哭就没别的,上面说的很清楚干啥用,但是不知道语法该咋写。因为没写过这种自然语法,每次都是不停的尝试、失败,尝试、失败,尝试、失败。 AppleScript小众到谷歌都没有,大部分都是查阅Stack Overflow。 我这边选择是第二种 xxx.crash 文件名有空格的解决办法是直接重命名,查找之后直接把空格替换成下划线。dSYM 父目录路径空格,这边多次导出之后会导致父目录存在空格,这个相对上面就比较复杂。 这里有几点思考:在事物本身很难解决问题时,我们就需要放开视野,跳出事物本身,提升更高的角度去思考 当你这么想了,你思考问题的维度和角度就变了 在我们这个问题上,既然它的路径上存在空格,我给它换个不存在的路径不就好了 是不是一个很简单的解决办法,所以有时候不要太局限一点一面一点瞎扯淡 其实日常编码或者修复 BUG 的过程中也会遇到类似情况,我们在一个问题上纠结好久好久到快死了吧!但是问题还没能解决,这个时候就可以尝试:冷静下来 刻意放慢节奏 全身心放松下来 想点别的换换脑子或者睡一觉(我通常就是睡觉) 冥想(这个相对高级 需要练习)不去想这个问题一段时间之后,慢慢就会发现脑子开始活络起来,之前的问题解决办法好像一下子思路如泉涌,睡一觉精神也恢复了,思路也有了,简直两全其美是不是,比死磕一天啥都没有强千百倍吧,最后还得被喷延误工期,拉胯身体,最后无奈身不由己加入996.icu 这个 Big Party。 工程创建 1、选中dSYM文件 -> 右键 -> 服务 -> 创建服务 2、弹出一个快捷操作的模板空工程,可以配置参数入口(因为第一步选中了,参数就不需要配置了) 3、然后就可以拖拽你要的操作(类似于storyboard,xib操作) 4、保存 -> 命名,就会自动存储到本机的~/Libray/Services目录 所有的快捷操作,工作流都会在这个目录,就是说你想用别人写好的最后安装的也是这个目录 示例图:完整的操作步骤脚本交互Shell 调用 AppleScript可以用osascript -e AppleScript调用Shell可以用do shell script & do script do script需要配合终端示例: tell application "Terminal" activate --set new_tab to do script "echo fire" delay 1 do script "pwd" in front window do script "ls" in front window end tell演示模糊了点,为了加载快,压缩的有点狠,但是也能看大概流程就OK了 有两种使用方式启动 dSYM 自动化服务:首先选中 dSYM 文件,然后右键 -> 快捷操作 -> dSYM 首先选中 dSYM文件,快捷键即可(这里需要到 Finder -> Service 偏好设置里面配置好按键)执行流程如下: 1、启动之后就自动去/Users/$(whoami)/Downloads/目录文件下搜索.carsh文件 这里写死Downloads目录的原因是想提高搜索速度,所有导出的时候选择的就是Downloads目录。如果你想要全局搜索也不是不是可以, 但是你得等等Spotlight 2、搜索完毕之后会列出该目录下所有的 .crash 文件。 3、选择对应的文件(build number 一致),就会打开一个终端进入解析流程。 4、解析完毕之后会通过 sublime 打开。没有sublime会怎样? 就去掉 -a sublime ApplesScript 代码负责的部分:冒号:转斜杠/ 调用了剪切板做缓存 display dialog 显示.crash文件的搜索结果 唤起终端,执行解析总结 这里本来想在Automator里面加一个调用Shell脚本的的服务,这样就可以静默解析不用唤起终端,调试过程中解析一直失败,因为运行解析的 symbolicatecrash 需要的环境变量报错,也在对应的目录进行了export,但是最终还是不行,最后还是选择唤起终端来执行操作,或许看起来更酷一点吧 哈哈哈。 其他文件读取/拷贝/搜索/重命名都是Automator提供现成服务。Automator真的很强大,但是你要发现它的美,学会使用它。 最后就是要告诫自己:该做的事还得及时做出来, 不然就是午饭没吃 午休没睡。