Skip to content
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c4601bd
ci: add ESRP-based PyPI release pipeline
timenick May 8, 2026
446808b
Merge remote-tracking branch 'origin/main' into zhiwang/release-pypi-…
timenick May 13, 2026
bc9cee2
ci: migrate release pipelines from OneBranch to 1ES template
timenick May 13, 2026
a8b50fb
Merge remote-tracking branch 'origin/main' into zhiwang/release-pypi-…
timenick May 14, 2026
866062b
ci(temp): disable release/* branch gate on GitHub release pipeline
timenick May 14, 2026
4b63f3d
ci: drop modelkit-release-github.yml from this branch
timenick May 14, 2026
52445f3
Merge remote-tracking branch 'origin/main' into zhiwang/release-pypi-…
timenick May 14, 2026
715a588
ci(temp): disable release/* branch gate on PyPI release pipeline
timenick May 14, 2026
91bd298
use npm for testing
timenick May 14, 2026
c06ad4a
ci(temp): inline owner/approver emails for ESRP test run
timenick May 14, 2026
fef4b93
Merge remote-tracking branch 'origin/main' into zhiwang/release-pypi-…
timenick May 20, 2026
be61743
ci: update wheel sanity-check for winml-cli rename
timenick May 20, 2026
0244da0
ci: update ESRP ClientId for PyPI release
timenick May 20, 2026
6bcbc1b
ci: upgrade EsrpRelease task to v12 for PyPI release
timenick May 21, 2026
8ec8746
ci: disable managed identity for ESRP PyPI release
timenick May 21, 2026
49a395e
ci: re-enable managed identity for ESRP PyPI release
timenick May 22, 2026
3e7416e
Merge remote-tracking branch 'origin/main' into zhiwang/release-pypi-…
timenick May 25, 2026
bfdfa43
ci: finalize PyPI release pipeline
timenick May 25, 2026
01762c4
Merge remote-tracking branch 'origin/main' into zhiwang/release-pypi-…
timenick May 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions .pipelines/modelkit-release-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)

trigger: none

parameters:
- name: OFFICIAL_BUILD_ID
displayName: 'Specify which official build run to use (pipeline build ID)'
type: string
- name: ESRP_OWNERS
displayName: 'ESRP owner emails (comma-separated)'
type: string
default: ''
- name: ESRP_APPROVERS
displayName: 'ESRP approver emails (comma-separated)'
type: string
default: ''

resources:
repositories:
- repository: 1ESPipelineTemplates
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release

extends:
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
parameters:
pool:
isCustom: true
name: 'ProjectReunionESPool-2022'
# Need a clone of MMS2022 with artifact windows-1es-pt-prerequisites-v2 installed to satisfy 1ESPT.
# https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/1es-pipeline-templates/onboardingesteams/create-agent-image
demands: ImageOverride -equals MMS2022-1ES-GPT
stages:
- stage: Publish
displayName: 'Publish to PyPI'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/release/')
jobs:
- job: Prepare
displayName: 'Prepare PyPI Artifacts'
templateContext:
outputs:
- output: pipelineArtifact
displayName: 'Publish staged PyPI artifacts'
targetPath: $(Build.SourcesDirectory)/pyPackage
artifactName: PyPiPackages

steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download official build artifacts'
inputs:
buildType: 'specific'
project: '320d2220-0d58-42de-ae76-6f774e8e5eef'
definition: '191314'
buildVersionToDownload: 'specific'
pipelineId: ${{ parameters.OFFICIAL_BUILD_ID }}
targetPath: '$(Build.SourcesDirectory)/official_build'

- task: PowerShell@2
displayName: 'Stage wheel + sdist'
inputs:
targetType: 'inline'
script: |
$src = "$(Build.SourcesDirectory)/official_build"
$dst = "$(Build.SourcesDirectory)/pyPackage"
New-Item -ItemType Directory -Path $dst -Force | Out-Null

$wheels = Get-ChildItem $src -Filter "*.whl" -Recurse
$sdists = Get-ChildItem $src -Filter "*.tar.gz" -Recurse

if ($wheels.Count -eq 0) { throw "no wheel found under $src - PyPI requires at least one wheel" }
if ($sdists.Count -eq 0) { throw "no sdist (.tar.gz) found under $src - official build always produces one, missing means upstream is broken" }

$wheels | Copy-Item -Destination $dst
$sdists | Copy-Item -Destination $dst

# Note: official build also emits *.zip (runtime check rules).
# We deliberately do NOT copy them - PyPI publish folder must contain
# only the artifacts we want ESRP to upload.

Write-Host "Staged for PyPI publish:"
Get-ChildItem $dst | ForEach-Object { Write-Host (" - {0} ({1:N0} bytes)" -f $_.Name, $_.Length) }

- task: PowerShell@2
displayName: 'Sanity-check wheel version vs pyproject.toml'
inputs:
targetType: 'inline'
script: |
$wheel = Get-ChildItem "$(Build.SourcesDirectory)/pyPackage/*.whl" | Select-Object -First 1
# Wheel filename is <normalized_name>-<version>-<python tag>-<abi tag>-<platform tag>.whl
# PEP 503 normalizes "winml-cli" -> "winml_cli" in filenames.
if ($wheel.Name -notmatch '^winml_cli-([^-]+)-') {
throw "wheel filename '$($wheel.Name)' does not match expected pattern 'winml_cli-<version>-...'"
}
$wheelVersion = $matches[1]

$pyproject = Get-Content "$(Build.SourcesDirectory)/pyproject.toml" -Raw
if ($pyproject -notmatch 'version\s*=\s*"([^"]+)"') {
throw "could not parse version from pyproject.toml"
}
$repoVersion = $matches[1]

if ($wheelVersion -ne $repoVersion) {
throw "version mismatch: wheel says '$wheelVersion', pyproject.toml says '$repoVersion'. Wrong OFFICIAL_BUILD_ID?"
}
Write-Host "Version check passed: $wheelVersion"

- job: ManualValidation
displayName: 'Manual Approval'
dependsOn: Prepare
pool: Server
steps:
- task: ManualValidation@0
displayName: 'Verify staged artifacts before publishing to PyPI'
timeoutInMinutes: 1440 # 24 hours
inputs:
notifyUsers: '${{ parameters.ESRP_OWNERS }}'
instructions: |
Review the Prepare job logs (staged wheel + sdist filenames, version check).
Approve to invoke ESRP Release - ESRP will then trigger its own approval flow.
Reject if the wrong OFFICIAL_BUILD_ID was queued or the staged artifacts look wrong.
onTimeout: 'reject'

- job: PublishToESRP
displayName: 'Publish to PyPI via ESRP'
dependsOn: ManualValidation
templateContext:
type: releaseJob # Required, this indicates this job is a release job
isProduction: true # Required, must be 'true' or 'false'
inputs:
- input: pipelineArtifact
artifactName: PyPiPackages
targetPath: $(Build.SourcesDirectory)/pyPackage

steps:
- task: PowerShell@2
displayName: 'List artifacts to publish'
inputs:
targetType: 'inline'
script: |
Get-ChildItem -Path "$(Build.SourcesDirectory)/pyPackage" -Recurse

- task: EsrpRelease@12
displayName: 'Publish to PyPI via ESRP Release'
inputs:
connectedservicename: 'ModelKit ESRP ADO Service Connection'
usemanagedidentity: true
keyvaultname: 'ModelKit-KeyVault'
signcertname: 'modelkit-tss-sign'
clientid: 'e80da4f8-3154-4198-9ad6-8f8988263b02'
intent: 'PackageDistribution'
contenttype: 'PyPi'
contentsource: 'Folder'
folderlocation: '$(Build.SourcesDirectory)/pyPackage'
waitforreleasecompletion: true
owners: '${{ parameters.ESRP_OWNERS }}'
approvers: '${{ parameters.ESRP_APPROVERS }}'
serviceendpointurl: 'https://api.esrp.microsoft.com'
mainpublisher: 'ESRPRELPACMAN'
domaintenantid: '975f013f-7f24-47e8-a7d3-abc4752bf346'
Loading