패키지를 pub.dev에 자동으로 게시하기
패키지를 pub.dev에 자동으로 게시하기
패키지의 새 버전을 낼 때마다 손으로 dart pub publish를 눌러야 한다면 조금 번거롭죠. git 태그를 푸시하는 것만으로 자동으로 pub.dev에 게시되게 만들 수 있어요. 이 글에서 GitHub Actions를 중심으로 자동 게시를 설정하는 방법을 살펴볼게요.
본문
여러분은 다음 환경에서 자동 게시를 구성할 수 있어요.
- GitHub Actions,
- Google Cloud Build, 또는
- GCP 서비스 계정을 이용한 그 외 어디에서든
아래 섹션에서 자동 게시가 어떻게 구성되는지, 그리고 여러분의 선호에 맞게 게시 흐름을 어떻게 커스터마이즈할 수 있는지 설명할게요.
자동 게시를 구성할 때는 배포 환경에 복사되는 오래 지속되는(long-lived) 시크릿을 만들 필요가 없어요. 대신 인증은 GitHub Actions가 서명한 임시 OpenID-Connect 토큰(GitHub Actions용 OIDC 참고) 또는 Google Cloud IAM이 서명한 토큰에 의존해요. 신원(identity) 서비스가 없는 배포 환경에서는 내보낸 서비스 계정 키를 사용할 수 있어요. 다만 이렇게 내보낸 서비스 계정 키는 오래 지속되는 시크릿이라 어떤 환경에서는 쓰기 쉬울 수 있지만, 실수로 유출되면 더 큰 위험이 될 수도 있어요.
GitHub Actions로 패키지 게시하기
GitHub Actions를 사용한 자동 게시를 구성할 수 있어요. 여기에는 다음이 포함돼요.
- pub.dev에서 자동 게시를 활성화하고, GitHub 저장소와 게시를 허용할 **태그 패턴(tag-pattern)**을 지정하기.
- pub.dev에 게시하기 위한 GitHub Actions 워크플로우 만들기.
- 게시할 버전의 git 태그 푸시하기.
아래 섹션에서 이 단계들을 하나씩 짚어 볼게요.
pub.dev에서 GitHub Actions 자동 게시 구성하기
GitHub Actions에서 pub.dev로 자동 게시를 활성화하려면 다음 중 하나여야 해요.
- 패키지의 업로더(uploader), 또는
- 퍼블리셔(publisher)의 관리자 (패키지가 퍼블리셔가 소유한 경우).
충분한 권한이 있다면 다음 방법으로 자동 게시를 활성화할 수 있어요.
- Admin 탭(
pub.dev/packages/<package>/admin)으로 이동하기. - Automated publishing 섹션을 찾기.
- Enable publishing from GitHub Actions를 클릭하면 다음을 지정하라는 메시지가 나와요.
- 저장소(
<organization>/<repository>, 예:dart-lang/pana) - 태그 패턴 (
{{version}}을 포함하는 문자열)
- 저장소(
저장소는 GitHub의 <organization>/<repository>예요. 예를 들어 저장소가 https://github.com/dart-lang/pana라면 repository 필드에 dart-lang/pana를 지정해야 해요.
태그 패턴은 반드시 {{version}}을 포함해야 하는 문자열이에요. 이 태그 패턴과 일치하는 태그 푸시로 트리거된 GitHub Actions만 여러분의 패키지를 게시할 수 있어요. 예를 들어 v{{version}} 같은 태그 패턴은 [git tag v1.2.3 && git push v1.2.3로 트리거된 GitHub Actions]가 패키지의 1.2.3 버전을 게시하도록 허용해요. 따라서 pubspec.yaml의 version 키가 이 버전 번호와 일치하는 것도 중요해요.
저장소에 여러 개의 패키지가 있다면 각각에 별도의 태그 패턴을 주세요. my_package_name이라는 패키지라면 my_package_name-v{{version}} 같은 태그 패턴을 고려해 볼 만해요.
pub.dev에 게시하는 GitHub Action 워크플로우 구성하기
pub.dev에서 GitHub Actions 자동 게시가 활성화되면, 게시용 GitHub Actions 워크플로우를 만들 수 있어요. .github/workflows/publish.yml 파일을 다음과 같이 만들면 돼요.
# .github/workflows/publish.yml
name: Publish to pub.dev
on:
push:
tags:
# pub.dev에 구성된 태그 패턴과 맞아야 해요. 보통 {{version}}을
# [0-9]+.[0-9]+.[0-9]+ 로 바꾸면 돼요.
- 'v[0-9]+.[0-9]+.[0-9]+' # pub.dev 태그 패턴: 'v{{version}}'
# '1.2.3'처럼 'v' 접두사가 없는 태그를 선호한다면:
# - '[0-9]+.[0-9]+.[0-9]+' # pub.dev 태그 패턴: '{{version}}'
# 여러 패키지를 담고 있다면 다음과 같은 패턴을 고려해 보세요:
# - 'my_package_name-v[0-9]+.[0-9]+.[0-9]+'
# dart-lang의 재사용 가능한 워크플로우로 게시합니다.
jobs:
publish:
permissions:
id-token: write # OIDC를 사용한 인증에 필요합니다
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
# with:
# working-directory: path/to/package/within/repository
on.push.tags의 패턴을 pub.dev에 지정한 태그 패턴과 맞춰야 해요. 그렇지 않으면 GitHub Action 워크플로우가 동작하지 않아요. 같은 저장소에서 여러 패키지를 게시한다면 my_package_name-v{{version}} 같은 패키지별 태그 패턴을 사용하고, 패키지마다 별도의 워크플로우 파일을 만들어야 해요.
위 워크플로우 파일은 dart-lang/setup-dart/.github/workflows/publish.yml을 사용해 패키지를 게시해요. 이 재사용 가능한 워크플로우는 Dart 팀이 게시 로직을 유지 관리할 수 있게 해 주고, pub.dev가 패키지가 어떻게 게시됐는지 알 수 있게 해 줘요. 이 재사용 가능한 워크플로우를 사용하는 것이 강력히 권장돼요.
패키지에 생성된 코드가 필요하다면, 그 코드를 저장소에 커밋해 두는 편이 좋아요. 그러면 pub.dev에 게시된 파일이 저장소의 파일과 일치하는지 검증하기가 쉬워지죠. 생성되거나 빌드된 아티팩트를 저장소에 커밋하는 게 합리적이지 않다면, 다음과 같은 맞춤 워크플로우를 만들 수도 있어요.
# .github/workflows/publish.yml
name: Publish to pub.dev
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+' # pub.dev 태그 패턴: 'v{{version}}'
# 커스텀 워크플로우로 게시
jobs:
publish:
permissions:
id-token: write # OIDC를 사용한 인증에 필요합니다
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dart-lang/setup-dart@v1
- name: Install dependencies
run: dart pub get
# 여기에 필요한 커스텀 단계를 넣을 수 있어요
# - run: dart tool/generate-code.dart
- name: Publish
run: dart pub publish --force
이 워크플로우는 임시 GitHub 서명 OIDC 토큰을 사용해 pub.dev에 인증해요. 이 토큰은 dart-lang/setup-dart 단계에서 생성되고 구성돼요. 이후 단계에서는 dart pub publish --force를 실행해 pub.dev에 게시할 수 있어요.
GitHub Actions에서 자동 게시 트리거하기
pub.dev에서 자동 게시를 구성하고 GitHub Actions 워크플로우를 만든 뒤에는, 패키지의 새 버전을 게시할 수 있어요. 게시하려면 구성된 태그 패턴과 일치하는 git 태그를 푸시하면 돼요.
$ cat pubspec.yaml
package: my_package_name
version: 1.2.3 # git 태그에 쓰인 버전 번호와 일치해야 합니다
environment:
sdk: ^2.19.0
$ git tag v1.2.3 # 태그 패턴이 'v{{version}}'이라고 가정합니다
$ git push origin v1.2.3 # 내 패키지를 게시하는 액션을 트리거합니다
푸시한 뒤에는 https://github.com/<organization>/<repository>/actions에서 워크플로우 로그를 확인해 보세요. 액션이 트리거되지 않았다면 .github/workflows/publish.yml에 설정된 패턴이 푸시한 git 태그와 일치하는지 확인해 보세요. 액션이 실패했다면 로그에 실패 이유에 대한 단서가 있을 거예요.
게시가 완료되면 pub.dev의 감사 로그(audit-log)에서 게시 이벤트를 확인할 수 있어요. 감사 로그 항목에는 해당 패키지 버전을 게시한 GitHub Action 실행으로 가는 링크가 포함돼 있어야 해요.
git CLI로 태그를 만들고 싶지 않다면 https://github.com/<organization>/<repository>/releases/new에서 GitHub 릴리스를 직접 만들 수도 있어요. 더 자세한 내용은 GitHub의 저장소에서 릴리스 관리 문서를 참고해요.
GitHub의 태그 보호 규칙으로 보안 강화하기
GitHub Actions에서 자동 게시를 구성하면, 저장소에 태그를 푸시할 수 있는 사람이라면 누구나 pub.dev에 대한 게시를 트리거할 수 있어요. GitHub의 태그 보호 규칙을 사용해 누가 저장소에 태그를 푸시할 수 있는지 제한할 수 있어요. 태그 패턴과 일치하는 태그를 만들 수 있는 사람을 제한함으로써, 누가 패키지를 게시할 수 있는지를 제한할 수 있죠.
다만 현재 태그 보호 규칙은 유연성이 부족해요. 다음 섹션에서 설명하는 것처럼 GitHub Deployment Environments(배포 환경)를 사용해 게시 트리거를 제한하는 편을 원할 수도 있어요.
GitHub Deployment Environments로 보안 강화하기
pub.dev에서 GitHub Actions 자동 게시를 구성할 때, GitHub Actions 환경(environment)을 요구하도록 지정할 수 있어요. 게시에 GitHub Actions 환경을 요구하려면 다음 단계를 거쳐야 해요.
- Admin 탭(
pub.dev/packages/<package>/admin)으로 이동하기. - Automated publishing 섹션 찾기.
- Require GitHub Actions environment 클릭하기.
- Environment name 지정하기 (보통
pub.dev가 좋은 이름이에요).
pub.dev에서 환경이 요구되면 GitHub Actions는 environment: pub.dev를 가지지 않는 한 게시할 수 없어요. 따라서 다음을 해야 해요.
- GitHub에 같은 이름의 환경(보통
pub.dev)을 만들기. .github/workflows/publish.yml워크플로우 파일을 수정해서environment: pub.dev를 지정하기:
# .github/workflows/publish.yml
name: Publish to pub.dev
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+' # 'v1.2.3' 같은 태그용
jobs:
publish:
permissions:
id-token: write # OIDC를 사용한 인증에 필요합니다.
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
with:
# GitHub Action 배포 환경을 지정합니다.
environment: pub.dev
# working-directory: path/to/package/within/repository
환경은 pub.dev와의 인증에 사용되는 임시 GitHub 서명 OIDC 토큰에 반영돼요. 따라서 저장소에 푸시할 권한이 있는 사용자라도 워크플로우 파일을 수정해서 환경 보호 규칙을 우회할 수는 없어요.
GitHub 저장소 설정에서 환경 보호 규칙으로 필수 리뷰어(required reviewers)를 구성할 수도 있어요. 이 옵션을 구성하면, 필수 리뷰어 중 한 명이 실행을 승인하기 전까지 GitHub가 해당 환경의 액션 실행을 막아요.
Google Cloud Build에서 게시하기
Google Cloud Build에서 자동 게시를 구성할 수 있어요. 여기에는 다음 단계가 포함돼요.
- Google Cloud 프로젝트 등록하기 (또는 기존 프로젝트 사용).
- pub.dev에 게시하기 위한 서비스 계정 만들기.
- pub.dev에서 패키지의 관리 탭에서 자동 게시를 활성화하고, 게시용으로 만든 서비스 계정의 이메일을 지정하기.
- 기본 Cloud Build 서비스 계정에 게시용으로 만든 서비스 계정을 가장(impersonate)할 권한 부여하기.
- 임시 OIDC
id_token을 얻어 pub.dev에 게시하는 데 사용하는cloudbuild.yaml파일 만들기. - Google Cloud Build의 프로젝트에서
cloudbuild.yaml의 단계를 실행하기 위한 Cloud Build 트리거 구성하기.
아래 섹션에서 이 단계들을 어떻게 완료하는지 설명할게요.
게시용 서비스 계정 만들기
pub.dev에 게시하기 위해, pub.dev에서 패키지를 게시할 권한을 부여받은 서비스 계정을 만들 거예요. 그런 다음 Cloud Build에 이 서비스 계정을 가장할 권한을 부여할 거예요.
클라우드 프로젝트가 없으면 먼저 만드세요. 그런 다음 서비스 계정을 다음과 같이 만들어요.
$ gcloud iam service-accounts create pub-dev \
--description='Service account to be impersonated when publishing to pub.dev' \
--display-name='pub-dev'
이렇게 하면 pub-dev@$PROJECT_ID.iam.gserviceaccount.com이라는 이름의 서비스 계정이 생성돼요.
이제 이 서비스 계정에 패키지를 게시할 권한을 부여해요. 이 단계를 완료하려면 패키지에 대한 업로더 권한이 있거나, 패키지를 소유한 퍼블리셔의 관리자여야 해요.
- Admin 탭(
pub.dev/packages/<package>/admin)으로 이동하기. - Enable publishing with Google Cloud Service account 클릭하기.
- Service account email 필드에 서비스 계정의 이메일을 입력하기.
이 계정은 이전 단계에서 만들었어요: pub-dev@$PROJECT_ID.iam.gserviceaccount.com입니다.
이 절차가 끝나면 서비스 계정을 가장할 수 있는 사람은 누구나 패키지의 새 버전을 게시할 수 있어요. 누가 서비스 계정을 가장할 권한을 갖는지 반드시 검토하고, 필요하면 클라우드 프로젝트에서 권한을 변경해야 해요.
Cloud Build에 게시 권한 부여하기
Cloud Build에서 게시하려면 기본 Cloud Build 서비스 계정에 이전 섹션에서 게시용으로 만든 서비스 계정을 가장할 권한을 줘야 해요.
-
클라우드 프로젝트에서 IAM Service Account Credentials API를 활성화하기. 이 API가 없으면 서비스 계정 가장 시도가 실패해요.
# IAM Service Account Credentials API 활성화 $ gcloud services enable iamcredentials.googleapis.com -
프로젝트 번호 찾기.
# PROJECT_NUMBER는 다음과 같이 얻을 수 있어요: $ gcloud projects describe $PROJECT_ID --format='value(projectNumber)' -
게시 서비스 계정을 가장할 권한 부여하기.
# 기본 cloud 서비스 계정에 권한 부여 $ gcloud iam service-accounts add-iam-policy-binding \ pub-dev@$PROJECT_ID.iam.gserviceaccount.com \ --member=serviceAccount:[email protected] \ --role=roles/iam.serviceAccountTokenCreator
Cloud Build 구성 파일 작성하기
Cloud Build에서 게시하려면 Cloud Build가 다음을 수행하도록 단계를 지정해야 해요.
- 서비스 계정을 가장해서 임시 OIDC 토큰을 얻기.
- 임시 OIDC 토큰을 게시 시 사용하도록
dart pub에 제공하기. dart pub publish를 호출해서 패키지 게시하기.
Google Cloud Build의 단계는 cloudbuild.yaml 파일에 제공되는데, 형식의 전체 문서는 빌드 구성 파일 스키마를 참고해요. Google Cloud Build에서 pub.dev로 게시하려면 다음과 같은 cloudbuild.yaml 파일이면 충분해요.
# cloudbuild.yaml
steps:
- id: Create temporary token
name: gcr.io/cloud-builders/gcloud
volumes:
- name: temporary-secrets
path: /secrets
script: |
gcloud auth print-identity-token \
--impersonate-service-account=pub-dev@$PROJECT_ID.iam.gserviceaccount.com \
--audiences=https://pub.dev \
--include-email > /secrets/temporary-pub-token.txt
env:
- PROJECT_ID=$PROJECT_ID
- id: Publish to pub.dev
name: dart
volumes:
- name: temporary-secrets
path: /secrets
script: |
cat /secrets/temporary-pub-token.txt | dart pub token add https://pub.dev
dart pub publish --force
gcloud auth print-identity-token은 지정한 서비스 계정을 가장해서 OIDC id_token을 만들어요. 이 id_token은 Google이 서명하며, 서명은 1시간 이내에 만료돼요. audiences 파라미터는 pub.dev가 토큰의 의도된 수신자임을 알게 해 주고, --include-email 옵션은 pub.dev가 서비스 계정을 인식하는 데 필요해요.
id_token이 만들어지면 볼륨의 파일에 기록되는데, 이 메커니즘을 사용해 단계 사이에 데이터를 전달해요. 토큰을 /workspace에 저장하지 마세요. /workspace는 게시하려는 저장소가 체크아웃되는 위치이기 때문이에요. 토큰 저장에 /workspace를 쓰지 않으면, 게시할 때 실수로 토큰이 패키지에 포함될 위험을 줄일 수 있어요.
Cloud Build 트리거 만들기
서비스 계정을 구성하고 저장소에 cloudbuild.yaml 파일을 두었다면, console.cloud.google.com 대시보드를 사용해 Cloud Build 트리거를 만들 수 있어요. 빌드 트리거를 만들려면 소스 저장소에 연결하고 어떤 이벤트가 빌드를 트리거할지 지정해야 해요. GitHub, Cloud Source Repository, 또는 다른 옵션을 사용할 수 있어요. Cloud Build 트리거를 구성하는 방법은 빌드 트리거 만들기 및 관리 문서를 참고해요.
이전 단계의 cloudbuild.yaml을 사용하려면 Cloud Build 트리거 유형을 저장소의 /cloudbuild.yaml 파일에 있는 "Cloud Build Configuration"으로 구성해요. 빌드가 트리거될 서비스 계정은 지정하지 마세요. 대신 Cloud Build의 기본 서비스 계정을 사용하길 원할 거예요.
Cloud Build 트리거를 구성할 때는 누가 빌드를 트리거할 수 있는지 고려해 보세요. 빌드를 트리거하는 것이 패키지의 새 버전을 게시할 수 있기 때문이에요. 수동 빌드만 허용하거나, 다음 섹션에서 설명할 Cloud Build 승인(approvals)을 사용해 빌드를 게이트하는 것을 고려해 보세요.
Cloud Build 승인으로 보안 강화하기
Cloud Build 트리거를 구성할 때 **빌드 실행 전 승인 필요(require approval before build executes)**를 선택할 수 있어요. Cloud Build 트리거가 승인을 요구하면 트리거됐을 때 바로 실행되지 않고 승인을 기다려요. 이것을 사용해 누가 패키지의 새 버전을 게시할 수 있는지 제한할 수 있어요. Cloud Build Approver 역할이 있는 사용자만 승인을 줄 수 있어요. 승인할 때 승인자는 URL과 코멘트를 지정할 수 있어요. 대기 중인 승인에 대한 알림도 구성할 수 있어요. 자세한 내용은 승인 시 게이트 빌드 문서를 참고해요.
서비스 계정으로 어디에서든 게시하기
GitHub Actions 밖에서 자동 게시를 허용하려면 Cloud Build와 비슷한 방식으로 서비스 계정을 이용해 인증할 수 있어요. 여기에는 보통 다음이 포함돼요.
- 게시용 서비스 계정 만들기.
- 게시 서비스 계정을 다음 두 가지 방법 중 하나로 가장하기.
- Workload Identity Federation
- 내보낸 서비스 계정 키(Exported Service Account Keys)
Cloud Build 섹션에서 게시 서비스 계정을 만드는 방법을 설명했어요. 그러면 pub-dev@$PROJECT_ID.iam.gserviceaccount.com 같은 서비스 계정이 생겨요.
Workload Identity Federation으로 게시하기
OIDC나 SAML을 지원하는 클라우드 서비스에서 실행 중이라면 Workload Identity Federation을 사용해 GCP 서비스 계정을 가장할 수 있어요. 이렇게 하면 클라우드 제공자의 신원 서비스를 활용할 수 있어요. 예를 들어 EC2에 배포한다면 AWS로 workload identity federation을 구성해서 EC2 메타데이터 서비스의 임시 AWS 토큰이 서비스 계정을 가장할 수 있게 할 수 있어요. 이 흐름을 구성하는 방법은 workload identity federation 문서를 참고해요.
내보낸 서비스 계정 키로 게시하기
신원 서비스가 없는 커스텀 시스템에서 실행 중이라면 서비스 계정 키를 내보낼 수 있어요. 내보낸 서비스 계정 키를 사용하면 해당 서비스 계정으로 인증할 수 있어요. 자세한 내용은 서비스 계정 키 만들기 및 관리 문서를 참고해요.
서비스 계정 키 내보내기 — 기존 서비스 계정에 대한 내보낸 서비스 계정 키를 만들어요.
$ gcloud iam service-accounts keys create key-file.json \
--iam-account=pub-dev@$PROJECT_ID.iam.gserviceaccount.com
나중에 쓰려고 key-file.json 파일을 저장해 두세요.
내보낸 서비스 계정 키로 패키지 게시하기 — 내보낸 서비스 계정 키를 사용해 패키지를 게시하려면:
-
key-file.json(이전 단계에서 만든 것)을 사용해 gcloud를 인증하도록 설정하기.$ gcloud auth activate-service-account --key-file=key-file.json -
pub.dev용 임시 토큰을 만들어
dart pub token add https://pub.dev에 전달하기. 서비스 계정을 가장하려면--include-email옵션을 포함해요.$ gcloud auth print-identity-token \ --audiences=https://pub.dev \ | dart pub token add https://pub.dev -
임시 토큰을 사용해 게시하기. yes/no 프롬프트를 건너뛰려면
--force옵션을 추가해요.$ dart pub publish --force