Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

[ci skip] 커밋 메시지로 GitHub Actions 실행 취소하기

CI를 사용하면 커밋이 올라올 때마다 CI를 실행하게 되는데 보통 시간도 몇 분 이상 걸리고 유료로 사용하면 과금이 되기 때문에 커밋을 올리면서 불필요한 CI 실행을 하지 않을 필요가 있다. 예를 들면 README 파일만 수정했다거나 소스 코드와 관련이 없는 부분의 수정이라서 굳이 CI를 실행해서 결과를 확인해 보지 않아도 되는 경우이다.

개발자가 직접 취소할 수도 있지만, 대부분의 CI에서는 커밋 메시지에 [ci skip][skip ci]를 추가하면 CI 실행을 하지 않고 있다. Travis CI도 이를 지원하고 CircleCI도 지원하고 있다.

일반화된 기능이라 GitHub Actions에도 있을 것 같지만 아직 이를 지원하지 않는다. 그래서 검색을 해보면 커뮤니티에서는 if: "! contains(toJSON(github.event.commits.*.msg), '[skip-ci]')" 같은 설정으로 이를 할 수 있다고 하고 있지만 실제로 사용해보면 이걸로는 다 되진 않는다.

얼마 전 이 부분이 필요해서 작업했는데 당시에는 기존의 마켓플레이스에 올라와 있던 Skip based on commit message가 더이상 유지 보수되고 있지 않아서 직접 적용했는데 글을 쓰면서 찾아보니 11월에 Skip Workflow, CI-SKIP-ACTION 2개의 액션이 올라와 있다. 이 2개의 코드를 다 열어보진 않았는데 아주 간단히 사용하려면 위 액션을 참고하면 좋을 것 같다.

GitHub Actions에서 [ci skip]

다음과 같은 간한 CI 설정이 있다고 해보자. 여기서는 test라는 메인 job 1개만 있다.

name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: 14.x
    - run: npm test

skip 기능을 검색해 보면 if를 사용해서 커밋 검사하는 방법을 다들 대답하고 있는데 제대로 안 해본 게 틀림없다. if 구문은 jobs.<job_id>.if에서 쓸 수 있는데 해당 조건이 참이면 잡을 실행 한다. 여기서 사용하는 표현식은 GitHub 문서를 참고하면 된다.

jobs:
  test:
    runs-on: ubuntu-latest
    if: ${{ !contains(github.event.head_commit.message, '[ci skip]') }}
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: 14.x
    - run: npm test

if 부분을 추가했다. Git은 여러 커밋을 한꺼번에 올리는 경우가 많은데 보통 마지막 커밋에 대해서만 CI를 실행하므로 헤드 커밋 메시지(github.event.head_commit.message)에 [ci skip] 문자열이 포함되어 있는지 검사해서 포함되어 있지 않으면 test 잡을 실행 한다. 만약 [skip ci]도 지원하고 싶다면 다음과 같이 조건을 하나 더 붙여주면 된다.

if: >-
  ${{ !contains(github.event.head_commit.message, '[ci skip]') &&
  !contains(github.event.head_commit.message, '[skip ci]')
  }}

GitHub Action이 취소됨

[ci skip] 메시지가 있는 커밋을 올리면 해당 잡이 실행 안 된 것을 확인할 수 있다.

리스트에서 취소된 잡이 취소표시됨

잡이 1개뿐이라 워크플로우 자체가 실행 안되었기 때문에 리스트에서도 취소된 것을 볼 수 있다.

Pull Request에서 [ci skip]

같은 방식으로 [ci skip]이 메시지에 포함된 커밋을 Pull Request로 올려보자.

Push에서는 잘 skip되지만 Pull Request에서는 실행됨

테스트한 저장소가 포크한 저장소가 아니라 브랜치도 푸시 되었기 때문에 push 이벤트와 pull_request 이벤트 모두에 실행되었다. 위 결과를 보면 push 이벤트에서는 제대로 실행을 건너뛰었지만 pull_request 이벤트에서는 그냥 실행되었다.

debug라는 잡은 일부러 github.event.head_commit.message를 출력해보려고 추가한 것인데 우측을 보면 echo로 아무것도 출력되지 않은 걸 볼 수 있다. GitHub에서 push 이벤트의 페이로드pull_request 이벤트의 페이로드가 완전히 다르기 때문이다. pull_request 이벤트에서는 head_commit을 주지 않아서 커밋 메시지를 알아낼 수가 없었다.

pull_request 정보를 가지고 API로 커밋 내용을 가져오거나 코드를 체크아웃해서 커밋메시지를 확인해야 했다. [ci skip] 기능이 Pull Request에서도 필요했기 때문에 나는 코드를 체크아웃해서 가져오는 방법을 이용했다.

Pull Request에서 [ci skip]을 확인하려다 보니 꽤 복잡해졌다. 앞에서 다른 사람들이 만든 액션이 있어서 그걸 써도 되지만 여기서 GitHub Actions 사용 방법을 몇 가지 볼 수 있다.

jobs:
  prepare-commit-msg:
    name: Retrive head commit message
    runs-on: ubuntu-latest
    outputs:
      HEAD_COMMIT_MSG: ${{ steps.commitMsg.outputs.HEAD_COMMIT_MSG }}
    steps:
      - uses: actions/checkout@v1
        if: github.event_name == 'pull_request'
      - name: find commit msg for PR
        id: commitMsg
        if: github.event_name == 'pull_request'
        run: echo "::set-output name=HEAD_COMMIT_MSG::$(git log --no-merges -1 --oneline)"
  check-skip:
    name: Check to skip CI
    needs: prepare-commit-msg
    runs-on: ubuntu-latest
    if: ${{ !contains(github.event.head_commit.message, '[ci skip]') && !contains(needs.prepare-commit-msg.outputs.HEAD_COMMIT_MSG, '[ci skip]') }}
    steps:
      - run: echo "${{ github.event.head_commit.message }}"
  test:
    runs-on: ubuntu-latest
    needs: check-skip
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: 14.x
    - run: npm test
  • prepare-commit-msgcheck-skip 2개의 잡을 추가 했다.
  • 3개의 잡을 needs로 연결했다. needs로 다른 잡을 지정 하면 해당 잡이 성공해야만 다음 잡이 실행된다.
  • prepare-commit-msgpull_request인 경우 커밋 메시지를 찾는 잡이다.

    • 모든 스텝에서 if: github.event_name == 'pull_request'를 추가해서 다른 이벤트에서는 실행되지 않도록 했다. if를 잡에 걸지 않은 이유는 이 잡이 취소되면 이어진 잡이 실행되지 않기 때문이다.
    • echo "::set-output name=HEAD_COMMIT_MSG::$(git log --no-merges -1 --oneline)"::set-output 커맨드를 이용해서 헤드 커밋의 메시지를 HEAD_COMMIT_MSG에 저장한 것이다. 이렇게 내보내면 같은 잡에서 이 값을 참조해서 사용할 수 있다.
    • 잡에 outputs:를 따로 지정했다. 앞에서 set-output을 지정했지만, 이 출력값은 다른 잡에서는 참조를 할 수 없다. 그래서 steps.commitMsg.outputs.HEAD_COMMIT_MSG를 가져와서 잡의 출력값으로 다시 지정한 것이다.
  • check-skip 잡은 스킵할 지 여부를 정하는 잡이다.

    • if 구문이 더 복잡해졌는데 !contains(github.event.head_commit.message, '[ci skip]')는 앞에서 설명한 대로 push 이벤트일 때 바로 여기서 스킵 여부를 결정할 수 있다.
    • pull_request 이벤트일 때 뒤 조건인 !contains(needs.prepare-commit-msg.outputs.HEAD_COMMIT_MSG, '[ci skip]')에서 판단하게 된다. needs로 이전 잡을 연결해 왔기 때문에 출력값을 needs.prepare-commit-msg.outputs.HEAD_COMMIT_MSG로 가져올 수 있다. 앞에서 Pull Request의 헤드 커밋 메시지를 찾아놨기 때문에 여기서는 검사만 한다.
    • 이 잡은 실행여부 검사만 하기 때문에 스텝에서는 헤드 커밋을 echo로 출력하기만 했다. test잡에서 needs: check-skip로 지정했으므로 이 잡이 실행되어야 전체를 실행할 수 있다.

Push와 Pull Request에서 모두 잘 skip 됨

다시 올린 Pull Reqeust를 보면 push, pull_request 둘 다 실행이 잘 취소된 것을 볼 수 있다. Retrive head commit message 잡이 실행되는 것은 별수 없다.

2020/11/22 19:50 2020/11/22 19:50

기술 뉴스 #162 : 20-11-15

웹개발 관련

  • Clickjacking Attacks and How to Prevent Them : Clickjacking 공격은 사용자를 UI로 속여서 사용자가 클릭 혹은 다른 동작을 했지만 실제로는 상품 구매나 좋아요 클릭 등 다른 동작이 이뤄지도록 하는 공격 방법으로 Likejacking, Cookiejacking, Filejacking, Password manager attacks 등 다양한 변형이 있다. Clickjacking 공격 예제를 제공하는 데 이를 보면 유효한 사이트를 iframe으로 띄워서 투명하게 만들 뒤 사용자가 클릭할 버튼과 겹치게 만들어서 실제로 다른 버튼을 공격하게 만든다. 이는 CSRF 공격과 비슷해 보이지만 CSRF와 달리 공격자가 따로 요청을 보내는 것은 존재하지 않는다는 점이 다르다. 이를 막으려면 X-Frame-Options: sameoriginX-Frame-Options: deny 헤더를 지정해서 Content-Security-Policy: frame-ancestors 'self' 헤더를 지정해서 iframe내에서 사이트가 못 뜨도록 할 수 있고 최신 브라우저에서만 지원하는 sameSitestrict로 쿠키에 지정해서도 막을 수 있다.(영어)
  • Why not use GraphQL? : Apollo에서 올린 Why use GraphQL?에 대한 응답으로 올린 글인데 이글도 GraphQL을 지지하는 입장의 글이다. 사실 두 글 모두 GraphQL 관련 업체이기 때문에 글에 자사의 입장이 어느 정도는 들어가 있지만 GraphQL과 REST의 특징을 이해하는데 도움 될 부분이 있다.

    • Apollo 글에서 얘기한 데이터를 불필요하게 많이 가져오고 중첩 데이터의 워터폴 네트워크 문제는 BFF(backend for frontend)를 만들면 대부분 해결가능하고 REST에는 304 응답을 이용한 캐시도 있어서 오히려 더 효율적일 수 있다고 한다.
    • Apollo 글에서 REST는 너무 많은 버전이 생길 수 있지만 GraphQL은 버전이 하나밖에 없다고 한 부분은 REST도 너무 많은 버전이 생긴다면 조직의 문제가 아닌지 봐야 하고 GraphQL의 스키마 추적 기능도 Apollo의 유료 기능이지 GraphQL에서 제공하는 기능이 아니기 때문에 사실상 버전 관리를 안 하면 똑같고 어차피 브레이킹 체인지가 발생하면 안 되고 호환되도록 업데이트를 해야 하는 게 양쪽 다 마찬가지라고 얘기하고 있다.
    • GraphQL이 엄격한 타입으로 안정적이라고 한 것도 REST에서 Open API 명세 같은 API 문서를 제공하지 않는 것과 비교하는 것을 말이 되지 않고 GraphQL도 응답은 자유롭게 할 수 있기 때문에 클라이언트가 명세대로 올 것이라고 믿어야 하는 건데 이는 API 문서와 사실 같은 상황이라고 하고 한다.
    • 문서 작성에 드는 시간이 적고 API 찾기 쉽다고 한 부분은 비교를 문서 파일로 비교해서 그렇고 Open API를 쓰면 보통 개발자 포털에 배포해서 사용하기 때문에 검색이 쉬운 데다가 Open API는 여러 서비스 간에도 참조를 할 수 있고 빌드를 한꺼번에 하는 것도 가능하다. 그리고 Open API나 GraphQL 스키마가 있다고 문서화가 잘 되었다고 할 수는 없고 인증을 어떻게 해야 하는지, 좋은 방법은 무엇인지 등 다양한 설명이 있어야 처음 사용할 때 편한데 Open API에는 이러한 기능을 많이 지원하는 데 반해 오히려 GraphQL 도구들에는 이런 기능이 보통 빠져있다고 한다.
  • 싱페어 (SPA) 의 피로감 : React를 이용한 SPA(Single Page Application)는 개발자의 생산성은 올려주었지만 서버 사이드 렌더링(SSR)이 쉽지 않고 느린 데다가 상태 주입이 어렵기 때문에 그 미래가 밝지 않다고 설명하는 글이다.(한국어)
  • ES Modules in Depth : ES Modules의 사용 방법을 설명하는 글이다. export default의 사용법과 다양하게 export하는 방법을 설명하고 import 할 때도 전체로 가져오거나 일부만 가져오는 방법을 설명한다. 앞부분에는 정적으로 import하는 방법을 설명하고 뒤에는 동적 import라고 부르는 비동기로 가져오는 방법을 설명한다.(영어)
  • Goodbye Google Webmasters, hello Google Search Central : 웹 마스터란 용어가 너무 오래되었고 요즘은 잘 사용하지 않는 용어라서 Google Webmasters Central을 Google Search Central로 변경했다. 이 사이트에서는 웹사이트가 잘 검색되도록 정보를 제공한다.(영어)

그 밖의 개발 관련

  • How to Boost Docker with WSL2 : Windows에서 Docker를 개발환경으로 보통 사용하는 대부분의 환경에서는 큰 문제가 없지만, 종종 성능 이슈가 생기는 경우가 있다. 이는 Windows가 Linux를 지원하지 않음으로 Docker Desktop for windows가 Hypervisor를 이용해서 VM을 띄우고 파일을 주고받기 때문에 이 부분에서 성능 저하가 일어난다. WSL2를 이용하면 Linux 파일 시스템을 VM 없이 바로 이용할 수 있기 때문에 이런 성능 저하가 일어나지 않지만 두 파일 시스템 간의 통신으로 성능이 저하될 수 있다. 이를 위해 WSL2를 설치한 뒤에 Ubuntu를 설치하고 Docker에서 설정으로 Ubuntu를 활성화한 뒤에 VS Code에서 "Remote WSL"을 이용하면 작업은 윈도우에서 하면서 코드 수정은 Linux에서 일어나도록 할 수 있다고 한다.(영어)
  • How we open sourced docs.github.com : 최근 GitHub이 문서 사이트를 오픈소스로 공개했는데 이 과정을 설명한 글이다. 문서 사이트는 처음에는 Ruby on Rails로 만들어졌지만 이를 정적 사이트 생성기인 Jekyll, 그다음엔 또 다른 정적 사이트 생성기인 Nanoc를 이용했고 지금은 Node.js 웹서비스로 만들어졌다. 이를 오픈소스화하더라도 내부에서 해야 할 작업이 필요했고 공개 저장소와 비공개 저장소를 동기화하기 위해 비슷한 Actions를 만든 사람과 협업해서 Repo Sync 액션을 만들어서 공개했고 Liquid 템플릿을 이미 많이 쓰고 있었지만 적당한 npm 모듈이 없어서 많은 컨트리뷰터들과 liquid 모듈을 만들어 공개했다고 한다.(영어)
  • 유연하고 테스트 가능한 Go 코드 작성하기 : 당근마켓의 플랫폼 팀이 Go로 서비스를 만들면서 Go의 인터페이스가 메서드의 집합이라는 부분을 활용하여 인터페이스로 클라이언트의 의존성 없이 모킹해서 작성한 로직의 테스트 코드를 작성하는 방법을 보여준다.(한국어)
  • 코드에서 암호를 안전하게 사용할 방법을 찾아서… : 코드에 하드 코딩해서 넣기 어려운 데이터베이스 비밀번호 같은 비밀 정보를 관리하기 위해 S3에 올리거나 환경 변수로 관리하는 시도를 해보다가 AWS의 Parameter Store를 사용해 봤으나 주기적인 암호 변경이 필요해서 AWS Secret Manager로 넘어가서 관리하게 되는 과정과 각 단계의 장단점을 적어놨다. Secret Manager는 Parameter Store와 사용법은 비슷하지만, 주기적으로 암호가 변경되도록 설정할 수 있다.(한국어)
  • 복잡한 커밋 로그를 정리해줄 구원자, gitmoji : Atom 프로젝트 등에서 사용하던 커밋 앞에 커밋의 종류를 구분하기 위한 이모지를 붙이는 방법을 설명한 글이다. 커밋 앞에 docs, feat같은 식으로 붙이기도 하는데 이 대신 약속한 이모지를 붙이는 것이고 이를 통해 커밋 히스토리를 보면 쉽게 구분이 가능하다는 장점이 있지만, 규칙을 모르면 이모지만으로는 알 수 없다는 문제가 있다.(한국어)
  • Docker fails to launch on Apple Silicon : Apple에서 Apple Silicon 기반 맥이 새로 나왔는데 아직 Docker for mac이 동작하지 않는다고 한다.(영어)
  • The new pricing model for travis-ci.com : travis-ci.com이 11월 2일부터 가격정책을 변경했다. Linux와 Windows를 사용하는 사용자는 변경이 없고 macOS는 크레딧을 구매해서 사용해야 하고 어뷰저가 많아져서 무료체험을 Linux 1,000분의 빌드 시간을 제공하는 것으로 바꾸었다. 오픈소스 프로젝트도 1,000분의 빌드 시간을 제공하고 추가로 필요하면 별도로 신청해야 한다.(영어)

인프라 관련

볼만한 링크

  • 일 잘 하는 개발자는 왜 비즈니스까지 신경쓸까? : 개발자가 기술적으로 좋은 설계에 관해 관심을 많이 가지고 이게 주 역할이지만 고용된 사람으로서 개발자의 시간도 비용이 들기 때문에 어느 정도 노력을 들일 건인지 비즈니스의 관점에서 적절하게 판단할 수 있어야 한다는 글이다. 개발자에 다양한 타입이 있고 처한 상황도 다양하겠지만 이 글에서 주장하는 비즈니스까지도 같이 고려해야 한다는 점에 동의하는 편이다.(한국어)
  • 인턴쉽 : 탈중앙 온라인 게임을 만드는 플라네타리움의 인턴으로 합류해서 게임 론처 부분에 기여한 경험을 적은 글이다. 인턴쉽에 지원하면서 자신의 관심 있는 영역과 회사의 상황을 묻고 피드백 받는 부분이 흥미롭다. 인턴쉽을 하면서 팀을 위해서 테스트를 자동화하고 론처의 UX를 개선하고 접근성 작업을 하다가 팀원과의 협의를 통해 중단하는 등 인턴쉽을 알차게 보내셨다는 생각이 든다.(한국어)

IT 업계 뉴스

프로젝트

  • ffmpeg.wasm : FFmpeg을 WebAssembly와 JavaScript로 포팅한 프로젝트로 브라우저에서 비디오/오디오를 녹화하고 변환할 수 있다.
  • Git User Switch : Git 사용자와 서명키를 쉽게 바꿔서 사용할 수 있는 CLI 도구.
  • gping : 그래프로 ping 결과를 볼 수 있는 CLI 도구.
  • Security Scorecards : Open Source Security Foundation에서 오픈소스 프로젝트의 보안상태를 점검해주는 도구를 공개했고 주요 오픈소스를 점검한 결과를 지속해서 공개하고 있다.
  • Krew : kubectl의 플러그인 매니저.

버전 업데이트

2020/11/15 21:50 2020/11/15 21:50