본문 바로가기

iOS

[iOS] Fastlane + Github Actions로 CI/CD 파이프라인 구축하기 (6편 - Fastlane, github actions로 tuist project의 CI/CD 구현)

[iOS] Fastlane + Github Actions로 CI/CD 파이프라인 구축하기 (1편 - CI/CD란 무엇일까?)

[iOS] Fatslane + Github Actions로 CI/CD 파이프라인 구축하기 (2편 - Github Actions 살펴보기)

[iOS] Fatslane + Github Actions로 CI/CD 파이프라인 구축하기 (3편 - fastlane으로 TestFlight에 앱 업로드하기)

[iOS] Fastlane + Github Actions로 CI/CD 파이프라인 구축하기 (4편 - fastlane match로 인증서와 profile을 git 저장소에 관리하기)

[iOS] Fastlane + Github Actions로 CI/CD 파이프라인 구축하기 (5편 - Tuist와 Fastlane 함께 사용하기, xcconfig과 schemes, match 설정)


Tuist 프로젝트에 fastlane과 Github Action으로 CI/CD 적용하기

지금까지 fastlane, match, github action을 사용하는 방법에 대해 간단히 알아보았고, tuist 프로젝트에는 어떤 식으로 signing을 해야 하는지에 대해서도 살펴 보았습니다.

 

이번 편에서는 이 모든 것을 종합하여 CI/CD 파이프라인을 구현해 보겠습니다. fastlane 명령어 한 줄 입력으로 가상 머신에서 tuist project를 빌드하고, QA Scheme을 적용하여 test flight에 업로드할 수 있도록 합니다.

 

tuist를 사용한 project가 다른 프로젝트와 차이가 나는 부분은, xcodeproj파일을 remote에 올리지 않는다는 것입니다. 따라서 빌드 버전 및 앱스토어 버전을 증가시킨 다음 가상 머신에 올리는 것이 불가능해집니다. 물론 infoPlist나 xcconfig의 값을 변경할 수 있지만, 가상 머신에서 fastlane의 increment version을 이용하는 방법을 사용하는 쪽이 간단해 보여 그렇게 구현해 보겠습니다.

 

그 전에 ssh key와 secrets에 대한 내용을 먼저 알아야 합니다.

1. SSH Key 인증

우선 ssh key에 대해 알아야 합니다. 이전에 fastlane match를 이용하여 private repo에 profile과 인증서를 저장했기에, 가상 머신이 이 repo에 접근하기 위해서는 특별한 권한이 필요합니다. 이 권한을 얻기 위해 custom action인 shimataro/ssh-key-action을 사용할 수 있습니다. 해당 액션을 실행하면 github가 인증서가 담긴 private repo에 접근할 수 있는 권한을 얻습니다.

 

SSH Key를 생성하고 github에 적용하는 방법은 링크에 자세히 설명되어 있습니다. 만약 이전에 생성했지만 key의 내용이 기억나지 않거나, openssh를 사용하고 있어 오류가 난다면 링크를 참조하여 rsa 형식으로 변환할 수 있습니다.

2. Github Secrets

github repository에는 github secrets라는 환경 변수가 있습니다. 보안을 유지하는 동시에 actions와 같은 곳에서 환경 변수를 사용하고 싶다면 github secrets를 사용하면 됩니다. github secrets 등록 방법은 여기에 자세히 설명되어 있습니다.

 

CI/CD 구현 중 추가로 알게 된 것은, github actions의 'pull request' trigger가 fork한 레포에서 발동된 경우, github secrets에서 정보를 받아올 수 없다는 것입니다. 생각해보면 보안상 당연한 이야기일 수 있는데, secrets에 대한 hijacking이 가능하기 때문에 그러한 위험으로부터 안전한 origin repo에서만 허가되는 것 같습니다. 링크에 설명이 나와있습니다.

 

Fastlane tag push

본격적인 CI/CD 플로우의 시작은 fastlane을 통해 변경 사항을 tag push함으로써 이루어 집니다.

desc 'Upload QA App'
lane :QA do |options|
  set_version(version: options[:version])
  version_number = "#{lane_context[SharedValues::VERSION_NUMBER]}(#{lane_context[SharedValues::BUILD_NUMBER]})"

  git_commit(path: "./", message: "QA staging")
  add_git_tag(tag: "v#{options[:version]}")
  push_to_git_remote
end

우선 원하는 version을 세팅하고 context를 통해 version_number를 가져옵니다. 그리고 커밋을 한 다음 tag를 붙이고, remote에 push하는 간단한 과정입니다. 여기서 version_number를 만들었지만 아직 쓰고 있지는 않는데, 추후 github actions에서 parsing을 통해 사용할 예정입니다. 여기서는 그냥 version만 넘겨주고 있습니다.

Github Actions

우선 아래 코드는 많은 부분 링크를 참조하여 작성했음을 알려드립니다.

 

일단 workflow의 trigger는 develop 브랜치에 tag를 푸시한 경우입니다. if: startWith을 통해 tag가 있는 경우에만 실행하도록 해주었습니다.

 

runs-on과 matrix 부분에서는 실행환경에 대한 정의를 하고 있습니다. matrix 내부에 여러 element를 넣어주면 다수의 환경에서 CI가 가능합니다.

name: QA

on:
  push:
    branches:
      - 'develop'
    tags:
      - 'v*.*.*'

jobs:
  QA:
    if: startsWith(github.ref, 'refs/tags/')
    
    runs-on: macos-latest
    
    strategy:
      matrix:
        tuist: [ '3.16.0' ]
        ruby: [ '2.7' ]

    steps:
      - uses: actions/checkout@v2
      - uses: shimataro/ssh-key-action@v2
        with:
          key: ${{ secrets.SSH_KEY }}
          known_hosts: ${{ secrets.KNOWN_HOSTS }}
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby }}
          bundler-cache: true
      - run: brew install fastlane
      - name: Install Tuist
        run: |
          curl -L -o ./tuist.zip https://github.com/tuist/tuist/releases/download/${{ matrix.tuist }}/tuist.zip
          unzip ./tuist.zip -d .tuist-bin
      - name: tuist generate
        run: |
          .tuist-bin/tuist fetch
          .tuist-bin/tuist generate
      - run: bundle exec fastlane set_version version:1.0.0
      - run: bundle exec fastlane QA_CD
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSPHRASE }}
          KEYCHAIN_NAME: ${{ secrets.KEYCHAIN_NAME }}
          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
          APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
          APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }}
          APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}

1. git checkout : 말 그대로 git checkout을 해주는 부분입니다.

git init -> git config -> git fetch -> git checkout 의 과정을 통해서 repository의 code를 가상 머신이 사용할 수 있도록 합니다.

 

2. ssh-key-action : 다음으로는 secrets에 저장된 SSH_KEY를 받아와서 match에 사용할 권한을 받아오고 있습니다.

 

3. ruby, fastlane, tuist 설치 : 이후 ruby, fastlane, tuist를 가상 머신에 설치합니다.

 

4. tuist generate : 설치가 완료되면 tuist의 의존성을 fetch해오고 project를 generate합니다.

 

5. bundle exec fastlane set_version version:1.0.0 : marketing version과 build version을 만들어진 project에 부여하는 custom lane입니다. 현재 릴리즈 이전 시점이기 때문에 고정적으로 1.0.0 버전만 부여하도록 했습니다.

 

6. bundle exec fastlane QA_CD : 각종 환경변수를 secrets로 받아서 앱을 빌드하고, 배포하는 custom lane입니다.

Fastlane QA_CD

마지막 단계인 fastlane QA_CD는 아래와 같이 구성된 lane입니다.

desc 'Upload QA App'
lane :QA_CD do |options|
    create_keychain(
      name: ENV["KEYCHAIN_NAME"],
      password: ENV["KEYCHAIN_PASSWORD"],
      timeout: 1800,
      default_keychain: true,
      unlock: true,
      lock_when_sleeps: false
    )

    match(
      git_branch: "master",
      type: "appstore",
      readonly: true,
      keychain_name: ENV["KEYCHAIN_NAME"],
      keychain_password: ENV["KEYCHAIN_PASSWORD"]
    )

    app_store_connect_api_key(
      key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"],
      issuer_id: ENV["APP_STORE_CONNECT_API_ISSUER_ID"],
      key_content: ENV["APP_STORE_CONNECT_API_KEY_CONTENT"]
    )

    build_app(
      scheme: "EarthIsRound-QA",
      include_symbols: true,
      export_method: "app-store"
    )

    upload_to_testflight
end

1. create_keychain : 새로운 키체인을 저장합니다. 추후 match에서 받아온 인증서와 연결됩니다. keyChain을 만들지 않으면 framework의 signing 과정에서 에러가 납니다. dsym을 generating 하는 과정에서 멈추는 오류가 있었기에, keychain을 생성해 주었습니다. 해당 오류에 대한 레퍼런스는 여기에 있습니다.

 

2. match : fastlane match를 이용하여 인증서와 프로필을 받아옵니다.

 

3. app_store_connect_api_key : APP Store Connect API에 접근하기 위한 key 세팅

공식 문서에서, fastlane은 역사적으로 Apple ID, username 및 password 또는 웹 세션을 이용하여 App Store Connect API에 접근해 왔다고 합니다. 그러나 WWDC 2018에서 도입된 app store connect api key 이후로는 key를 사용하는 것을 권장한다고 합니다. 사유는 아래와 같습니다.

  • No 2FA needed ( 이중 인증 )
  • Better performance
  • Documented API
  • Increased reliability

app store connect api key의 이용 방법에 대해서는 이 글에 자세히 나와 있습니다.

 

4. build_app : 원하는 scheme을 지정하여 ipa 파일을 빌드합니다.

 

5. upload_to_testflight : 해당 빌드를 testflight에 제출합니다.

https://docs.fastlane.tools/actions/git_commit/

 

최종적으로 아래와 같이 workflow가 종료됩니다.

다음 스텝

 여기까지 QA 버전의 빌드를 'fastlane QA version: 1.0.0' 명령어 한 줄로 가상 머신에서 테스트플라이트에 제출하는 방법에 대해 알아보았습니다. 다음으로는 deliver를 통해 build를 app store에 제출하거나, github actions를 통해 unit tests를 수행하는 방법에 대해 알아보겠습니다.