Khi Github Actions và CircleCI song kiếm hợp bích thì đỉnh không gì bằng [Phần 2]

Cách thức thực hiện

Có thể nói ưu điểm của Github Actions chính là số lượng trigger nhiều.

CircleCI chỉ có 1 trigger là lúc push vào nhánh. Còn Github Actions thì có một lượng trigger không thể đếm xuể.

Việc khởi động CircleCI sẽ gọi API của CircleCI từ phía Github Actions và kích hoạt trigger.

Thông thường, ta sử dụng Webhook để khởi động CircleCI, nhưng lần này sẽ dùng cách call API.

Cho nên, ở setting của repository, hãy cài OFF cho Webhook.

Chỉ cần không check vào ô Active là đã tắt được Webhook.

Cách thức khởi động CircleCI sau khi Reviewer đã thực hiện approve

Dù tôi có nhắc đến việc Github Actions có rất nhiều trigger, nhưng thật ra lại không có trigger giới hạn nào sau khi reviewer approve.

Đến đây có thể các bạn sẽ thốt lên: "Ôi thế thì sao mà được nhỉ?" "Haizzz cũng thế cả mà thôi"

Nhưng xin đừng vội vàng nói như vậy, bởi ta có thể thực hiện được chỉ cần kết hợp các trigger lại với nhau!

(Tham khảo chi tiết ở link này, ghi chú nhỏ là không phải tiếng Việt https://qiita.com/dosukoi_android/items/a3464548b3aa293c62dd)

Nói một cách đơn giản, bạn chỉ cần call API của CircleCI bằng cách: trigger = submit review, và trạng thái ('STATE') của review get được thông qua webhook của github là 'APPROVED' <= vậy là đã OK rồi.

Cách thức khởi động CircleCI tại thời điểm merge

Lại phải nói, do nó không có trigger vào thời điểm merge. Vậy ta phải làm sao? Đương nhiên là lại kết hợp các trigger!

(Chi tiết có thể đọc bài viết ở link sau, bài gốc không có vietsub nhé https://qiita.com/dosukoi_android/items/e41f5c2c2a7120685af8)

Nói một cách đơn giản, ta sẽ dùng combo trigger: sau khi được close; và flag đã được merge chưa, get được thông qua webhook => nếu đã được merge rồi, vậy thì API của CircleCI sẽ được gọi.

Vấn đề

Hẳn những ai từng sử dụng CircleCI đều hiểu, nó không thể khởi chạy từng job. (tôi cho là như vậy, cũng không chắc lắm, nếu điều này sai thì xin lỗi nhé, vì tôi nghĩ rằng chắc chắn không thể làm được vậy lúc call API)

Cho nên chỉ với vấn đề này thôi, thì sẽ nảy sinh câu chuyện là : lúc reviewer approve => merge => job bị khởi chạy.

Điều này không ổn cho lắm. Dù chỉ muốn test một chút thôi nhưng lại thành ra deploy... thế là muốn ngất luôn rùi.

Cách giải quyết

Có thể giải quyết vấn đề trên bằng cách sử dụng parameters có trong CircleCI v2.1.

Lúc call API của CircleCI, sẽ truyền đi parameters và như vậy có thể phán đoạn được job nào sẽ được chạy.

https://circleci.com/docs/api/v2/#operation/triggerPipeline

Thí dụ một chút nhé!

Lúc call API

curl -X POST -H "Content-Type:application/json" -d '{"branch": "master", "parameters": {"task": "build"}}' \
https://circleci.com/api/v2/project/github/username(Organizationname)/repositoryname/pipeline

Bên CircleCI

version: 2.1

parameters:
  task:
    type: enum
    enum: ["build", "test", "deploy"]
    default: "build"

orbs: 
  android: circleci/[email protected]

job: 
  build: 
    executor: android/android
    steps:
      Viết cái muốn làm vào đây

  test:
    Giống với build

  deploy:
    Như trên

workflows:
  version: 2.1
  build:
    # đây là phần chính nhé!
    when:
      equal: [ build, << pipeline.parameters.task >> ]
      jobs:
        - build

  test:
    when:
      equal: [ test, << pipeline.parameters.task >> ]
      jobs:
        - test

  deploy:
    when:
      equal: [ deploy, << pipeline.parameters.task >> ]
      jobs:
        - deploy

Kiểu như vậy là ta sẽ truyền được parameters "enum" => tuỳ vào giá trị được truyền đi thì sẽ quyết định xem job nào sẽ được chạy!

(tác giả bài báo cực kỳ mê "enum")

PHẦN KẾT ~~~~~~

Nhắc lại !!!!

Github Actions

  • Build
  • Lint check

CircleCI

  • Test
  • Notification to Slack
  • Automatic merge
  • Deploy

Thử với Android commands như dưới nhé~~~

BUILD


name: Build

on:
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/[email protected]
      - name: set up JDK 1.8
        uses: actions/[email protected]
        with:
          java-version: 1.8
      - name: Build with Gradle
        run: ./gradlew compileDebugSources

Rất đơn giản! Thuần tuý chỉ cần viết xử lý của Github Actions là OK!

Lint check

name: Build

on:
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/[email protected]
      - name: set up JDK 1.8
        uses: actions/[email protected]
        with:
          java-version: 1.8
      - name: Build with Gradle
        run: ./gradlew lintDebug

(Thực ra nó là multi-module và tác giả bài viết sử dụng Danger để hiển thị kết quả ở comment của Github)

TEST

Từ đây trở đi là bước chính nhé! Tác giả đăng toàn bộ workflow của Github Actions trước và đăng workflow của CircleCI ở cuối (Vì CircleCI đang quản lý tập trung ở config.yml)

Đây là chạy test sau khi reviewer thực hiện approve nhé.

Github Actions

name: UnitTest

on:
  pull_request_review:
    types: [ submitted ]

jobs:
  test:
    # Handling if approved here & handling by branch name if using Git-flow
    if: ${{ github.event.review.state == 'approved' && github.event.pull_request.base.ref == 'master' }}
    runs-on: ubuntu-latest

    steps:
      - uses: actions/[email protected]
      - name: Call CircleCI API
        # Call CircleCI's API here
        run: |
          curl \
          -X POST \
          -H "Content-Type: application/json" \
          -d '{ "branch": "master", "parameters": { "build_variant": "dev", "task": "test", "pull_request_title": "${{ github.event.pull_request.title }}", "pull_request_html_url": "${{ github.event.pull_request.html_url }}", "pull_request_user": "${{ github.event.pull_request.user.login }}", "pull_request_url": "${{ github.event.pull_request.url }}", "pull_request_sha": "${{ github.event.pull_request.head.sha }}"}}' \
          https://circleci.com/api/v2/project/github/${{ github.event.repository.full_name }}/pipeline

DEPLOY

Github Actions

name: Deploy

on:
  pull_request:
    types: [closed]
    branches: [master]

jobs:
  build:
    # Only when PR is closed and merged is true → That is, it starts only when merged
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-18.04

    steps:
      - uses: actions/[email protected]
      - name: Call CircleCI API
        run: |
          curl \
          -X POST \
          -H "Content-Type: application/json" \
          -H "Circle-Token: ${{ secrets.CIRCLE_TOKEN }}" \
          -d '{ "branch": "master", "parameters": { "build_variant": "dev", "task": "deploy", "pull_request_title": "${{ github.event.pull_request.title }}" } }' \
          https://circleci.com/api/v2/project/github/${{ github.event.repository.full_name }}/pipeline

CircleCI

version: 2.1

parameters:
  # Handling which job to start
  task:
    type: enum
    enum: ["deploy", "test"]
    default: "deploy"
  # Can be changed for each environment
  build_variant:
    type: enum
    enum: ["dev", "stg", "production"]
    default: "dev"
  # From here on is used for Slack notifications
  pull_request_title:
    type: string
    default: ""
  pull_request_html_url:
    type: string
    default: ""
  pull_request_url:
    type: string
    default: ""
  pull_request_user:
    type: string
    default: ""
  pull_request_sha:
    type: string
    default: ""

executors:
  android:
    docker:
      - image: circleci/android:api-30
    environment:
      JVM_OPTS: -Xmx1536m
      GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-Xmx1536m -XX:+HeapDumpOnOutOfMemoryError" -Dorg.gradle.configureondemand=true -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false'


orbs:
  android: circleci/[email protected]

jobs:

  deploy:
    executor: android
    steps:
      - checkout
      - run:
          # Create aab file
          name: Build with Gradle
          command: |
            ENV=(<< pipeline.parameters.build_variant >>)
            ENV_UPPER_CASE=${ENV[@]~}
            ./gradlew ":navigation:phone:bundle${ENV_UPPER_CASE}Release"
      - run:
          # Call DeployGate's API
          name: Distribute App
          command: |
            curl \
            -H "Authorization: token $DEPLOY_GATE_API_KEY" \
            -F "[email protected]/phone/build/outputs/bundle/<< pipeline.parameters.build_variant >>Release/phone-<< pipeline.parameters.build_variant >>-release.aab" \
            -F "message=<< pipeline.parameters.pull_request_title >>" \
            -v "https://deploygate.com/api/users/$DEPLOY_GATE_USER_NAME/apps"


  test:
    executor: android
    steps:
      - checkout
      - run:
          name: Unit Test
          command: |
            ENV=(<< pipeline.parameters.build_variant >>)
            ENV_UPPER_CASE=${ENV[@]~}
            ./gradlew "test${ENV_UPPER_CASE}DebugUnitTest"
      - run:
          # Slack notification on failure
          when: on_fail
          name: Unit Test Failure Notification
          command: |
            curl \
            -X POST \
            -H "Content-Type: application/json" \
            -d '{"attachments": [{"color": "#D73A49", "title": "<< pipeline.parameters.pull_request_title >>", "title_link": "<< pipeline.parameters.pull_request_html_url >>", "text": "Failure Unit Test", "author_name": "<< pipeline.parameters.pull_request_user >>"}]}' \
            $SLACK_WEBHOOK

  auto_merge:
    executor: android
    steps:
      - run:
          name: Wait For Status Check
          command: sleep 5s
      - run:
          # Call MergeAPI of Github
          name: Auto Merge
          command: |
            curl \
            -X PUT \
            -H "Authorization: token $PERSONAL_ACCESSTOKEN" \
            -H "Content-Type: application/json" \
            -d '{"sha": "<< pipeline.parameters.pull_request_sha >>", "merged": "true", "message": "Pull Request successfully merged"}' \
            "<< pipeline.parameters.pull_request_url >>/merge"


workflows:
  version: 2.1
  build_and_deploy:
    # Launch this job if the parameter (when calling the API)) is 'deploy'
    when:
      equal: [deploy, << pipeline.parameters.task >>]
    jobs:
      - deploy:
          name: Deploy

  test:
    # Launch this job if the parameter (when calling the API) is 'test'
    when:
      equal: [test, << pipeline.parameters.task >>]
    jobs:
      - test
      - auto_merge:
          name: Auto Merge
          requires:
            - test
     # When the 'test' job is finished, start the 'auto_merge' job

(Gần như là AUTOMATIC từ bước build cho đến deploy, việc duy nhất cần chạy cơm đó là lúc REVIEW Miễn là không có conflicts, thì có thể tự động hoá mọi thứ từ test, merge cho đến deploy đó!)

Hết rùi ạ, thanks for reading~~


All Rights Reserved