본문 바로가기

Swift/All

Github Action을 이용한 CI-CD (트러블 슈팅들...까지)

테스트앱을 배포할 때 매번 아카이브 하고 올리고 내용을 슬랙에 공유하고 하는게 귀찮아서 dev branch에 mergey가 되면 testflight에 올라가고 자동으로 슬랙에 공유되는 형식을 구현하고자 CI-CD를 구현하고자 했다
 
그 중 Github action을 사용하기로 결정한 이유는... 솔직히 "제일 간편하게 세팅 할 수 있어서" 이다
 

포스팅 읽기 전 주의 사항...

1. 일단 가장 하단에 최종 workflows를 작성해두긴 할건데 중간에 작성한 workflows코드는 최대한 최종본으로 반영하려고 했으나 포스팅을 하면서 계속해서 수정된 코드이기에 최종본하고 다른 부분이 있을 수 있음
 
2. 하단에 트러블 슈팅 있는데 장담컨데 모든 블로그에서 다룬 트러블 슈팅보다 많을듯... ㄷㄷ(본인이 멍청하다 이말임)
 
 

CI-CD구현 전 Pod -> Spm 전환

일단 pod -> spm으로 전환하는 작업을 먼저 했는데 이유는 아래와 같다
- firebaseAnalytics라이브러리를 pod으로 설치했을 때 이 xcode15에서 파일을 수정해줘야 하는 일이 있다("DT_"를 지워주는 일) -> 이것 때문에 git에서 아카이브가 잘 안될 것 이기 때문
- github action구현 파일(yml)에서 pod install을 따로 해줘야 한다
- 안정성을 높이기 위해(프로젝트 관리를 편하게 하기 위해) 원래 SPM으로 전환하려고 했는데 미루고 미루다 이번 기회에 진행
 

0. 파일 준비

- testflight로 배포까지 하기 위해서는 Certification & Provisioning profile 그리고 ExportOptions.plist 파일 그리고 몇 가지 키들이 필요하다
 

- Certification

키체인 접근에서 얘를 내보내기 해준다(이름은 Certification으로 지정)

 

 

그리고 여기서 작성하는 암호는 꼭 기억해두도록 하자(나중에 사용)
 

- Provisioning profile

애플 개발자 홈페이지에서 해당 경로에서 어플에서 사용중이 프로필 파일 다운
 

!! 추가적으로 provisioning을 사용한다면(Notification Service Extension같이) 그 것도 다운로드 해 암호화 필요 !!

 

- 수동 코드 서명

 
위에서 다운 받은 프로파일로 프로젝트에서 자동 서명 체크 해제하고 해당 파일을 import해준다

- 암호화

gnupg을 이용해 두 파일을 암호화를 해야한다

brew install gnupg2 // gnupg설치

//설치 후 두 파일이 있는 폴더로 가 암호화 진행

gpg -c 인증서이름.p12
gpg -c Provisioning이름.mobileprovision

암호화 실행을 하면 이럼 화면이 뜨는데 이 비밀번호도 잘 기억해두자(나중에 복호화 할 때 사용)
 

- ExportOptions.plist

 
프로젝트 아카이브 후 이렇게 Export를 해주면

이렇게 ExportOptions.plist가 생기는데 이걸 프로젝트에 넣어준다
나중에 경로설정을 하기 편하게 프로젝트의 가장 상위 폴더에 넣어줬다

- 프로젝트에 저장

프로젝트폴더/.github/secrets/ < 폴더에 두 파일을 이동시킨 후 푸쉬
 
- 필요한 키 적어두기

위 경로에 들어가 Issure Id와 api key id를 메모해두고 API키 다운로드 버튼을 클릭해 다운한다
(다운은 딱 일 번밖에 못받기 때문에 잘 보관해놔야 한다!!!)



키 파일에 들어가면 이렇게 키가 있으니 이것도 메모

(!! 가려놓은 부분만 key에 입력하는게 아니라

-----BEGIN PRIVATE KEY-----
key 내용
-----END PRIVATE KEY-----

이 전체를 복사해서 넣어야한다!!)

 
 

1. 깃허브에 파일 암호 설정

휴... 이제 준비 과정이 끝났다
이제 위 과정에서 메모해둔 비밀번호들을 깃허브에서 설정해야 한다
 

 

이렇게 시크릿 만드는 화면이 나오는데
Name과 그 안에 들어갈 시크릿은 아래와 같이 만들어줬다

 

2. yml파일 생성

- 레포지토리에서 Action 탭 -> Swift의 Configure 선택

 

3. 코드(?) 수정

맨 아래에 전체 코드를 첨부할 예정이나 일단은 부분적으로 설명을 작성해보려 한다
 

- 환경 변수 설정

jobs:
  build:
    runs-on: macos-latest
    env:    
      XC_PROJECT: ${{ 'sampleroad-iOS/sampleroad-iOS.xcodeproj' }}
      XC_SCHEME: ${{ 'sampleroad-iOS' }}
      XC_ARCHIVE_PATH: ${{ 'sampleroad-iOS.xcarchive' }}
      XC_EXPORT_PATH: ${{ './artifacts' }}
      KEYCHAIN: ${{ 'temp.keychain' }}
    #프로젝트 이름 설정
    #sampleroad-iOS 는 내가 관리하는 프로젝트 이름이고 이걸 다른거로 대체 하면 된다
    #XC_CONFIGURATION "debug"보다 "release"가 좀더 최적화된 코드(?)를 제공해 배포에 적함
    #KEYCHAIN의 temp 부분은 임의로 지정 가능
      
      ENCRYPTED_CERTS_FILE_PATH: ${{ '.github/secrets/certification.p12.gpg' }}
      DECRYPTED_CERTS_FILE_PATH: ${{ '.github/secrets/certification.p12' }}
      CERTS_ENCRYPTION_PWD: ${{ secrets.CERTS_ENCRYPTION_PWD }}

      ENCRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/comtovbanahsampleroad.mobileprovision.gpg' }}
      DECRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/comtovbanahsampleroad.mobileprovision' }}
      PROVISION_ENCRYPTION_PWD: ${{ secrets.PROVISION_ENCRYPTION_PWD }}
      
      ENCRYPTED_NOTIFICATION_PROVISION_FILE_PATH: ${{ '.github/secrets/comtovbanahsampleroadNotificationService.mobileprovision.gpg' }}
      DECRYPTED_NOTIFICATION_PROVISION_FILE_PATH: ${{ '.github/secrets/comtovbanahsampleroadNotificationService.mobileprovision' }}
      NOTIFICATION_PROVISION_ENCRYPTION_PWD: ${{ secrets.NOTIFICATION_PROVISION_ENCRYPTION_PWD }}

      CERT_EXPORT_PWD: ${{ secrets.CERT_EXPORT_PWD }}
    # 인증서 등의 파일 경로와 시크릿에서 지정해준 시크릿을 선언해준다

 

- Checkout & Keychain

    steps:
    - name: check Xcode version
      run: /usr/bin/xcodebuild -version
# action 처음 만들면 있는 uses그대로 이렇게 만들어줌

    - name: checkout repository
      uses: actions/checkout@v3
      
    - name: Keychain
      run: |
        security create-keychain -p "" "$KEYCHAIN" 
        security list-keychains -s "$KEYCHAIN" 
        security default-keychain -s "$KEYCHAIN" 
        security unlock-keychain -p "" "$KEYCHAIN"
        security set-keychain-settings -lut 1200
        security list-keychains
# 환경 변수 설정에서 지정한 이름으로(나는 Keychain으로 만들었으니 이걸 대입)
# -lut 1200은 키체인 언락 후 유지시간이라는데 아카이브가 얼마나 걸리는지 몰라서 다른 블로그 처럼 1200으로 지정
# 만약 이 시간 때문에 아카이브 안된다면 늘려주도록 할 예정

 

- Signing

    - name: Configure Code Signing
      run: |
        gpg -d -o "$DECRYPTED_CERTS_FILE_PATH" --pinentry-mode=loopback --passphrase "$CERTS_ENCRYPTION_PWD" "$ENCRYPTED_CERTS_FILE_PATH"
        gpg -d -o "$DECRYPTED_PROVISION_FILE_PATH" --pinentry-mode=loopback --passphrase "$PROVISION_ENCRYPTION_PWD" "$ENCRYPTED_PROVISION_FILE_PATH"
        gpg -d -o "$DECRYPTED_NOTIFICATION_PROVISION_FILE_PATH" --pinentry-mode=loopback --passphrase "$NOTIFICATION_PROVISION_ENCRYPTION_PWD" "$ENCRYPTED_NOTIFICATION_PROVISION_FILE_PATH"
        security import "$DECRYPTED_CERTS_FILE_PATH" -k "$KEYCHAIN" -P "$CERT_EXPORT_PWD" -A
        security set-key-partition-list -S apple-tool:,apple: -s -k "" "$KEYCHAIN"
        mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
        echo `ls .github/secrets/*.mobileprovision`
        for PROVISION in `ls .github/secrets/*.mobileprovision`
          do
            UUID=`/usr/libexec/PlistBuddy -c 'Print :UUID' /dev/stdin <<< $(security cms -D -i ./$PROVISION)`
          cp "./$PROVISION" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision"
        done
        
        # 솔직히 뭐하는 코드인지는 정확히 모르겠지만 일단 환경변수 설정한거 매칭하는 코드 같다
        # 뒤에 변수명 잘 확인해보고 매칭해준다

 

 

- Archaive & Export & Export

    - name: Archive
      run: |
        mkdir artifacts
        xcodebuild archive -project "$XC_PROJECT" -scheme "$XC_SCHEME" -configuration release -archivePath "$XC_ARCHIVE_PATH"
        
    - name: EXPORT
      run: | 
        xcodebuild -exportArchive -archivePath "$XC_ARCHIVE_PATH" -exportOptionsPlist ExportOptions.plist -exportPath "$XC_EXPORT_PATH"

 

- APPSTORE_API_PRIVATE_KEY 세팅 & TestFlight 업로드

    - name: Install private API key P8 && Upload app to TestFlight
      env:
        APPSTORE_API_PRIVATE_KEY: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}
        APPSTORE_API_KEY_ID: ${{ secrets.APPSTORE_API_KEY_ID }}
        APPSTORE_ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }}
      run: | 
          mkdir -p ./private_keys
          echo "$APPSTORE_API_PRIVATE_KEY" > ./private_keys/AuthKey_$APPSTORE_API_KEY_ID.p8
          ls -al
          xcrun altool --output-format xml --upload-app -f sampleroad-iOS.ipa --type ios --apiKey $APPSTORE_API_KEY_ID --apiIssuer $APPSTORE_ISSUER_ID

 

결과는....???

개같이 실패;;
사실 오늘이 두 번째 도전인데 저번에는 빌드에서 실패하기라도 했지 이번에는 아예 시작조차 못한다
뭐 uese설정부터 아예 잘못해서 시작도 못한 것 같은데
일단 구글링...

 
uses 앞에 하이픈 빼주니 된다
 

는 개뿔 변수 설정 잘못했다
앞에 secrets. 붙여주니까 일단 돌아가는 중...
 

Proccess completed with exit code 65.

저번에 할 때도 이 것 때문에 계속 실패 했는데 그 때는 읽어도 뭔이유인지 몰랐는데 이번에는 자동서명을 사용 할 수 없다는 내용이다
아마 위 순서대로 잘 따라왔다면 이 에러는 뜨지 않았을 것이며 나는 처음에 수동 서명 절차를 진행 안해줘서 뜬거니 패쓰
 
 
는 수동으로 해줬는데도 오류 뜬다
이유는 푸쉬 서비스 때문에 UNNotificationServiceExtension을 사용중인데 얘는 인증서가 달라서 그런 것 같음
다른 인증서 세팅 어떻게 해야하는지 찾아봐야지(2023년 12월 22일 기준)
 

23.12.24(일)

 
위와 같이 UNNotificationServiceExtension의 provisioning도 추가해줬더니 인증서는 통과 했으나...
 

compileError

compile 에러가 뜬다...
분명 xcode에서는 테스트 플라이트 까지 잘 올라갔는데...? 포기...
 

23.12.25(크리스 마스...)

자다가 문득 내 xcode는 15.0.1이고 gitgub action에서 빌드 하는 xcode 버전은 14.2인데 15에서는 잘되는 코드가 14에서는 문제가 있을 수 있지 않을까? 하고 바로 14.2 다운로드... 그런데 실행이 안된다...?(sonoma에서는 14버전대 xcode실행을 못시킴)
 
암튼 위 내용은 
https://dev-doogie.tistory.com/10

xcode 여러개 사용하는 방법(xcodes)

작성 예정

dev-doogie.tistory.com

https://dev-doogie.tistory.com/11

xcode 여러개 사용하는 방법(xcodes)

작성 예정

dev-doogie.tistory.com

 
여기서 확인 하고 우여곡절 끝 xcode 14.2를 실행시켰더니?!!

이게 왠말이오....
아니 return을 안해준것도 문제 있는데 이게 15에서는 돌아간다니 ㄷㄷ 
아무튼 14.2에서 돌아가게 title 내부 스위치 문에서 string 앞에 다 return을 시켜주고 다시 ㄱㄱ

드디어!!! Archive ~ EXPORT까지 성공했다 ㅠㅠ 눈물
하지만 TestFlight 업로드 과정에서 문제가 생겼다
뭐 이것도 해결하면 되지~ 여까지 온것만으로도 감동 ㅠㅠ
일단 외출해야하니 이만...여기까지(이따 밤에 다시해야지)
 
 

외출 후

찾아보니 key 관련해서 문제가 있었는데
APPSTORE_API_PRIVATE_KEY를 base64로 디코딩 하고 어쩌고 저쩌고...
아무튼 기존에 자성했던 코드로는 안된다! 이말...

원래 간단하게 사용했던 오픈소스가 아닌 직접 디코딩을 해줘서 만들어주는 방법으로 하고 다시 ㄱㄱ...

했는데 계속 이딴 에러가 나서 뭐 쬐끔쬐끔 수정하면서 계속 올렸는데
포크해온 내 레포지토리가 gitaction에 사용하는 2000분을 다 초과해서 돈 내놓으라길래... 어쩔 수 없이 회사 레포로 가서 다시 ㄱㄱ...
(뭐! 어차피 회사 플젝에 적용하는거니까...!)
 
암튼 문제는 secret 저장 할 때
다운 로드한 api private key도 저장을 하는데

-----BEGIN PRIVATE KEY-----
key 내용
-----END PRIVATE KEY-----

 

이렇게 되어있으면 key내용만 저장해서 생긴 문제였던 것...
위 아래 BEGIN과 END까지 복사해서 저장해줘야 한다고 하는데...
 
일단 수정해주고 다시 ㄱㄱ 했지만
또 문제가 생겨서 key 내용 전체 복사해서 넣어야 한다고 했던 블로그 보니 decode를 안하고 하길래
'키를 인코딩 하는데 문제가 생기는거면 디코드를 안하고 해도 되는건가? 그냥 해볼까?'
하는 생각이 들어 디코드 하는 라인을 지우고 실행한 결과...
 

이제 빌드 버전이 중복된다는 에러가 발생했다
이 에러가 발생하는 것 보니 이제 key 문제는 아닌 것 같다
근데 뜬금 없이 7이라는 숫자가 있어서 App store connect를 확인해보니...
 

?!
딱! 7버전까지 있는거...

그래서 이 부분을 8로 변경해주고 다시 ㄱㄱ...
 
 

드디어...!

크리스마스의 축복이다...! 23년 12월 26일이 되기를 17분 남기고 드디어 테스트 플라이트에 올라갔다...!!
 
시간이 19분이나 걸렸는데
실행시켜놓고 응가하고 양치하고 강아지 양치시키고 왔는데도 계속 돌아가고 있길래 덜덜덜덜 떨면서 잘준비 하고 왔더니 드디어 성공...
 
내 레포로 47번 회사 레포로 26번(왜냐면 처음 시도는 회사 레포로 했었기에;;) 총 73번의 실행 끝에 드디어 성공했다...
 
아 편하게 자야지
 
 

workflows최종

# This workflow will build a Swift project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift

name: "sampleroad-iOS"

# 트리거: "dev" 브랜치에 푸쉬 혹은 머지 되었을 경우
on:
  push:
    branches: [ "dev" ]
  pull_request:
    branches: [ "dev" ]

jobs:
  build:
    runs-on: macos-latest
    env:
      XC_PROJECT: ${{ 'sampleroad-iOS/sampleroad-iOS.xcodeproj' }}
      XC_SCHEME: ${{ 'sampleroad-iOS' }}
      XC_ARCHIVE_PATH: ${{ 'sampleroad-iOS.xcarchive' }}
      KEYCHAIN: ${{ 'temp.keychain' }}
      
      ENCRYPTED_CERTS_FILE_PATH: ${{ '.github/secrets/certification.p12.gpg' }}
      DECRYPTED_CERTS_FILE_PATH: ${{ '.github/secrets/certification.p12' }}
      CERTS_ENCRYPTION_PWD: ${{ secrets.CERTS_ENCRYPTION_PWD }}

      ENCRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/comtovbanahsampleroad.mobileprovision.gpg' }}
      DECRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/comtovbanahsampleroad.mobileprovision' }}
      PROVISION_ENCRYPTION_PWD: ${{ secrets.PROVISION_ENCRYPTION_PWD }}
      
      ENCRYPTED_NOTIFICATION_PROVISION_FILE_PATH: ${{ '.github/secrets/comtovbanahsampleroadNotificationService.mobileprovision.gpg' }}
      DECRYPTED_NOTIFICATION_PROVISION_FILE_PATH: ${{ '.github/secrets/comtovbanahsampleroadNotificationService.mobileprovision' }}
      NOTIFICATION_PROVISION_ENCRYPTION_PWD: ${{ secrets.NOTIFICATION_PROVISION_ENCRYPTION_PWD }}

      CERT_EXPORT_PWD: ${{ secrets.CERT_EXPORT_PWD }}
      
    steps:
    - name: check Xcode version
      run: /usr/bin/xcodebuild -version
      
    - name: checkout repository
      uses: actions/checkout@v3
      
    - name: Keychain
      run: |
        security create-keychain -p "" "$KEYCHAIN" 
        security list-keychains -s "$KEYCHAIN" 
        security default-keychain -s "$KEYCHAIN" 
        security unlock-keychain -p "" "$KEYCHAIN"
        security set-keychain-settings -lut 1200
        security list-keychains
        
    - name: Configure Code Signing
      run: |
        gpg -d -o "$DECRYPTED_CERTS_FILE_PATH" --pinentry-mode=loopback --passphrase "$CERTS_ENCRYPTION_PWD" "$ENCRYPTED_CERTS_FILE_PATH"
        gpg -d -o "$DECRYPTED_PROVISION_FILE_PATH" --pinentry-mode=loopback --passphrase "$PROVISION_ENCRYPTION_PWD" "$ENCRYPTED_PROVISION_FILE_PATH"
        gpg -d -o "$DECRYPTED_NOTIFICATION_PROVISION_FILE_PATH" --pinentry-mode=loopback --passphrase "$NOTIFICATION_PROVISION_ENCRYPTION_PWD" "$ENCRYPTED_NOTIFICATION_PROVISION_FILE_PATH"
        security import "$DECRYPTED_CERTS_FILE_PATH" -k "$KEYCHAIN" -P "$CERT_EXPORT_PWD" -A
        security set-key-partition-list -S apple-tool:,apple: -s -k "" "$KEYCHAIN"
        mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
        echo `ls .github/secrets/*.mobileprovision`
        for PROVISION in `ls .github/secrets/*.mobileprovision`
          do
            UUID=`/usr/libexec/PlistBuddy -c 'Print :UUID' /dev/stdin <<< $(security cms -D -i ./$PROVISION)`
          cp "./$PROVISION" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision"
        done
        
    - name: Archive
      run: |
        xcodebuild archive -project "$XC_PROJECT" -scheme "$XC_SCHEME" -configuration release -archivePath "$XC_ARCHIVE_PATH"
        
    - name: EXPORT
      run: | 
        xcodebuild -exportArchive -archivePath "$XC_ARCHIVE_PATH" -exportOptionsPlist ExportOptions.plist -exportPath . -allowProvisioningUpdates
        ls
        
    - name: Install private API key P8 && Upload app to TestFlight
      env:
        APPSTORE_API_PRIVATE_KEY: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}
        APPSTORE_API_KEY_ID: ${{ secrets.APPSTORE_API_KEY_ID }}
        APPSTORE_ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }}
      run: | 
          mkdir -p ./private_keys
          echo "$APPSTORE_API_PRIVATE_KEY" > ./private_keys/AuthKey_$APPSTORE_API_KEY_ID.p8
          ls -al
          xcrun altool --output-format xml --upload-app -f sampleroad-iOS.ipa --type ios --apiKey $APPSTORE_API_KEY_ID --apiIssuer $APPSTORE_ISSUER_ID

 
 
 

추가적으로...

1. 앱 이미지 파일이 안나오는게 있어서 확인해 보니 gitaction으로 테스트 플라이트 올라갈때 파일 명에한글 있는 파일은 제대로 전달이 안된다 이미지 셋 뿐만 하니라 finder로 찾아가 실 이미지 파일 이름까지 변경해줘야한다
 
2. 수동인증으로 변경한 뒤 실 기기 빌드는 

얘만 오토로 하면 됨
 
3. 깃허브 액셔은 빌드 시간에 대해 월 2000분을 무료로 제공한다
"엥? 빌드 시간만 2000분이면 개꿀인데?"
할 수 있으나 mac환경 빌드의 경우 10배의 가격이 매겨짐
즉, 빌드 시간 12분이면 120분 사용하는 꼴...;; 열심히 했는데 실 사용하기는 답없을듯...

참고 글

[Github action IOS] CI/CD  Testflight 자동화 배포 하기

테스트플라이트 배포까지 여러 정보들을 필요로한다. Certification & Provisioning profile 암호화 파일 Certification & Provisioning profile 암호 비밀번호 Certification 비밀번호 ExportOptions.plist Appstore issuer id Appstor

dev-in-gym.tistory.com

펫프렌즈 iOS 플랫폼의 Github Actions & SPM 도입하기

Github Actions와 SPM을 도입했던 과정을 소개합니다 !

techblog.pet-friends.co.kr

[Git & Github] Github Action으로 TestFlight 업로드 해보기(3)

안녕하세요~ 오늘은 Github Action으로 TestFlight에 배포하는 과정의 마무리 포스팅이 될 것 같습니다 : ) [ 일단 지난 포스팅 - 사전 준비 ] [Git & Github] Github Action으로 TestFlight 업로드 해보기(2) 안녕하

pooh-footprints.tistory.com

[CI/CD] Github Actions 를 이용한 TestFlight 업로드 자동화

release branch로 push 하면 TestFlight 업로드하기

sujinnaljin.medium.com

[CI/CD] 아무것도 모르는 상태에서 Github Actions 를 이용한 TestFlight 업로드 자동화(삽질 회고)

Github Actions로 CI/CD를 구현하여 앱 빌드 & TestFlight 업로드 자동화를 만들어야 하는 일이 생겨 아무것도 모르는 상태에서 1주일 동안 삽질해가며 마침내 구현하였고 모르고 시작하는 분들의 어려움

phant0m.tistory.com