Skip to main content

Distribution

How eigenoid distributes Python packages across environments using GCP Artifact Registry, automated tagging, and GitHub Actions.

Overview​

Every Python package in the org follows the same distribution pipeline:

  1. Push code to a branch (dev, qa, or main)
  2. Auto-tag -- a workflow reads the version from pyproject.toml and creates a git tag with the environment suffix
  3. Release -- the tag triggers a release workflow that builds, tests, and publishes the package to the correct Artifact Registry
  4. Promote -- merge the branch forward (dev -> qa -> main) to promote through environments

No manual tagging, no manual publishing, no manual anything.

Key concepts​

ConceptWhat it means
Artifact Registry (AR)Google Cloud service that hosts Python packages. Each GCP project has its own AR repository.
Environment suffixTags ending in -dev or -qa target those environments. Clean tags (e.g., v1.0.0) target production.
Auto-tagA GitHub Actions workflow that creates git tags automatically on push -- no manual git tag needed.
WIF (Workload Identity Federation)Keyless authentication from GitHub Actions to GCP. No service account keys stored anywhere.

Environments​

Each environment has its own Artifact Registry repository in its own GCP project:

EnvironmentBranchTag formatGCP ProjectAR URL
Developmentdevv1.0.0-deveigenoid-devus-central1-python.pkg.dev/eigenoid-dev/eigenoid-python/simple/
QAqav1.0.0-qaeigenoid-qaus-central1-python.pkg.dev/eigenoid-qa/eigenoid-python/simple/
Productionmainv1.0.0eigenoid-prdus-central1-python.pkg.dev/eigenoid-prd/eigenoid-python/simple/

How it works​

Auto-tagging​

When you push to dev, qa, or main, the auto-tag.yml workflow:

  1. Reads the version field from pyproject.toml
  2. Appends the environment suffix (e.g., 0.5.0 on dev becomes v0.5.0-dev)
  3. Checks if that tag already exists
  4. If not, creates an annotated tag via the GitHub API using the automation bot

The automation bot token is used instead of GITHUB_TOKEN because tags created with GITHUB_TOKEN do not trigger other workflows.

Release​

When the auto-tag creates a new tag, release.yml triggers and:

  1. Resolves the environment from the tag suffix (-dev -> dev, -qa -> qa, clean -> prd)
  2. Runs tests across a Python version matrix
  3. Builds the source distribution and wheel
  4. Creates a GitHub Release (marked as pre-release for dev/qa) using the releaser bot
  5. Publishes to Artifact Registry using WIF authentication with environment-specific credentials

Each GitHub Environment (dev, qa, prd) stores its own WIF provider, service account, and AR repository variables.

Promotion​

To promote a version from dev to qa to production:

# After validating in dev, create PR from dev to qa
gh pr create --base qa --head dev --title "chore: promote v0.5.0 to qa"

# After validating in qa, create PR from qa to main
gh pr create --base main --head qa --title "chore: promote v0.5.0 to prd"
bash

Each merge triggers auto-tag -> release -> publish in the target environment.

Squash merge caveat

When using squash merge for promotion PRs (dev -> qa), the qa branch gets a new commit that does not share ancestry with main. This can make the subsequent qa -> main PR show as "not mergeable." Workaround: use merge commits for promotion PRs, or merge main into qa before creating the PR to main.

Installing packages​

Prerequisites​

pip install keyrings.google-artifactregistry-auth
gcloud auth application-default login
bash

From production​

pip install --extra-index-url \
https://us-central1-python.pkg.dev/eigenoid-prd/eigenoid-python/simple/ \
eigenoid==0.5.0
bash

From dev (for testing)​

pip install --extra-index-url \
https://us-central1-python.pkg.dev/eigenoid-dev/eigenoid-python/simple/ \
eigenoid-sample==0.5.0
bash

In requirements.txt​

--extra-index-url https://us-central1-python.pkg.dev/eigenoid-prd/eigenoid-python/simple/
eigenoid==0.5.0
text

Infrastructure​

The Artifact Registry repositories are managed by Terraform in iac-distribution:

LayerWhat it creates
artifact-registryAR repository for eigenoid/eigenoid + publisher service account + WIF binding
sample-artifact-registryAR repository for eigenoid/eigenoid-sample + publisher service account + WIF binding

Each layer creates:

  • A Python-format Artifact Registry repository
  • A publisher service account (e.g., "Eigenoid PyPI Publisher")
  • A WIF binding that allows the specific GitHub repo to authenticate as the publisher SA
  • A cleanup policy that keeps the last 5 versions

See IaC docs for how Terraform deployment works.

GitHub Apps involved​

BotRoleToken type
eigenoid-automationCreates tags via GitHub APIvars.AUTOMATION_BOT_CLIENT_ID + secrets.AUTOMATION_BOT_PRIVATE_KEY
eigenoid-releaserAuthors GitHub Releasesvars.RELEASER_BOT_CLIENT_ID + secrets.RELEASER_BOT_PRIVATE_KEY

Both are org-level GitHub Apps. Their credentials are stored as organization variables/secrets with ALL repository visibility.

Reference: eigenoid-sample​

eigenoid-sample is the reference implementation of this pipeline. It is a minimal Python package that exists solely to validate the distribution flow end-to-end. Use it as a template when setting up a new package.

Adding a new package​

To set up distribution for a new Python package:

  1. Create the AR infrastructure -- add a new layer in iac-distribution (copy sample-artifact-registry as a template)
  2. Deploy to all environments -- merge to main, let Terraflow provision dev -> qa -> prd
  3. Create GitHub Environments on the repo -- dev, qa, prd with the correct GCP_WIF_PROVIDER, GCP_SA_EMAIL, GCP_PROJECT_ID, AR_LOCATION, AR_REPOSITORY variables
  4. Add workflows -- copy auto-tag.yml and release.yml from eigenoid-sample
  5. Push to dev -- the pipeline takes over from here