Automated publishing of packages to pub.dev
- Publishing packages using GitHub Actions
- Publishing from Google Cloud Build
- Publish from anywhere using a Service Account
You can automate publishing from:
- GitHub Actions,
- Google Cloud Build or,
- Anywhere else using a GCP service account.
The following sections explain how automated publishing is configured, and how you can customize publishing flows in line with your preferences.
When configuring automated publishing you don't need to create a long-lived secret that is copied into your automated deployment environment. Instead, authentication relies on temporary OpenID-Connect tokens signed by either GitHub Actions (See OIDC for GitHub Actions) or Google Cloud IAM.
You can use exported service account keys for deployment environments where an identity service isn't present. Such exported service account keys are long-lived secrets, they might be easier to use in some environments, but also pose a larger risk if accidentally leaked.
Publishing packages using GitHub Actions
#You can configure automated publishing using GitHub Actions. This involves:
Enabling automated publishing on pub.dev, specifying:
- The GitHub repository and,
- A tag-pattern that must match to allow publishing.
Creating a GitHub Actions workflow for publishing to pub.dev.
Pushing a git tag for the version to be published.
The following sections outline how to complete these steps.
Configuring automated publishing from GitHub Actions on pub.dev
#To enable automated publication from GitHub Actions to pub.dev
, you must be:
- An uploader on the package, or,
- An admin of the publisher (if the package is owned by a publisher).
If you have sufficient permission, you can enable automated publishing by:
Navigating to the Admin tab (
pub.dev/packages/<package>/admin
).Find the Automated publishing section.
Click Enable publishing from GitHub Actions, this prompts you to specify:
- A repository (
<organization>/<repository>
, example:dart-lang/pana
), - A tag-pattern (a string containing
{{version}}
).
- A repository (
The repository is the <organization>/<repository>
on GitHub. For example, if your repository is https://github.com/dart-lang/pana
you must specify dart-lang/pana
in the repository field.
The tag pattern is a string that must contain {{version}}
. Only GitHub Actions triggered by a push of a tag that matches this tag pattern will be allowed to publish your package.
Example: a tag pattern like v{{version}}
allows GitHub Actions (triggered by git tag v1.2.3 && git push v1.2.3
) to publish version 1.2.3
of your package. Thus, it's also important that the version
key in pubspec.yaml
matches this version number.
If your repository contains multiple packages, give each a separate tag-pattern. Consider using a tag-pattern like my_package_name-v{{version}}
for a package named my_package_name
.
Configuring a GitHub Action workflow for publishing to pub.dev
#When automated publishing from GitHub Actions is enabled on pub.dev, you can create a GitHub Actions workflow for publishing. This is done by creating a .github/workflows/publish.yml
file as follows:
# .github/workflows/publish.yml
name: Publish to pub.dev
on:
push:
tags:
# must align with the tag-pattern configured on pub.dev, often just replace
# {{version}} with [0-9]+.[0-9]+.[0-9]+
- 'v[0-9]+.[0-9]+.[0-9]+' # tag-pattern on pub.dev: 'v{{version}}'
# If you prefer tags like '1.2.3', without the 'v' prefix, then use:
# - '[0-9]+.[0-9]+.[0-9]+' # tag-pattern on pub.dev: '{{version}}'
# If your repository contains multiple packages consider a pattern like:
# - 'my_package_name-v[0-9]+.[0-9]+.[0-9]+'
# Publish using the reusable workflow from dart-lang.
jobs:
publish:
permissions:
id-token: write # Required for authentication using OIDC
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
# with:
# working-directory: path/to/package/within/repository
Make sure to match the pattern in on.push.tags
with the tag pattern specified on pub.dev. Otherwise, the GitHub Action workflow won't work. If publishing multiple packages from the same repository, use a per-package tag pattern like my_package_name-v{{version}}
and create a separate workflow file for each package.
The workflow file above uses dart-lang/setup-dart/.github/workflows/publish.yml
to publish the package. This is a reusable workflow that allows the Dart team to maintain the publishing logic and enables pub.dev to know how the package was published. Using this reusable workflow is strongly encouraged.
If you need generated code in your package, then it is preferable to check this generated code into your repository. This simplifies verifying that the files published on pub.dev match the files from your repository. If checking generated or built artifact into your repository is not reasonable, you can create a custom workflow along the lines of:
# .github/workflows/publish.yml
name: Publish to pub.dev
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+' # tag pattern on pub.dev: 'v{{version}'
# Publish using custom workflow
jobs:
publish:
permissions:
id-token: write # Required for authentication using OIDC
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dart-lang/setup-dart@v1
- name: Install dependencies
run: dart pub get
# Here you can insert custom steps you need
# - run: dart tool/generate-code.dart
- name: Publish
run: dart pub publish --force
The workflow authenticates to pub.dev
using a temporary GitHub-signed OIDC token, the token is created and configured in the dart-lang/setup-dart
step. To publish to pub.dev, subsequent steps can run dart pub publish --force
.
Triggering automated publishing from GitHub Actions
#After you've configured automated publishing on pub.dev
and created a GitHub Actions workflow, you can publish a new version of your package. To publish, push a git tag matching the configured tag pattern.
$ cat pubspec.yaml
package: my_package_name
version: 1.2.3 # must match the version number used in the git tag
environment:
sdk: ^2.19.0
$ git tag v1.2.3 # assuming my tag pattern is: 'v{{version}}'
$ git push origin v1.2.3 # triggers the action that publishes my package.
Once pushed, review the workflow logs at https://github.com/<organization>/<repository>/actions
.
If the Action didn't trigger, check that the pattern configured in .github/workflows/publish.yml
matches the pushed git tag. If the Action failed, the logs might contain clues as to why it failed.
Once published, you can see the publication event in the audit-log
on pub.dev
. The audit-log
entry should contain a link to the GitHub Action run that published the package version.
If you don't like using the git
CLI to create tags, you can create releases on GitHub from https://github.com/<organization>/<repository>/releases/new
. To learn more, check out managing releases in a repository from GitHub.
Hardening security with tag protection rules on GitHub
#Configuring automated publishing from GitHub Actions allows anyone who can push a tag to your repository to trigger publishing to pub.dev. You can restrict who can push tags to your repository using tag protection rules on GitHub.
By limiting who can create tags matching your tag pattern, you can limit who can publish the package.
At this time, the tag protection rules lack flexibility. You might want to restrict who can trigger publishing using GitHub Deployment Environments, as outlined in the next section.
Hardening security with GitHub Deployment Environments
#When configuring automated publishing from GitHub Actions on pub.dev, you can require a GitHub Actions environment. To require a GitHub Actions environment for publishing you must:
- Navigate to the Admin tab (
pub.dev/packages/<package>/admin
). - Find the Automated publishing section.
- Click Require GitHub Actions environment.
- Specify an Environment name, (
pub.dev
is typically a good name)
When an environment is required on pub.dev, GitHub Actions won't be able to publish unless they have environment: pub.dev
. Thus, you must:
- Create an environment with the same name on GitHub (typically
pub.dev
) - Alter your
.github/workflows/publish.yml
workflow file to specifyenvironment: pub.dev
, as follows:
# .github/workflows/publish.yml
name: Publish to pub.dev
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+' # for tags like: 'v1.2.3'
jobs:
publish:
permissions:
id-token: write # Required for authentication using OIDC
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
with:
# Specify the github actions deployment environment
environment: pub.dev
# working-directory: path/to/package/within/repository
The environment is reflected in the temporary GitHub-signed OIDC token used for authentication with pub.dev. Thus, a user with permission to push to your repository cannot circumvent environment protection rules by modifying the workflow file.
In GitHub repository settings, you can use environment protection rules to configure required reviewers. If you configure this option, GitHub prevents actions with the environment from running until one of the required reviewers have approved the run.
Publishing from Google Cloud Build
#You can configure automated publishing from Google Cloud Build. This involves:
- Register a Google Cloud Project (or using an existing project),
- Create a service account for publishing to pub.dev,
- Enable automated publishing in the admin tab for the package on pub.dev, specifying the email of the service account created for publishing.
- Grant the default Cloud Build service account permission to impersonate the service account created for publishing.
- Create a
cloudbuild.yaml
file that obtains a temporary OIDCid_token
and uses it for publishing to pub.dev - Configure a Cloud Build trigger, for running the steps in
cloudbuild.yaml
in your project on Google Cloud Build.
The following sections outline how to complete these steps.
Creating a service account for publishing
#For publishing to pub.dev you are going to create a service account that is granted permission to publish your package on pub.dev. You are then going to grant Cloud Build permission to impersonate this service account.
Create a cloud project, if you don't have an existing project.
Create a service account as follows:
$ gcloud iam service-accounts create pub-dev \ --description='Service account to be impersonated when publishing to pub.dev' \ --display-name='pub-dev'
This creates a service account named
pub-dev@$PROJECT_ID.iam.gserviceaccount.com
.Grant the service account permission to publish your package.
To complete this step, you must have uploader permission on the package or be an admin of the publisher that owns the package.
a. Navigate to the Admin tab (
pub.dev/packages/<package>/admin
). a. Click Enable publishing with Google Cloud Service account. a. Type the email of the service account into the Service account email field. You created this account in the previous step:pub-dev@$PROJECT_ID.iam.gserviceaccount.com
With this procedure complete, anyone who can impersonate the service account can publish new versions of the package. Make sure to review who has permissions to impersonate the service account and change permissions in the cloud project as needed.
Granting Cloud Build permission to publish
#To publish from Cloud Build you must give the default Cloud Build service account permission to impersonate the service account created for publishing in the previous section.
Enable the IAM Service Account Credentials API in the cloud project. Attempts to impersonate a service account will fail without this API.
# Enable IAM Service Account Credentials API $ gcloud services enable iamcredentials.googleapis.com
Find the project number.
# The PROJECT_NUMBER can be obtained as follows: $ gcloud projects describe $PROJECT_ID --format='value(projectNumber)'
Grant the permission to impersonate the publishing service account.
# Grant default cloud $ gcloud iam service-accounts add-iam-policy-binding \ pub-dev@$PROJECT_ID.iam.gserviceaccount.com \ --member=serviceAccount:$PROJECT_NUMBER@cloudbuild.gserviceaccount.com \ --role=roles/iam.serviceAccountTokenCreator
Writing a Cloud Build configuration file
#To publish from Cloud Build, you must specify steps for Cloud Build to:
- Impersonate the service account to obtain a temporary OIDC token.
- Provide the temporary OIDC token to
dart pub
for use when publishing. - Calling
dart pub publish
to publish the package.
Steps for Google Cloud Build are provided in a cloudbuild.yaml
file, see build configuration file schema for full documentation of the format.
For publishing to pub.dev from Google Cloud Build, a cloudbuild.yaml
file as follows will do:
# 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
The gcloud auth print-identity-token
creates an OIDC id_token
impersonating the specified service account. This id_token
is signed by Google, with a signature that expires within 1 hour. The audiences parameter lets pub.dev know that it is the intended recipient of the token. The --include-email
option is necessary for pub.dev to recognize the service account.
Once the id_token
is created, it's written to a file that resides in a volume; this mechanism is used to pass data between steps. Don't store the token in /workspace
. Since /workspace
is where the repository from which you wish to publish is checked out. Not using /workspace
for storing the token reduces the risk that you accidentally include it in your package when publishing.
Creating a Cloud Build trigger
#With service accounts configured and a cloudbuild.yaml
file in the repository you can create a Cloud Build Trigger using the console.cloud.google.com dashboard. To create a build trigger, you need to connect to a source repository and specify which events should trigger a build. You can use GitHub, Cloud Source Repository, or one of the other options. To learn how to configure a Cloud Build Trigger, check out creating and managing build triggers.
To use the cloudbuild.yaml
from the previous step, configure the Cloud Build Trigger type as "Cloud Build Configuration" located in the repository in the /cloudbuild.yaml
file. Do not specify a service account for the build to be triggered with. Instead you'll want to use the default service account for Cloud Build.
When configuring your Cloud Build trigger, consider who can trigger the build. Because triggering a build might publish a new version of your package. Consider only allowing manual builds or use Cloud Build approvals to gate builds as outlined in next section.
Hardening security with Cloud Build Approvals
#When configuring a Cloud Build trigger, you can select require approval before build executes. If a Cloud Build trigger requires approval, it won't run when triggered. Instead, it'll wait for approval. This can be used to limit who can publish new versions of your package.
Only a user with the Cloud Build Approver role can give approval. When giving a approval, the approver can specify a URL and comment.
You can also configure notifications for pending approvals. To learn more, check out gate build on approval.
Publish from anywhere using a Service Account
#To allow automated publishing outside of GitHub Actions, you might authenticate using service accounts in way similar to Cloud Build.
This usually involves:
- Create a service account for publishing,
- Impersonate the publishing service account in one of two ways:
- Workload Identity Federation
- Exported Service Account Keys
The section for Cloud Build outlined how to create a service account for publishing. This should provide a service account, such as pub-dev@$PROJECT_ID.iam.gserviceaccount.com
.
Publish using Workload Identity Federation
#When running on a cloud service that supports OIDC or SAML, you can impersonate a GCP service account using Workload Identity Federation. This enables you to leverage your cloud provider's identity services.
For example, if deploying on EC2, you can configure workload identity federation with AWS, allowing temporary AWS tokens from the EC2 metadata service to impersonate a service account. To learn how to configure these flows, check out workload identity federation.
Publish using Exported Service Account Keys
#When running on a custom system without identity services, you can export service account keys. Exported service account keys allows you to authenticate as said service account. To learn more, check out how to create and manage service account keys.
Export service account keys
#Create exported service account keys for an existing service account.
$ gcloud iam service-accounts keys create key-file.json \ --iam-account=pub-dev@$PROJECT_ID.iam.gserviceaccount.com
Save the
key-file.json
file for later use.
Publish packages using exported service account keys
#To publish a package using exported service account keys:
Setup gcloud to authenticate using
key-file.json
(created in the previous step)$ gcloud auth activate-service-account --key-file=key-file.json
Create a temporary token for pub.dev and pass it to
dart pub token add https://pub.dev
. To impersonate service account, include the--include-email
option.$ gcloud auth print-identity-token \ --audiences=https://pub.dev \ | dart pub token add https://pub.dev
Publish using the temporary token. Add the
--force
option to skip theyes/no
prompt.$ dart pub publish --force
Unless stated otherwise, the documentation on this site reflects Dart 3.6.0. Page last updated on 2024-08-30. View source or report an issue.