경험/이슈

Github Actions로 PR 테스트 자동화를 진행하면서 했던 삽질

호야_ 2023. 12. 2. 01:10
728x90

Gitub Actions를 통해 PR이 올라오면 자동으로 테스트를 돌려주는 기능을 적용하면서 삽질을 했던 것을 풀어보려고 한다. 아직 완벽하게 해결하지 못한 문제가 있어서 밤이 깊어가는데도 마음 한구석에 찝찝하고 아쉬움이 남았다. 이 과정에서 배운 것도 많고, 앞으로 더 개선할 부분에 대한 고민도 많이 하게 되었다..🤣

삽질을 해야 얻는 게 많아, 너무 잘 풀리면 배우는 게 없어 ~ (라고 세뇌 중....)
12/06 결국 해결완료🔥🔥

 

workflows에서 Actions이 작동이 안 한다!?

name: Java CI with Gradle

on:
  pull_request:
    branches: [ "main" ]
    
permissions:
  contents: read
  
jobs:
  test:
  
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    - name: 🍀 JDK 17 세팅
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
        
    - name: 🍀 gradlew 실행 권한 설정
      run: chmod +x gradlew

    - name: 🍀 테스트 진행
      run: ./gradlew --info test

가장 첫 ci.yml파일은 말랑님 블로그를 참고해서 우선 잘 모르고 세팅해서 부족한 부분이 아주 많다.

  1. applicatoin.yml 파일을. gitignore 했거나 환경변수로 대체해 놨을 텐데 어떻게 세팅하지?
  2. 그런데 왜 Actions가 작동을 안 하지? - (작동하면서 테스트를 실패해야 하는 게 아닌가?)
  3. 근데 이게 어디서 실행되지?

일단 2번은 이유조차 모르겠고 3번은 git에 runner라는 게 실행시켜 준다는 것 같아 우선 넘어가고 1번부터 해결하기로 했다.

 

1. PR 테스트 자동화에서 환경변수 다루기

Github에서 사용할 수 있는 Actions secrets와 submodule을 사용해서 구성했다.

 

왜 두 개 다 사용했지?
: submodule을 private로 해놓긴 했지만 혹시나 하는 불상사를 막기 위해
그렇지만 모든 것을 다 secrets에 넣은 건 아닌 진짜 중요한 key들이나 비밀번호만 넣었다.
 = 최대한 변경이 없는 것과 중요한 것만 secrets로 넣었기 때문에?! 한 번만 설정하면 그다음엔 건들지 않아도 된다.

 

 name: Java CI with Gradle

on:
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  test:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: 🍀 서브모듈 추가
        with:
          token: ${{ secrets.GIT_TOKEN }}
          submodules: true

      - uses: actions/checkout@v3
      - name: 🍀 JDK 17 세팅
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: 🍀 gradlew 실행 권한 설정
        run: chmod +x gradlew
  
      - name: 🍀 비밀 설정 파일 복사
        run: ./gradlew copySecret

      - name: 🍀 테스트 진행
        env:
          MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }}
          JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}
          DEV_DB_PASSWORD: ${{ secrets.DEV_DB_PASSWORD }}
          PROD_DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
        run: ./gradlew test

그렇게 탄생한 ci.yml 파일이다. 그렇지만 여기서 또 에러가 발생하는데...

Error: .github#L1
every step must define a `uses` or `run` key

계속 수정 수정하면서 yml파일이 조금 많이 살이 찐 것 같다.. 우선 name 옆에 -이 있고 uses를 두 번 쓴 곳도 있다.

➜ 워크플로에 문법오류가 있어서 실행자체가 안 된 것 같다. (잘 확인하길 바라며..)

 

최종 ci.yml 파일

이 전에 삽질을 좀 많이 하면서 workflows에 대한 이해가 조금조금씩 생겨 많이 깔끔해지고 기능도 추가했다.

name: Java CI with Gradle

on:
  pull_request:
    types: [ opened, reopened ]
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: 🍀 서브모듈 추가
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.GIT_TOKEN }}
          submodules: true

      - name: 🍀 JDK 17 세팅
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: 🍀 gradlew 실행 권한 설정
        run: chmod +x gradlew

      - name: 🍀 비밀 설정 파일 복사
        run: ./gradlew copySecret

      - name: 🍀 테스트 진행
        env:
          MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }}
          JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}
          DEV_DB_PASSWORD: ${{ secrets.DEV_DB_PASSWORD }}
          PROD_DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
        run: ./gradlew test

그런데 왜 Github Actions가 실행을 안 하지..?

 

2. Actions이 작동을 안 한다.

이것저것 찾아보고 계속되는 삽질과 테스트로 얻은 정보들이 있다.

  • 찐 main(즉 upstream main)에서 branch를 파고 PR을 올리면 Actions가 작동을 한다.  ☑️
  • 찐 main에서 Fork를 하고 나의 main에서 branch를 파서 PR을 올리면 Actions이 작동을 안 한다. 🚫

삽질..

더보기

삽질 중 Actions 테스트를 하려고 PR을 close 하고 reopen 하는 것을 반복했는데 나중엔

`types: [ opened, reopened, synchronize ]`를 추가해서 commit을 해도 Actions가 작동하도록 수정했다.

 

결국 Fork 한 곳에서 PR을 올려도 Actions가 작동하는 방법을 알아냈다!

 

"Setting > Actions > General > Run workflows from fork pull requests"를 체크해 주면 된다..!

(참고로 Repository에서 설정하면 저게 선택이 안 되는데 Repository 내에서 설정하는 게 아니라 Organization을 사용하는 경우는 Organization에서 설정해야 한다.)

 

이제 Actions가 작동은 하는데 바로 실패한다.

Error: Input required and not supplied: token

secrets에 등록해 놓은 GIT_TOKEN을 못 가져오는 것 같다.

혹시나 해서 설정한 Send secrets 그렇지만 실패 🚫

 

3. 실마리가 보인다!

그래서?

  • 여기서 근본적인 의문이 생겼다. Fork를 한 곳에서 PR을 요청했을 때 secrets를 못 가져오는 게 맞나? (그렇지만 나의 계정엔 ADMIN 권한이 있는데..?)
  • ADMIN 권한이 있는 계정에서도 secrets를 못 가져오는 게 맞나? - 근데 생각해 보면 보안상으로도 누구나 할 수 있는 Fork단에서도 Actions가 잘 작동하면 보안상으로 위협이 될 것 같긴 하다.
혹시나 이거에 대해 아시는 내용이 있다거나 저의 추측이 틀렸다면 댓글을 달아주세요. 🙏

 

우선은 곧 public으로 바꿀 Repository이고 오픈소스로 기여할 프로젝트는 아니기 때문에 Fork를 못하게 다시 막고 PR은 main에서 branch를 파고 하기로 했다.

 

마음이 불편해서 마지막까지 테스트

거듭된 실패...


12/03 새로운 시도!

혹시나 "포크 된 레포에서 시크릿을 설정하면 되지 않을까?"라는 추천을 받아서 해봤는데 안 된다..


12/06 또 새로운 시도, 드디어 성공?!

12/3에 했던 포크 된 레포에서 시크릿을 설정하는 게 반은 맞았던 것이었다..!

포크 된 레포에서 시크릿을 설정하고, " Run workflows from fork pull requests"도 체크해 주고! 마지막으로

30일!

관련 글을 찾아보다가 30일로 안 해서 삽질했다는 글을 봤던 기억이 있어서 30일로 설정한 토큰을 줬는데?! 성공했다..

(너무 여러 개를 건드려서 이게 확실한지는 정확하게 확신할 순 없지만 맞는 것 같다. 그 글을 찾고 싶었는데 너무 많은 방문기록 때문에 ㅠㅠ)

 

실패에 대한 workflows를 더 추가해서 다시 난관에 부딪혔다.

		...

      - name: 🍀 테스트 결과 Report
        uses: EnricoMi/publish-unit-test-result-action@v2
        if: always()
        with:
          files: 'build/test-results/**/*.xml'

      - name: 🍀 테스트 실패 Comment
        uses: mikepenz/action-junit-report@v3
        if: always()
        with:
          report_paths: 'build/test-results/test/TEST-*.xml'

      - name: 🍀 테스트 실패시 슬랙 알림
        if: failure()
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_CHANNEL: ${{ secrets.SLACK_GIT_ACTIONS_CHANNEL_NAME }}
          SLACK_COLOR: ${{ job.status }}
          SLACK_MESSAGE: '테스트 실패: ${{ github.repository }}'
          SLACK_TITLE: '럭키즈 서버 PR 테스트 실패 알림 🍀'
          SLACK_USERNAME: GitHub Actions
          SLACK_WEBHOOK: ${{ secrets.SLACK_GIT_ACTIONS_WEBHOOK_URL }}

우선 이렇게 테스트 실행 후에 3개의 flow들을 추가했다.

그리고 실행해 봤는데 아래와 같이 에러가 떴다. `테스트 결과 Report`를 실행할 때 쓰기 권한이 없는 것 같다.

Annotations
1 error and 1 warning

test
❌ Failed to create checks using the provided token. (HttpError: Resource not accessible by integration)
test
⚠️ This usually indicates insufficient permissions. More details: https://github.com/mikepenz/action-junit-report/issues/23

 

이거에 대한 해결방안은 우선 `checks: write`를 추가해 주고 "Send write tokens to workflows from fork pull requests."를 체크해 주는 것이다.

name: Java CI with Gradle

on:
  pull_request:
    types: [ opened, reopened, synchronize ]
    branches: [ "main" ]

permissions:
  contents: read
  checks: write	# 추가

jobs:
  test:
    runs-on: ubuntu-latest
    
    ...

 

 

물론 이렇게 권한을 많이 주면 줄수록 보안에 대한 걱정을 해야 한다. 특히 public으로 공개한다면 더욱더 신경을 써야 한다. fork를 지정된 인원만 되게 하거나 다른 제한을 둘 필요가 있다.

 

결론

최종 `ci.yml` 파일

name: Java CI with Gradle

on:
  pull_request:
    types: [ opened, reopened, synchronize ]
    branches: [ "main" ]

permissions:
  contents: read
  checks: write

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: 🍀 서브모듈 추가
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.GIT_TOKEN }}
          submodules: true

      - name: 🍀 JDK 17 세팅
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: 🍀 gradlew 실행 권한 설정
        run: chmod +x gradlew

      - name: 🍀 비밀 설정 파일 복사
        run: ./gradlew copySecret

      - name: 🍀 테스트 진행
        env:
          MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }}
          JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}
          DEV_DB_PASSWORD: ${{ secrets.DEV_DB_PASSWORD }}
          PROD_DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
        run: ./gradlew test

      - name: 🍀 테스트 결과 Report
        uses: EnricoMi/publish-unit-test-result-action@v2
        if: always()
        with:
          files: 'build/test-results/**/*.xml'

      - name: 🍀 테스트 실패 Comment
        uses: mikepenz/action-junit-report@v3
        if: always()
        with:
          report_paths: 'build/test-results/test/TEST-*.xml'

      - name: 🍀 테스트 실패시 슬랙 알림
        if: failure()
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_CHANNEL: ${{ secrets.SLACK_GIT_ACTIONS_CHANNEL_NAME }}
          SLACK_COLOR: ${{ job.status }}
          SLACK_MESSAGE: '테스트 실패: ${{ github.repository }}'
          SLACK_TITLE: '럭키즈 서버 PR 테스트 실패 알림 🍀'
          SLACK_USERNAME: GitHub Actions
          SLACK_WEBHOOK: ${{ secrets.SLACK_GIT_ACTIONS_WEBHOOK_URL }}
  • Fork 된 상태에서 CI 테스트 자동화를 하고 싶다면
    • "Setting > Actions > General > Run workflows from fork pull requests"부분 잘 체크하기
    • yml파일 권한 잘 확인하기 (문법 오류도 잘 확인하기)
    • 그렇지만 권한을 많이 늘렸기 때문에, 보안과 권한의 균형을 잘 고려하는 게 좋을 것 같다.

 

최종 성공 🤣

 

12/07 한 발 남았다.

Fork 된 메인에서 판 branch에서 올린 PR에서는 내가 의도한 대로 잘 작동했다. 그렇지만 이제 진짜 잘 될 것이라고 생각했는데 저렇게 오른쪽 이미지를 보면 테스트 결과 Report에서 에러가 난다.

// 주요 에러 내용

Request POST /repos/luck-kids/luck-kids-server/issues/61/comments failed with 403: Forbidden
2023-12-07 00:49:22 +0000 - github.GithubRetry -  INFO - Request POST /repos/luck-kids/luck-kids-server/issues/61/comments failed with 403: Forbidden

...

  File "/usr/local/lib/python3.8/site-packages/github/GithubRetry.py", line 179, in increment
    raise Requester.createException(response.status, response.headers, content)  # type: ignore
github.GithubException.GithubException: 403 {"message": "Resource not accessible by integration", "documentation_url": "https://docs.github.com/rest/issues/comments#create-an-issue-comment"}

권한에러가 난다. Fork 된 곳에서 권한을 이렇게 다 설정해서 다 해결했는데 왜 안 될까..... 추측으로는 아마 Fork 된 곳에서는 아예 코멘트를 쓸 접근조차 실행 안 한 것 같은데 찐 main에서는 코멘트를 작성하려다 보니 권한 문제가 발생한 것 같다. 이것저것 권한을 줘봤는데 에러가 계속 나서 코멘트가 꼭 필요한 것은 아니라 yml파일에서 삭제했다.

 

최종 `ci. yml` 파일

name: Java CI with Gradle

on:
  pull_request:
    types: [ opened, reopened, synchronize ]
    branches: [ "main" ]

permissions:
  contents: read
  checks: write

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: 🍀 서브모듈 추가
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.GIT_TOKEN }}
          submodules: true

      - name: 🍀 JDK 17 세팅
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: 🍀 gradlew 실행 권한 설정
        run: chmod +x gradlew

      - name: 🍀 비밀 설정 파일 복사
        run: ./gradlew copySecret

      - name: 🍀 테스트 진행
        env:
          MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }}
          JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}
          DEV_DB_PASSWORD: ${{ secrets.DEV_DB_PASSWORD }}
          PROD_DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
        run: ./gradlew test

      - name: 🍀 테스트 결과 Report
        uses: mikepenz/action-junit-report@v3
        if: always()
        with:
          report_paths: 'build/test-results/test/TEST-*.xml'

      - name: 🍀 테스트 실패시 슬랙 알림
        if: failure()
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_CHANNEL: ${{ secrets.SLACK_GIT_ACTIONS_CHANNEL_NAME }}
          SLACK_COLOR: ${{ job.status }}
          SLACK_MESSAGE: '테스트 실패: ${{ github.repository }}'
          SLACK_TITLE: '럭키즈 서버 PR 테스트 실패 알림 🍀'
          SLACK_WEBHOOK: ${{ secrets.SLACK_GIT_ACTIONS_WEBHOOK_URL }}
아 그리고 위 글의 토큰을 발행하는 부분에서 30일로 해야 할 것 같다는 부분이 있는데 그것도 기한 없는 것으로 수정해서 테스트해 봤는데 잘 되었다 ㅎㅎ (30일마다 refresh 안 해도 될 것 같다.)

 

 

 

언제나 잘못된 설명이나 부족한 부분에 대한 피드백은 환영입니다🤍

728x90