DEV Community

loading...
Cover image for 用Github Actions 持續整合 Ruby on Rails

用Github Actions 持續整合 Ruby on Rails

LuoKevin
Hi, I'm developer from Taiwan
・4 min read

最近研究了一下用 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'
Enter fullscreen mode Exit fullscreen mode

說明版

# 此 workflow 的名稱,可任意取,到時會出現在 Github Actions 中
name: CI
# on 是控制何時要執行這個 workflow
# * push: 有 commit  push 時
# * pull_request: PR 更新時
on: [push, pull_request]
# 一個 workflow 可以有很多jobs 組成,它們通常是同時(平行)執行的,
#不過可以設定先後順序,下面一點會提到。
#job 的名稱可任意取名,我這裡叫它 build
# `runs-on` 是 job 要在什麼 OS 上執行, github 上的 Github Actions 好像得用 ubuntu-latest,我也是照教學沿用了
jobs:
  build: 
    runs-on: ubuntu-latest 
    # `step` 是 Github Actions 的最小單位
    # steps 裡每個 step 可以執行一或多個指令或一個 Action
    # 要執行指令,則使用 `run`
    # `run: echo "hello world!"`
    # 要執行 Action,用 `uses`
    # `uses: actions的名字`
    # 每個 step 的 name 並不是必填,但填了就像註解一樣,方便理解
    steps:
      # Action 其實就預先寫好的腳本
      # GitHub Marketplace 上有一堆,可以想像是想把自動化的腳本當成在 App Store 上賣
      # [GitHub Marketplace · Actions to improve your workflow · GitHub](https://github.com/marketplace?type=actions)
      #`actions/checkout@v1` 就是一個常用的 Action,可以把你的 git repository 下載下來
      - uses: actions/checkout@v1

      # 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](https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby)
      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.7.2
          bundler-cache: true
      # 同上,安裝 Node
      - name: Install Node
        uses: actions/setup-node@v2
        with:
          node-version: '12.16.x'
      # `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 這種安裝的程式庫都把它們快取起來吧
      - 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
  # 這是第二個 job ,我命名為 test,因為準備要跑 rspec 了
  # 上面說 job 其實是平行執行的,`needs` 可以控制先後順序,needs: build 的意思就是 build 跑完才會跑 test
  # 雖然以目前這個 workflow 來看,其實可以把steps 全寫在同一個 job ,沒什麼差別。
  # 但寫成這樣,可以方便未來加入不同類的 test,比如 js 的 test, capybara 的 test,可以在 build 後,所有的 test 同時執行。
  test:
    needs: build
    runs-on: ubuntu-latest
    # `services` 可以用 docker image 架起需要用的服務,設定好 port  mapping
    # 我這裡架了 mysql 和 redis
    # 那些 env 是去 DockerHub 上去找官方的 image 的說明才知道有什麼可以加的
    # 順帶一提,CI 時 `config/database.yml` 可以兩種處理方式
    # * 直接加入 git,內容再用 ENV 去替換
    # * 新增一個 database.yml.ci ,在 workflow 裡加一個 step 去 `run: cp config/database.yml.ci config/database.yml`
    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
    # 下面這段到 yarn install 跟 build 是做完全一模一樣的事,執行時會直接取 build 快取的結果,所以很快就會跑完。
    # 看起來很冗,不過如果未來可以用 YAML 的 Anchor 功能,這一塊可以直接寫成一個可複用的 block,
    # 目前就直接重複吧!
    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
      # 如果要開始跑 rails 的指令時,要帶一些`環境變數`時,例如我這樣會用 RAILS_ENV 跟 RAILS_MASTER_KEY
      # 是密碼類的字串可以存在Github倉庫 >Settings>Secrets 裡,workflow 裡可以直接 `${{ secrets.RAILS_MASTER_KEY }}` 去讀取
      - name: Prepare Database
        env:
          RAILS_ENV: test
          RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
        run: bundle exec rails db:prepare
      # 這邊是跑 rspec
      # 有裝 rspec_junit-formatter ,所以可以產生一個 XML 檔紀錄測試結果
      - 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'
Enter fullscreen mode Exit fullscreen mode

最後用 mikepenz/action-junit-report@v2
去讀取 XML 的測試結果並秀出在頁面上
junit xml


心得

我覺得自動化最煩的就是一堆小細節要顧,
比如說也可以完全不快取硬讓它跑,但就會很慢。
像 CircleCI 的 orb 就是把每個語言、框架常用的自動化指令整理起來變成一組指令集。
但 Github Actions 直接更進一步讓社群製作指令集,並準備好市集,方便搜尋跟分享,野心勃勃要幹掉其它 CI。

整體上還滿容易用,連 Rails 都能輕易導入(回想一下把 Rails 裝進 Docker 的夜晚),滿推薦試試看的。
而且Github(微軟)滿佛的,每個月給2000小時,個人練習或小型專案應該是用不完啦,可以去帳號 Billing & Plans 下查用量。
alt text

參考資料

Discussion (0)