dev.to 版 https://dev.to/kevinluo201/github-actions-ruby-on-rails-42ig
最近研究了一下用 Github Actions 做 Rails 的 CI,分享一下经验 : )
Github Actions 是 Github 的自动化工具。
Github Actions 只要在你的专案根目录新增 .github/workflows
,再新增任意名称的 Yaml 档。
Git push 到 Github 后,即会根据你写的 workflow 的内容自动执行了。
可以直接看下面分享的 YAML 档,不过建议还是先看一下 Github Action 的文档 Introduction to GitHub Actions - GitHub Docs ,会比较有概念喔 (常常更新的也满快的)
先分享 .github/workflows/ci.yml
,后面会再说明每一列是代表什麽意思
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.2
bundler-cache: true
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: '12.16.x'
- name: Restore cached ./node_modules
uses: actions/cache@v2
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-lock-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-lock-
- name: Yarn Install
run: yarn install
test:
needs: build
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8
ports: ['3306:3306']
env:
MYSQL_ROOT_PASSWORD: 'my-root-pw'
MYSQL_DATABASE: test_db
MYSQL_USER: username_you_like
MYSQL_PASSWORD: password_you_like
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis
ports: ['6379:6379']
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v1
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.2
bundler-cache: true
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: '12.16.x'
- name: Restore cached ./node_modules
uses: actions/cache@v2
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-lock-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-lock-
- name: Yarn Install
run: yarn install
- name: Prepare Database
env:
RAILS_ENV: test
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
run: bundle exec rails db:prepare
- name: Run tests
env:
REDIS_URL: redis://localhost:6379/1
RAILS_ENV: test
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
run: |
bundle exec rspec --format RspecJunitFormatter --out ./reports/rspec.xml
- name: Publish Test Report
uses: mikepenz/action-junit-report@v2
with:
report_paths: './reports/rspec.xml'
name: CI
此 workflow 的名称,可任意取,到时会出现在 Github Actions 中
on: [push, pull_request]
on 是控制何时要执行这个 workflow
jobs:
build:
runs-on: ubuntu-latest
一个 workflow 可以有很多 jobs 组成,它们通常是同时 (平行) 执行的,
不过可以设定先后顺序,下面一点会提到。
job 的名称可任意取名,我这裡叫它 build
runs-on
是 job 要在什麽 OS 上执行,github 上的 Github Actions 好像得用 ubuntu-latest,我也是照教学沿用了
steps:
step
是 Github Actions 的最小单位
steps 裡每个 step 可以执行一或多个指令或一个 Action
要执行指令,则使用 run
run: echo "hello world!"
要执行 Action,用 uses
uses: actions的名字
每个 step 的 name 并不是必填,但填了就像註解一样,方便理解
- uses: actions/checkout@v1
Action 其实就预先写好的脚本
GitHub Marketplace 上有一堆,可以想像是想把自动化的脚本当成在 App Store 上卖
GitHub Marketplace · Actions to improve your workflow · GitHub
actions/checkout@v1
就是一个常用的 Action,可以把你的 git repository 下载下来
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.2
bundler-cache: true
ruby/setup-ruby@v1 是用来安装 Ruby 的 Action 下载的 ruby-version: 2.7.2 是要装 2.7.2 的 Ruby bundler-cache: true 是要快取 bundler 下载的 gem,下面再说明快取 基本上这些 Action 都可以在 Github Marketplace 上查到用法 Setup Ruby, JRuby and TruffleRuby · Actions · GitHub Marketplace · GitHub
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: '12.16.x'
同上,安装 Node
- name: Restore cached ./node_modules
uses: actions/cache@v2
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-lock-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-lock-
- name: Yarn Install
run: yarn install
actions/cache@v2
是专门来做快取的 Action
这裡写 path: ./node_modules 就是要把 ./node_modules 下的档案全 cache 起来
快取的结果会用 key 的设定去命名。
这个 action 会先依照 restore-keys 去找可以回復的快取
最后的 step 纯粹就是跑 yarn install
如果光看这个 顺序应该有人会疑惑: 快取的 action 是放在 yarn install 前,或更上方 ruby 的部分有 bundle install 前, 在安装依赖前,先「读取」快取好理解, 但重要的是「存」快取的时机怎麽没看到? 执行一次就知道,这类快取的 Action 都有挂个动作在 PostJob 的 callback, 当这个 job 结束后会把该快取的 path 存起来
另外,每个仓库有 5GB 的快取空间,正常来说应该是用不完啦。 所以 gems, npm 这种安装的程式库都把它们快取起来吧
test:
needs: build
runs-on: ubuntu-latest
这是第二个 job,我命名为 test,因为准备要跑 rspec 了
上面说 job 其实是平行执行的,needs
可以控制先后顺序,needs: build 的意思就是 build 跑完才会跑 test
虽然以目前这个 workflow 来看,其实可以把 steps 全写在同一个 job,没什麽差别。
但写成这样,可以方便未来加入不同类的 test,比如 js 的 test,capybara 的 test,可以在 build 后,所有的 test 同时执行。
services:
mysql:
image: mysql:8
ports: ['3306:3306']
env:
MYSQL_ROOT_PASSWORD: 'my-root-pw'
MYSQL_DATABASE: test_db
MYSQL_USER: test_user
MYSQL_PASSWORD: test_pw
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis
ports: ['6379:6379']
options: --entrypoint redis-server
services
可以用 docker image 架起需要用的服务,设定好 port mapping
我这裡架了 mysql 和 redis
那些 env 是去 DockerHub 上去找官方的 image 的说明才知道有什麽可以加的
顺带一提,CI 时 config/database.yml
可以两种处理方式
run: cp config/database.yml.ci config/database.yml
steps:
- uses: actions/checkout@v1
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.2
bundler-cache: true
- name: Install Node
uses: actions/setup-node@v2
with:
node-version: '12.16.x'
- name: Restore cached ./node_modules
uses: actions/cache@v2
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-lock-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-lock-
- name: Yarn Install
run: yarn install
这裡跟 build 是做完全一模一样的事,执行时会直接取 build 快取的结果,所以很快就会跑完。 看起来很冗,不过如果未来可以用 YAML 的 Anchor 功能,这一块可以直接写成一个可複用的 block,目前就直接重複吧!
- name: Prepare Database
env:
RAILS_ENV: test
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
run: bundle exec rails db:prepare
如果要开始跑 rails 的指令时,要带一些环境变数
时,例如我这样会用 RAILS_ENV 跟 RAILS_MASTER_KEY
是密码类的字串可以存在 Github 仓库 >Settings>Secrets 裡,workflow 裡可以直接 secrets.RAILS_MASTER_KEY
去读取
- name: Run tests
env:
REDIS_URL: redis://localhost:6379/1
RAILS_ENV: test
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
run: bundle exec rspec --format RspecJunitFormatter --out ./reports/rspec.xml
这边是跑 rspec 有装 rspec_junit-formatter,所以可以产生一个 XML 档纪录测试结果
- name: Publish Test Report
uses: mikepenz/action-junit-report@v2
with:
report_paths: './reports/rspec.xml'
最后用 mikepenz/action-junit-report@v2
去读取 XML 的测试结果并秀出在页面上
我觉得自动化最烦的就是一堆小细节要顾,
比如说也可以完全不快取硬让它跑,但就会很慢。
像 CircleCI 的 orb
就是把每个语言、框架常用的自动化指令整理起来变成一组指令集。
但 Github Actions 直接更进一步让社群製作指令集,并准备好市集,方便搜寻跟分享,野心勃勃要干掉其它 CI。
整体上还满容易用,连 Rails 都能轻易导入 (回想一下把 Rails 装进 Docker 的夜晚),满推荐试试看的。 而且 Github(微软) 满佛的,每个月给 2000 小时,个人练习或小型专案应该是用不完啦,可以去帐号 Billing & Plans 下查用量。