Fastlane, CircleCI를 이용하여 React Native DevOps 도입기 (2)

몬드리안에이아이(주)

React Native 프로젝트에서 Fastlane을 사용하기 (IOS편)

아래 내용은 Getting Started · React Native 에 따라서 기본 환경을 구성했다는 전제 하에 진행됩니다.

React Native Project 만들기

우선 샘플 프로젝트를 하나 만들도록 한다. 주의할 점은 create-react-native-app로 만들지 말아야 한다는 것이다. crna로 프로젝트를 만들면 expo로 동작하는 데 이용하면, 네이티브 프로젝트를 만들고 Native CodeTesting 하는 것이 불가능하다.

아래 명령어로, project를 init한다.

$ react-native init DevOpsForRN # Native Project Initialize
$ cd DevOpsForRN 

Fastlane을 Native Project Directory 에서 사용하기

* 아래 서술하는 내용은 공식 문서와 동일하다.

IOS Setup - fastlane docs

Android Setup - fastlane docs

* 우선 fastlane을 사용하기 위해서는 글로벌로 설치를 해야 한다.

# Using RubyGems
$ sudo gem install fastlane -NV
$ sudo gem install bundler

# 위 명령어가 실행 불가능 하다면 대안으로 아래 명령어를 실행한다.

# Alternatively using Homebrew
$ brew cask install fastlane
$ sudo gem install bundler

* 첫 번째로 ios 폴더에 들어가서 초기화를 진행한다.

$ cd ios # DevOpsForRN/ios
$ fastlane init # 초기화

* 다음과 같이 어느 걸 하고 싶으냐고 나올 텐데 우리는 여러 가지 일들을 manual하고 싶으니 4번을 선택한다.

[13:48:34]: What would you like to use fastlane for?
1. 📸  Automate screenshots
2. 👩‍✈️  Automate beta distribution to TestFlight
3. 🚀  Automate App Store distribution
4. 🛠  Manual setup - manually setup your project to automate 
$ 4 

명령어를 치고나면

* DevOpsForRN/ios/fastlane/Appfile -> 프로젝트나 account config(app_identifier, apple_id)

* DevOpsForRN/ios/fastlane/Fastfile -> 실제 돌릴 명령어들을 정의

* DevOpsForRN /ios/fastlane/Readme -> 설명서, lane을 추가할때 자동으로 변경됨

* DevOpsForRN /ios/fastlane/report.xml -> fastlane 명령어를 돌릴때마다 변경되는 report 문서

실제로는 Appfile 은 정해진 계정, bundle identifier등을 맨 처음 한번만 정의하면 되고 Fastfile의 경우는 **lane(작업들의 집합)**을 정의할 때만 수정하면 된다.

다음 빌드로 넘어가기 전에, 한 가지를 짚고 가야 한다. IOS 에서는 Code Signing이라는 개념이 굉장히 중요하다. Code Signing을 하지 않으면 Beta testing service에 올리거나, App Store에 Deploy 할 수 없다.

Fastlane에는 3가지 signing 방법이 있다.

Fastlane의 3가지 Signing

Match -> private keycertificates를 git에 암호화하여 보관하고, 이를 가지고 signing하는 방법

Cert and Sigh -> private key와 certificates를 local에서 가지고 있고, 이를 signing하는 방법

Manual -> Apple Developer Portal에서 .p12, .cer, . mobileprovision을 직접 signing 하는 방법

1번 방법(Match)**이 암호화된 인증서를 git에 올리는 게 다소 이상할 수 있지만 이 방식은 생각보다 안전하고, 새로운 팀 멤버가 들어올 경우 아니면 새로운 개발 장비를 세팅 할때도 쉽고 간편하므로 1번 방법으로 선택했다.

Code Signing에 대하여 더 자세히 이해하고 싶다면 Code Signing Guide for Teams 문서를 참조하자.

Apple Developer Portal에서 Bundle Identifier 생성

Apple Developer Portal 에서 bundle identifier를 만든다

* 좌측 탭에서 Identifiers -> App IDs

* 우측 상단에 + 버튼 클릭

* 앱 이름을 입력

* com.teammondrian.devopsrn 와 같은 당신이 만들 identifier 입력

* Done을 클릭

Fastlane 설정 파일 수정

다음은 match - fastlane docs 을 따라 하는 내용과 동일하다

* 첫번째로 private keycertificate를 담을 Github Repository를 새로 하나 생성한다.

* 주의할 점이 하나 있는데 민감한 정보를 담기 때문에 Private Repository 로 만들어야 한다.

# DevOpsForRN/ios 폴더에서 진행한다.
$ fastlane match init
# 명령어를 입력하고, 방금전 만든 github-repo주소를 넣는다.
$ fastlane match appstore
# 애플 아이디와 비밀번호를 묻는다. 
# 이전에 만든 `com.teammondrian.devopsrn` 와 같은 identifier를 입력한다
# passphrase -> 파일들을 암호화할 비밀번호를 입력한다. (자기가 만들 비밀번호)

* 그러면 DevOpsForRN/ios/fastlane/Matchfile이 만들어 지는데, 다음과 동일하도록 수정한다.

* type 'development' 를 type 'appstore' 로 수정한다.

* app_identifier ['com.teammondrian.devopsrn']에서 com.teammondrian.devopsrn 를 당신의 Bundle Identifier로 변경한 후 추가한다.

* username 'yoohoogun114@naver.com' 와 같이 이메일을 당신의 Apple ID로 변경후 추가한다.

git_url 'https://github.com/philographer/create-react-bootstrap-match.git' # 당신이 생성한 git repo의 주소를 입력

type 'appstore' # 기본값은 development이지만, appstore로 수정한다. appstore, adhoc, enterprise or development가 될 수 있다.

app_identifier ['com.teammondrian.devopsrn'] # 당신이 생성한 bundle identifier 입력
username 'yoohoogun114@naver.com' # 당신의 apple id로 수정

# For all available options run `fastlane match --help`
# Remove the # in the beginning of the line to enable the other options

* DevOpsForRN/ios/fastlane/Fastfile 도 파일이 다음과 동일하도록 수정한다.

* 아래 부분에서, 'com.teammondrian.devopsrn” 로 되어 있는 부분을 당신의 Bundle Identifier로 변경한다.

# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

platform :ios do
  desc 'Build and run tests'
  lane :test do
    scan(scheme: 'DevOpsForRN')
  end

  lane :beta do
    desc 'TesfFlight for IOS'
    # Circle CI does not create new code signing certificates or provisioning profiles.
    sync_code_signing(type: 'appstore', readonly: true, app_identifier: 'com.teammondrian.devopsrn') # Bundle Identifier를 아까 만든 값으로 치환한다.    
    # see code signing guide for more information, readonly because of CircleCI
    build_app(scheme: 'DevOpsForRN')
    upload_to_testflight(skip_waiting_for_build_processing: true)
    # slack(
    #   slack_url: 'https://hooks.slack.com/services/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'
    #   channel: 'dev',
    #   message: 'Successfully uploaded a fastlane beta release'
    # )
  end
end

lane :increase_build_number_and_push_to_beta do |values|
    # Increment the build number (not the version number)
    # Providing the xcodeproj is optional
    increment_build_number(xcodeproj: 'DevOpsForRN.xcodeproj')
    # Commit the version bump
    commit_version_bump(xcodeproj: 'DevOpsForRN.xcodeproj', force: true)
    # Add a git tag for this build. This will automatically
    # use an appropriate git tag name
    add_git_tag
    # Push the new commit and tag back to your git remote
    push_to_git_remote(
        remote_branch: 'beta', # optional, default is set to local_branch
        force: true,    # optional, default: false
    )
end

* DevOpsForRN/ios/fastlane/Appfile 도 파일이 다음과 동일하도록 수정한다.

* app_identifier 'com.teammondrian.com' 에서 com.teammondrian.com 부분을 당신의 Bundle Identifier 로 변경 후 추가한다.

* apple_id 'yoohoogun114@naver.com' 와 같이 이메일을 당신의 Apple ID로 변경후 추가한다.

app_identifier 'com.teammondrian.com' # The bundle identifier of your app
apple_id 'yoohoogun114@naver.com' # Your Apple email address

# For more information about the Appfile, see:
#     https://docs.fastlane.tools/advanced/#appfile

* IOS의 경우, TestFlight에 올리려면 Image Assets 들을 올려야 한다. 테스트 용도이므로, Free vector icons 에서 자기가 원하는 이미지를 선택하고

Use Asset Catalog를 클릭하고 필요한 Aseets들을 집어넣는다.

작성자는 Asset Catalog Creator Pro 를 사용하였지만, 인터넷에서 다른 Asset Generator를 사용해도 무관하다.

Itunse-Connect 앱 등록

* iTunes Connect에 로그인

* 나의 앱 -> + 버튼 -> 신규 앱

* 플랫폼 -> ios 체크

* 나머지 내용들을 채우고, 번들 ID 는 이전에 Developer Portal에서 만든 Bundle Identifier를 선택한다.

Fastlane on the CircleCI

이제 대망의 CircleCI와 Fastlane을 연동할 차례이다. 이를 위해서는 Continuous Integration - fastlane docs 문서를 따라해보자.

* DevOpsForRN/ios/fastlane/Fastfile 경로에 다음과 같이 추가하자.

# DevOpsForRN/ios/fastlane/Fastfile

...
platform :ios do
  before_all do
    setup_circle_ci
  end

 desc 'Build and run tests'
...
end

* DevOpsForRN/ios/fastlane/Fastfile 의 완성된 내용은 다음과 같다.

# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

platform :ios do
  before_all do
    setup_circle_ci
  end 

  desc 'Build and run tests'
  lane :test do
    scan
  end 

  lane :beta do
    desc 'TesfFlight for IOS'
    # Circle CI does not create new code signing certificates or provisioning profiles.
    sync_code_signing(type: 'appstore', readonly: true, app_identifier: 'com.teammondrian.devopsrn2') # Bundle Identifier를 아까 만든 값으로 치환한다.    
    # see code signing guide for more information, readonly because of CircleCI
    build_app(scheme: 'DevOpsForRN')
    upload_to_testflight(skip_waiting_for_build_processing: true)
    # slack(
    #   slack_url: 'https://hooks.slack.com/services/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'
    #   channel: 'dev',
    #   message: 'Successfully uploaded a fastlane beta release'
    # ) 
  end 
end

lane :increase_build_number_and_push_to_beta do |values|
    # Increment the build number (not the version number)
    # Providing the xcodeproj is optional
    increment_build_number(xcodeproj: 'DevOpsForRN.xcodeproj')
    # Commit the version bump
    commit_version_bump(xcodeproj: 'DevOpsForRN.xcodeproj', force: true)
    # Add a git tag for this build. This will automatically
    # use an appropriate git tag name
    add_git_tag
    # Push the new commit and tag back to your git remote
    push_to_git_remote(
        remote_branch: 'beta', # optional, default is set to local_branch
        force: true,    # optional, default: false
    )   
end

* DevOpsForRN 폴더 안에 .circleci라는 디렉토리를 만든다.

* DevOpsForRN/.circleci/config.yml 에는 다음의 내용을 추가하자

# .circleci/config.yml

# To Validate your config.yml then use this cli.
# `$ circleci config validate -c .circleci/config.yml`
version: 2
jobs:
  build:
    working_directory: ~/project
    docker:
      - image: circleci/node:8
    steps:
      - checkout
      - run: yarn
      - run: yarn run test
      - persist_to_workspace:
          root: ~/project
          paths:
            - node_modules
      - store_test_results:
          path: ~/project/junit.xml

  beta-deploy-ios:
    working_directory: ~/project/ios
    macos:
      xcode: '9.0'
    steps:
      - checkout:
          path: ~/project
      - attach_workspace:
          at: ~/project
      - run: bundle update fastlane
      - run: bundle exec fastlane beta

  ios:
    macos:
      xcode: '9.0'
    working_directory: ~/project/ios
    environment:
      FL_OUTPUT_DIR: ~/project/output
    shell: /bin/bash --login -o pipefail
    steps:
      - checkout:
          path: ~/project
      - run:
          name: Set Ruby Version
          command:  echo 'ruby-2.4' > ~/.ruby-version
      # Not using a workspace here as Node and Yarn versions
      # differ between the macOS image and the Docker containers above.
      - run: yarn
      - run: bundle install --path vendor/bundle
      - persist_to_workspace:
          root: ~/project
          paths:
            - ios
      - run: bundle exec fastlane test
      - store_artifacts:
          path: ~/project/output
      - store_test_results:
          path: ~/project/output/scan

workflows:
  version: 2
  node-android-ios:
    jobs:
      - build
      - ios:
          requires:
            - build
      - beta-deploy-ios:
          filters:
            branches:
              only:
                - beta
          requires:
            - ios

CircleCI에서 Code Signing, Fastlane Match를 이용하기 위해서는 다소 많은 초기 작업이 필요하다. 다음 문서를 따라 하면 일련의 초기화, key setting 작업을 완료할 수 있다. Setting Up Code Signing for iOS Projects - CircleCI

* Github에 프로젝트 모두 올리기

Github Repository를 새로 하나 생성한다.

# DevOpsForRN/ 디렉토리에서 진행한다.
$ git init
$ git remote add origin https://github.com/philographer/DevOpsForRN.git # 자신의 repo 주소로 변경하기
$ git add --all
$ git commit -m 'first commit'
$ git push -u origin master

* CircleCI의 Project를 생성한다.

* CircleCI - Dashboard 접속

* MacOS 빌드는 유료이므로, 무료 플랜을 선택한다.

* 좌측 Settings -> MacOS Plan -> Trial Plan Click

* 좌측 Project 탭 클릭 -> Add Project 클릭

* 방금 만든 Github Repo의 오른쪽에 Setup Project 버튼을 클릭

* OS는 macOS 선택

* Start Build 버튼 선택

* Work-Flow 탭으로 이동되는데, 자세히 보려면 빌드 이름을 누르면 된다.

다음과 같이 보이면 성공이다.

* CircleCI의 Project Setting -> Environment Variables -> Add Variable 을 클릭하고, 다음의 환경변수들을 추가한다.

MATCH_PASSWORD: 아까 만든 match pass phrase 입력

FASTLANE_USER: Apple ID 입력 (ex. yoohoogun114@naver.com)

FASTLANE_PASSWORD: Apple PW 입력

* CircleCI의 Project Setting -> Checkout SSH Keys -> Add Deploy Key 버튼 또는 Create and add ... user key 버튼을 클릭한다.

그렇다면 후에 CI에서 debug 해야 할 ssh-key를 자동으로 vm에 미리 넣어준다. 따라서 미리 지정해둔 계정에서는 ci vm에 접근할 수 있다.

Trigger Script 작성

* DevOpsForRN/package.json 파일을 다음과 같이 수정한다

* scripts에 native-test 추가

* scripts에 beta 추가

{
  'name': 'DevOpsForRN',
  'version': '0.0.1',
  'private': true,
  'scripts': {
    'start': 'node node_modules/react-native/local-cli/cli.js start',
    'test': 'jest',
    'native-test': 'tools/run_native_test.sh', # 추가
    'beta': 'tools/run_beta_deploy.sh', # 추가
  },
  'dependencies': {
    'react': '16.2.0',
    'react-native': '0.52.1',
    'react-native-fabric': '^0.5.1'
  },
  'devDependencies': {
    'babel-jest': '22.1.0',
    'babel-preset-react-native': '4.0.0',
    'jest': '22.1.4',
    'react-test-renderer': '16.2.0'
  },
  'jest': {
    'preset': 'react-native'
  }
}

* Shell Script 작성하기

* DevOpsForRN/tools 디렉토리를 생성한다

* DevOpsForRN/tools/run_beta_deploy.sh 파일을 생성하고, 다음의 내용을 작성한다.

#!/usr/bin/env bash

cd ios
fastlane increase_build_number_and_push_to_beta

* DevOpsForRN/tools/run_native_test.sh 파일을 생성하고, 다음의 내용을 작성한다.

#!/usr/bin/env bash

cd ios
fastlane test

* 작성한 후, 권한을 부여한다

$ chmod +x run_beta_deploy.sh
$ chmod +x run_beta_deploy.sh

Xcode Signing 설정

* fastlane에서 사용할 인증서를 선택한다.

TestFlight 보내기

다음의 스크립트를 실행했을 때, Circle CI에서 work-flow가 올바르게 실행되나 지켜본다.

# DevOps/ 디렉토리에서
$ npm run beta

* CircleCI Dashboard 접속

* 다음과 같이 JS Test -> IOS Native Test -> TestFlight Deploy가 성공했는지 확인

TestFlight에 사용자 초대

* iTunes Connect 에 접속

* 나의 앱 -> 자신이 이전에 만들었던 앱 클릭

* TestFlight 탭 클릭

* 테스터 및 그룹 -> Itunes Connect 사용자 클릭

* 버튼 누르고, 사용자 초대

* 초대한 사용자의 이메일에서 초대 수락

암호화

* 앱 내 추가 기능

* 암호화 탭의 지침에 따른다.

Reference

match is-this-secure? - fastlane docs

GitHub - CircleCI-Public/circleci-demo-react-native: A demo React Native project that’s building on CircleCI 2.0 with Workflows.

fastlane for React Native iOS & Android app DevOps – React Native Training – Medium

circleci-demo-workflows/config.yml at workspace-forwarding · CircleCI-Public/circleci-demo-workflows · GitHub

기업문화 엿볼 때, 더팀스

로그인

/