Skip to content

Add PyPI publishing workflow (Trusted Publishing / OIDC)#4480

Open
LiliDeng wants to merge 2 commits into
mainfrom
feature/pip-install-lisa_130526_105955
Open

Add PyPI publishing workflow (Trusted Publishing / OIDC)#4480
LiliDeng wants to merge 2 commits into
mainfrom
feature/pip-install-lisa_130526_105955

Conversation

@LiliDeng
Copy link
Copy Markdown
Collaborator

@LiliDeng LiliDeng commented May 13, 2026

mslisa PyPI Publishing — Design and Pre-release Testing Guide

Branch: feature/pip-install-lisa_130526_105955 on microsoft/lisa
PR: #4480 Add PyPI publishing workflow (Trusted Publishing / OIDC)
Package name: mslisa (test package name on TestPyPI: mslisa-lildeng-test)


1. Why pip install?

1.1 Current state (before this change)

The only documented way to install LISA has been:

git clone https://github.com/microsoft/lisa.git
cd lisa
python3 -m pip install --editable .[azure,libvirt] --config-settings editable_mode=compat

This approach has several long-standing problems:

Problem Impact
Forces a git clone Every user / CI agent must pull the full git history — large download, slow on constrained networks
Requires --editable mode Great for contributors (edits are live), but a burden for deployers (the source tree must stay around)
No version concept No pip install lisa==X.Y.Z, no pinning, no rollback
Hard to use in restricted environments Offline / locked-down images can pull a single wheel much more easily than a git clone + dependency resolution
Dependency isolation is awkward Users must know the magic extras names ([azure,libvirt])

1.2 Goal

Make this work for any Python user:

pip install mslisa            # core only
pip install "mslisa[azure]"   # most common: with Azure SDK
pip install "mslisa[azure,libvirt]"
lisa --help

Properties:

  • No source clone required
  • Real version numbers (CalVer: YYYYMMDD.N)
  • Coexists with the existing --editable development workflow — contributors are not affected

2. Design principles

2.1 Package name mslisa

lisa is already taken on PyPI, so the project ships as mslisa (Microsoft LISA).

  • Install command: pip install mslisa
  • The console script name is unchanged: lisa/pyproject.toml declares [project.scripts] lisa = "lisa.main:cli", so users still type lisa.
  • The Python import path is unchanged: import lisa.

2.2 Versioning: CalVer + setuptools_scm

  • Version format: YYYYMMDD.N (e.g. 20260420.1, 20260420.2)
  • No hand-maintained __version__; setuptools_scm derives the version from the git tag.
  • Multiple releases per day are fine — bump .1.2.3.
  • Once published, a version cannot be replaced (PyPI policy). To fix a bad release, yank it and publish a new one.

2.3 Trusted Publishing (OIDC, no secrets)

No long-lived API tokens anywhere. GitHub Actions proves its identity to PyPI / TestPyPI over OIDC.

  • The trust relationship is bound to: microsoft/lisa repo + publish.yml workflow + GitHub Environment name (pypi or testpypi).
  • Maintainers can come and go without rotating credentials.

2.4 Two-stage publish: TestPyPI → PyPI (with manual gate)

Every pushed tag automatically:

  1. Builds sdist + wheel
  2. Uploads to TestPyPI (no approval) — for human smoke testing
  3. Waits for manual review on the GitHub Environment pypi
  4. After approval, uploads to the real PyPI

This gives a "back out" window so a broken artifact never reaches the production index unattended.

2.5 Optional extras for opt-in features

pip install mslisa installs only core dependencies (paramiko, PyYAML, etc.).
Platform-specific and developer dependencies are opt-in via extras (lisa/pyproject.toml):

Extra Purpose
azure Azure SDK packages required for Azure tests
libvirt libvirt platform
aws AWS platform
baremetal Bare-metal / Redfish
ai AI-agent tests
docs, mypy, pylint, black, flake8, isort, typing, test Dev tooling

3. How the package is built

3.1 Key files

File Role
lisa/pyproject.toml Metadata, dependencies, extras, console script, build system
lisa/MANIFEST.in sdist include/exclude rules (note: prune doesn't apply to files already tracked by git when setuptools_scm is the file finder)
lisa/.github/workflows/publish.yml Publish workflow
lisa/RELEASE.md Release runbook

3.2 Build backend

[build-system]
requires = ["setuptools >= 70", "setuptools_scm[toml] >= 6.2"]
build-backend = "setuptools.build_meta"
  • Backend: setuptools + setuptools_scm
  • Artifacts:
    • mslisa-<version>-py3-none-any.whl (pure-Python wheel, cross-platform)
    • mslisa-<version>.tar.gz (sdist)

3.3 Known limitations

  • sdist build fails on Windows. setuptools_scm includes every git-tracked file (including deeply nested paths under lisa/ai/data/...), and the resulting paths exceed Windows' 260-character limit. The CI build on Linux is unaffected. End users install the wheel.
  • MANIFEST.in prune rules don't apply to files already tracked by git, because the file finder is setuptools_scm. To shrink the sdist, move lisa/ai/data/ out of git (git-lfs or a sibling repo).

4. How the publish workflow runs

4.1 Trigger

lisa/.github/workflows/publish.yml:

on:
  push:
    tags:
      - "2[0-9][0-9][0-9][0-9][0-9][0-9][0-9].*"   # CalVer
  workflow_dispatch:   # manual run for build-only smoke tests

4.2 The three jobs

┌─────────────┐     ┌──────────────────┐     ┌──────────────────┐
│   build     │ ──▶ │ publish-testpypi │ ──▶ │  publish-pypi    │
│ sdist+wheel │     │  (auto, OIDC)    │     │ (manual approval)│
└─────────────┘     └──────────────────┘     └──────────────────┘
  1. build

    • actions/checkout@v4 with fetch-depth: 0 (setuptools_scm needs the full tag history)
    • python -m build produces wheel + sdist
    • python -m twine check dist/* validates metadata
    • Uploads dist/ as an artifact
  2. publish-testpypi

    • Only runs on tag push
    • GitHub Environment: testpypi (no reviewers required)
    • Uses pypa/gh-action-pypi-publish@release/v1 with OIDC
    • Target: https://test.pypi.org/legacy/
  3. publish-pypi

    • Depends on publish-testpypi succeeding
    • GitHub Environment: pypi (required reviewers configured)
    • A maintainer must click "Review deployments" in the Actions UI to continue
    • Target: production PyPI

4.3 One-time bootstrap (already done)

See lisa/RELEASE.md. Summary:

  1. Add a pending publisher on PyPI and TestPyPI (binding microsoft/lisa + publish.yml + environment name).
  2. In GitHub Settings → Environments, create testpypi and pypi, and add 1–2 LSG maintainers as required reviewers on pypi.
  3. (Recommended) Add a tag protection rule 2[0-9][0-9][0-9][0-9][0-9][0-9][0-9].* so only release managers can push release tags.

4.4 Cutting a release

git checkout main
git pull --ff-only
TAG=$(date +%Y%m%d).1          # bump to .2 if today already has .1
git tag -a "$TAG" -m "$TAG"
git push origin "$TAG"
# Then watch the Actions run. Smoke test from TestPyPI, then approve to promote to PyPI.

5. Pre-release testing (before approving the final PyPI push)

Before the pypi environment is approved, the artifact already exists on TestPyPI. Anyone can install it from there and run end-to-end validation.

5.1 On WSL / Ubuntu 24.04 (recommended — isolated environment)

Ubuntu 24.04's system Python is locked down by PEP 668, so you cannot pip install into the system interpreter. Use a venv.

# 1. Install Python venv tooling
sudo apt update
sudo apt install -y python3 python3-venv python3-full

# 2. Confirm Python version (Ubuntu 24.04 ships 3.12)
python3 --version

# 3. Create a clean venv just for verification
python3 -m venv /tmp/lisa-pypi-verify

# 4. Activate
source /tmp/lisa-pypi-verify/bin/activate

# 5. Upgrade pip
pip install --upgrade pip

# 6. Install the pre-release from TestPyPI
#    --index-url       : pull the package itself from TestPyPI
#    --extra-index-url : resolve normal dependencies from PyPI
#    [azure]           : install Azure extras
#    ==<TAG>           : pin the version (required for rc/pre-release builds —
#                        pip skips pre-releases unless you spell out the version)
TAG="20260513.1rc1"   # replace with the tag you want to verify
pip install \
    --index-url https://test.pypi.org/simple/ \
    --extra-index-url https://pypi.org/simple/ \
    "mslisa-lildeng-test[azure]==${TAG}"
# After the real release on PyPI, the package name becomes mslisa:
# pip install \
#     --index-url https://test.pypi.org/simple/ \
#     --extra-index-url https://pypi.org/simple/ \
#     "mslisa[azure]==${TAG}"

# 7. Verify
lisa --help
python -c "import lisa; print(lisa.__file__)"

# 8. Exit when done
deactivate

Want a fully isolated WSL instance? In Windows PowerShell:

Invoke-WebRequest -Uri "https://cloud-images.ubuntu.com/releases/noble/release/ubuntu-24.04-server-cloudimg-amd64-root.tar.xz" -OutFile "$env:TEMP\noble-rootfs.tar.gz"
New-Item -ItemType Directory -Force -Path C:\WSL\lisa-pypi-test | Out-Null
wsl --import lisa-pypi-test C:\WSL\lisa-pypi-test "$env:TEMP\noble-rootfs.tar.gz" --version 2
wsl -d lisa-pypi-test

Remove when done: wsl --unregister lisa-pypi-test; Remove-Item -Recurse -Force C:\WSL\lisa-pypi-test

5.2 On Windows

$py = "C:\Users\<you>\AppData\Local\Programs\Python\Python313\python.exe"
& $py --version    # expect: Python 3.13.x
& $py -m venv C:\tmp\lisa-pypi-verify

$TAG = "20260513.1rc1"
& C:\tmp\lisa-pypi-verify\Scripts\python.exe -m pip install `
    --index-url https://test.pypi.org/simple/ `
    --extra-index-url https://pypi.org/simple/ `
    "mslisa-lildeng-test[azure]==$TAG"

& C:\tmp\lisa-pypi-verify\Scripts\lisa.exe --help

5.3 Local dry run (no upload anywhere)

Whenever you change pyproject.toml or MANIFEST.in, build locally first:

cd lisa
python -m pip install --upgrade build twine
python -m build --wheel      # sdist fails on Windows — wheel only there
python -m twine check dist/*

# Try installing into a fresh venv
python -m venv /tmp/mslisa-local
/tmp/mslisa-local/bin/pip install ./dist/mslisa-*.whl[azure]
/tmp/mslisa-local/bin/lisa --help

5.4 Verification checklist (run for every rc)

  • pip install completes with no dependency conflicts
  • lisa --help prints normally
  • lisa --version matches the tag
  • python -c "import lisa" succeeds
  • A simple runbook (e.g. examples/runbook/hello_world.yml) runs to completion
  • An Azure-platform runbook can authenticate and deploy a VM
  • Wheel size is reasonable (a sudden jump means something unwanted was packaged)

6. Contributor workflow is unchanged

Publishing to PyPI does not affect contributors. They keep using:

git clone https://github.com/microsoft/lisa.git
cd lisa
python3 -m pip install --editable .[azure,libvirt] --config-settings editable_mode=compat

Source edits are live, selftests work as before, PRs work as before.

One-line summary: pip install mslisa is for users; pip install -e . is for contributors.


7. References

LiliDeng added 2 commits May 13, 2026 12:42
- .github/workflows/publish.yml: tag-triggered build then publish via PyPI Trusted Publishing (OIDC); TestPyPI first, then PyPI gated by GitHub Environment reviewer approval. No tokens stored.

- RELEASE.md: one-time bootstrap (pending publishers, GitHub environments, tag protection) plus per-release SOP and known limitations.

- MANIFEST.in: prune lisa/ai/data; deeply nested log paths trigger Windows 260-char limit during sdist build.
Copilot AI review requested due to automatic review settings May 13, 2026 07:12
@LiliDeng LiliDeng requested a review from johnsongeorge-w as a code owner May 13, 2026 07:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a GitHub Actions-based release pipeline for publishing the mslisa package to TestPyPI and PyPI using PyPI Trusted Publishing (OIDC), along with release runbook documentation and an sdist pruning tweak to avoid Windows path-length issues during local builds.

Changes:

  • Introduce a tag-triggered publish.yml workflow that builds artifacts, validates with twine check, publishes to TestPyPI, then publishes to PyPI behind a GitHub Environment approval gate.
  • Add RELEASE.md with bootstrap steps (pending publishers/environments/tag protection) and a per-release SOP.
  • Prune lisa/ai/data from the sdist via MANIFEST.in to reduce deep-path issues on Windows.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
RELEASE.md Documents bootstrap + release SOP for Trusted Publishing and local dry-run guidance.
MANIFEST.in Excludes lisa/ai/data from sdists to mitigate Windows path-length failures.
.github/workflows/publish.yml Implements the build + TestPyPI + gated PyPI publish workflow using OIDC.

Comment thread RELEASE.md
# Try installing into a fresh venv
py -3.12 -m venv C:\tmp\mslisa-local
$wheel = (Get-Item dist\mslisa-*.whl).FullName
& C:\tmp\mslisa-local\Scripts\python.exe -m pip install "$wheel[azure]"
Comment thread RELEASE.md
Comment on lines +124 to +130
- **sdist build fails on Windows** because `setuptools_scm` includes every git-
tracked file (including deeply nested logs under `lisa/ai/data/...`) and the
resulting paths exceed Windows' 260-character limit. CI builds on Linux are
unaffected. The wheel is what users actually install.
- **`MANIFEST.in` `prune` rules don't apply** to files already tracked by git
when `setuptools_scm` is the file finder. To shrink the sdist, move
`lisa/ai/data/` out of git (git-lfs or a sibling repo).
Comment thread MANIFEST.in
prune .github
exclude .git*

# AI training data is large and not needed at runtime; exclude from sdist/wheel.
Comment on lines +14 to +18
on:
push:
tags:
# CalVer: e.g. 20260420.1, 20260420.2
- "2[0-9][0-9][0-9][0-9][0-9][0-9][0-9].*"
Comment thread RELEASE.md
---

## Cutting a release

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section shows PowerShell commands for the TestPyPI smoke test. Consider adding a bash equivalent too.


jobs:
build:
name: Build sdist + wheel
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build job validates metadata with twine check but doesn't verify the wheel is actually installable. Adding a quick dry-run install step would catch packaging issues early.

  • name: Smoke-test wheel
    run: |
    pip install dist/mslisa-*.whl --no-deps
    python -c "import lisa; print('OK')"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants