diff --git a/.Pipelines/ADO-PUBLISH-SETUP.md b/.Pipelines/ADO-PUBLISH-SETUP.md new file mode 100644 index 00000000..adc133a4 --- /dev/null +++ b/.Pipelines/ADO-PUBLISH-SETUP.md @@ -0,0 +1,243 @@ +# ADO Pipeline Setup Guide — MSAL Python → PyPI + +This document describes every step needed to create an Azure DevOps (ADO) +pipeline that checks out the GitHub repo, runs tests, builds distributions, +and publishes to test.pypi.org (via the MSAL-Python environment) and PyPI. + +The `.Pipelines/` folder follows the same template convention as [MSAL.NET](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/tree/main/build): + +| File | Purpose | +|------|---------| +| [`pipeline-publish.yml`](pipeline-publish.yml) | Thin top-level wrapper — triggers, parameters, calls `template-pipeline-stages.yml` with `runPublish: true` | +| [`template-pipeline-stages.yml`](template-pipeline-stages.yml) | Shared stages template — Validate, CI, Build, Publish stages; reusable by PR-gate and post-merge CI pipelines | +| [`credscan-exclusion.json`](credscan-exclusion.json) | CredScan suppression file — suppresses known false positives for test fixture files (`certificate-with-password.pfx`, `test_mi.py`) | + +--- + +## Overview + +This pipeline is **manually triggered only** — no automatic branch or tag triggers. +Every publish requires explicitly entering a version and selecting a destination. + +| Stage | Trigger | Target | +|-------|---------|--------| +| **PreBuildCheck** (PoliCheck + CredScan) | always | SDL security scans | +| **Validate** | release runs only (`runPublish: true`) | asserts `packageVersion` matches `msal/sku.py` | +| **CI** (tests on Py 3.9–3.14) | after Validate (or immediately on PR/merge runs) | — | +| **Build** (sdist + wheel) | after CI, release runs only | dist artifact | +| **PublishMSALPython** | `publishTarget = test.pypi.org (Preview / RC)` | test.pypi.org | +| **PublishPyPI** | `publishTarget = pypi.org (Production)` | PyPI (production) | + +--- + +## Step 1 — Prerequisites + +| Requirement | Notes | +|-------------|-------| +| ADO Organization | [Create one](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/create-organization) if you don't have one | +| ADO Project | Under the org; enable **Pipelines** and **Artifacts** | +| [Secure Development Tools](https://marketplace.visualstudio.com/items?itemName=securedevelopmentteam.vss-secure-development-tools) extension | Must be installed in the ADO organization — required for the PreBuildCheck stage (PoliCheck, CredScan, PostAnalysis tasks) | +| GitHub account with admin rights | Needed to authorize the ADO GitHub App | +| PyPI API token | Scoped to the `msal` project — generate at | +| MSAL-Python (test.pypi.org) API token | Scoped to the `msal` project on test.pypi.org | +| `AuthSdkResourceManager` Azure service connection *(optional)* | Required only if `LAB_APP_CLIENT_ID` is set to enable e2e tests. ARM service connection with **Get** access to the `LabAuth` secret in the `msidlabs` Key Vault. When not set, the Key Vault steps are automatically skipped. | + +--- + +## Step 2 — Connect ADO to the GitHub Repository + +1. In your ADO project go to **Project Settings → Service connections → New service connection**. +2. Choose **GitHub** and click **Next**. +3. Under **Authentication**, select **Grant authorization** (OAuth) — do **not** use Personal Access Token. + - Click **Authorize** — a GitHub OAuth popup will open. + - Sign in with a GitHub account that has admin rights on the `AzureAD` organization. + - Grant access to `microsoft-authentication-library-for-python`. + - This installs the Azure Pipelines GitHub App and enables webhook and repository listing. + + > **Why OAuth and not PAT:** PAT-based connections cannot install the GitHub webhook + > required for pipeline creation via CLI or API. The OAuth/GitHub App flow installs the + > webhook using the browser's authenticated GitHub session. + +4. Set **Service connection name**: `github-msal-python` +5. Check **Grant access permission to all pipelines**, click **Save**. + +--- + +## Step 3 — Create PyPI Service Connections (Twine) + +The `TwineAuthenticate@1` task uses "Python package upload" service connections +for external registries. + +### 3a — MSAL-Python (test.pypi.org) connection + +1. **Project Settings → Service connections → New service connection** +2. Choose **Python package upload**, click **Next**. +3. Fill in: + | Field | Value | + |-------|-------| + | **Twine repository URL** | `https://test.pypi.org/legacy/` | + | **EndpointName** (`-r` value) | `MSAL-Test-Python-Upload` | + | **Authentication method** | **Authentication Token** | + | **Token** | *(your test.pypi.org API token, full value including `pypi-` prefix)* | + | **Service connection name** | `MSAL-Test-Python-Upload` | +4. Check **Grant access permission to all pipelines**, click **Save**. + +### 3b — PyPI (production) connection + +1. **Project Settings → Service connections → New service connection** +2. Choose **Python package upload**, click **Next**. +3. Fill in: + | Field | Value | + |-------|-------| + | **Twine repository URL** | `https://upload.pypi.org/legacy/` | + | **EndpointName** (`-r` value) | `MSAL-Prod-Python-Upload` | + | **Authentication method** | **Authentication Token** | + | **Token** | *(your PyPI API token, full value including `pypi-` prefix)* | + | **Service connection name** | `MSAL-Prod-Python-Upload` | +4. Check **Grant access permission to all pipelines**, click **Save**. + +> **Security note:** Never commit API tokens to source control. All secrets +> are stored in ADO service connections and injected by `TwineAuthenticate@1` +> via the ephemeral `$(PYPIRC_PATH)` file at pipeline runtime. + +--- + +## Step 4 — Create ADO Environments + +Environments let you add approval gates before the deployment jobs run. + +1. Go to **Pipelines → Environments → New environment**. +2. Create two environments: + + | Name | Description | + |------|-------------| + | `MSAL-Python` | Staging — test.pypi.org uploads | + | `MSAL-Python-Release` | Production — PyPI uploads (**add approval check**) | + +3. For the `MSAL-Python-Release` environment: + - Click the `MSAL-Python-Release` environment → **Approvals and checks → +** + - Add **Approvals** → add the release approver(s) (e.g., release manager). + - This ensures a human must approve before the wheel is pushed to production. + +--- + +## Step 5 — Create the Pipeline in ADO + +1. Go to **Pipelines → New pipeline**. +2. Select **GitHub** as the code source. +3. Pick the repository **AzureAD/microsoft-authentication-library-for-python**. + - ADO will use the `github-msal-python` service connection created in Step 2. +4. Choose **Existing Azure Pipelines YAML file**. +5. Set the path to: `/.Pipelines/pipeline-publish.yml` +6. Click **Continue** → review the YAML → click **Save** (not *Run*). +7. Rename the pipeline to something descriptive, e.g. + `msal-python · publish`. + +> **Note:** The existing `azure-pipelines.yml` (CI-only, runs on `dev`) is a +> separate pipeline and is not affected. + +--- + +## Step 6 — Authorize Pipelines to use Service Connections + +When the pipeline first uses a service connection you may be prompted to +authorize it. To pre-authorize: + +1. **Project Settings → Service connections** → click a connection → + **Security** tab. +2. Set the **Pipeline permissions** to include the new publish pipeline. + +Repeat for all three connections: `github-msal-python`, `MSAL-Test-Python-Upload`, +`MSAL-Prod-Python-Upload`. + +--- + +## Step 7 — Pipeline Parameters (Run Pipeline UI) + +This pipeline is **always manually queued**. Both fields are required — the Validate stage fails if either is missing or the version doesn’t match `msal/sku.py`: + +| Parameter | Required | Description | Example values | +|-----------|----------|-------------|----------------| +| **Package version to publish** | Yes | Must exactly match `msal/sku.py __version__`. PEP 440 format only — no `-Preview` suffix. | `1.36.0` (release), `1.36.0rc1` (RC), `1.36.0b1` (beta) | +| **Publish target** | Yes | Explicit destination — no auto-routing. | `test.pypi.org (Preview / RC)` or `pypi.org (Production)` | + +> **Version format:** PyPI enforces [PEP 440](https://peps.python.org/pep-0440/). Versions with `-` (e.g. `1.36.0-Preview`) are rejected. Use `rc1`, `b1`, or `a1` suffixes instead. + +> **Version must be in sync:** Before queuing, update `msal/sku.py __version__` to the target version and push the change. The Validate stage checks the value on the branch the run is sourced from, not the pipeline default branch. + +--- + +## Step 8 — End-to-End Release Walkthrough + +### Publishing a preview / release candidate to test.pypi.org + +1. Set `msal/sku.py __version__ = "1.36.0rc1"` and push the change +2. Go to **Pipelines → MSAL-Python · Publish → Run pipeline** +3. Select the branch/tag to run from (e.g. the release branch) +4. Enter **Package version to publish**: `1.36.0rc1` +5. Select **Publish target**: `test.pypi.org (Preview / RC)` +6. Click **Run** — pipeline runs: Validate → CI → Build → Publish to test.pypi.org +7. Verify at + +### Publishing a production release to PyPI + +1. Set `msal/sku.py __version__ = "1.36.0"` and push to the release branch +2. Go to **Pipelines → MSAL-Python · Publish → Run pipeline** +3. Select the release branch +4. Enter **Package version to publish**: `1.36.0` +5. Select **Publish target**: `pypi.org (Production)` +6. Click **Run** — pipeline runs: Validate → CI → Build → Publish to PyPI (Production) +7. Verify: `pip install msal==1.36.0` or check + +## Pipeline Trigger Reference + +``` +Manual queue (publishTarget = test.pypi.org (Preview / RC)) + └─► PreBuildCheck ─► Validate ─► CI ─► Build ─► PublishMSALPython + (test.pypi.org (Preview / RC), auto) + +Manual queue (publishTarget = pypi.org (Production)) + └─► PreBuildCheck ─► Validate ─► CI ─► Build ─► PublishPyPI + (pypi.org (Production), requires approval) + +PR / merge build (runPublish: false) + └─► PreBuildCheck ─► CI +``` + +--- + +## Known Requirements + +The following requirements were identified during initial setup and testing: + +- The GitHub service connection **must** be created via OAuth (Grant authorization) in the ADO UI, not via CLI or PAT. The CLI `az pipelines create` command requires webhook installation on the GitHub repo, which requires org admin rights not available to service accounts. +- The pipeline **must** be created via the ADO REST API (`/_apis/build/definitions`) or UI — not via `az pipelines create` — when using an OAuth GitHub service connection without org-level admin rights. +- The `msal/sku.py __version__` must be updated and pushed to the source branch **before** the pipeline run is queued. The Validate stage reads the file from the checked-out branch at runtime. +- The `requirements.txt` file includes `-e .` (editable local install of `msal`). `azure-identity` does not depend on `msal`, so no PyPI version is pulled in as a transitive dependency and the local package is not overwritten. The template installs dependencies with `pip install -r requirements.txt`, which installs the editable local copy directly. +- The `1.35.1` version bump (hotfix) was released from `origin/release-1.35.0` and was never merged back into `dev`. Before the next release from `dev`, this should be backfilled via PR: `https://github.com/AzureAD/microsoft-authentication-library-for-python/compare/dev...release-1.35.0` + +--- + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---------|-------------|-----| +| `403` on twine upload | Token expired or wrong scope | Regenerate API token on pypi.org; update the service connection | +| `File already exists` error | Version already published; PyPI does not allow overwriting | Bump version in `msal/sku.py` | +| Validate stage: `msal/sku.py ''` (empty version) | Python import silently failed | The template uses `grep`/`sed` to read the version — verify `msal/sku.py` contains a `__version__ = "..."` line | +| Validate stage: version mismatch | `sku.py` on the source branch doesn't match the parameter entered | Update `msal/sku.py` on the branch the run is sourced from, not just the pipeline default branch | +| Tests: collection failure across all modules | Missing or broken dependency | Run `pip install -r requirements.txt` locally and confirm `msal` resolves to the local editable install (check `pip show msal`) | +| `az pipelines create` fails with webhook error | GitHub service connection PAT/account lacks org admin rights | Create the pipeline via the ADO UI using a browser session with org admin GitHub access | +| Pipeline creation fails: `Value cannot be null. Parameter name: Connection` | GitHub SC ID is wrong or SC was recreated | Re-query the SC ID with `az devops service-endpoint list` and use the current ID | +| Service connection shows `Authentication: PersonalAccessToken` | SC was created via CLI with a PAT | Delete and recreate via UI using OAuth (Grant authorization) so repos are enumerable | +| `TwineAuthenticate` says endpoint not found | Service connection name mismatch | Ensure `pythonUploadServiceConnection` value exactly matches the service connection name | + +--- + +## References + +- [Publish Python packages with Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/artifacts/pypi?view=azure-devops) +- [TwineAuthenticate@1 task reference](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/twine-authenticate-v1?view=azure-devops) +- [Publish and download Python packages with Azure Artifacts](https://learn.microsoft.com/en-us/azure/devops/artifacts/quickstarts/python-packages?view=azure-devops) +- [Python package upload service connection](https://learn.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints#python-package-upload-service-connection) +- [ADO Environments – approvals and checks](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/approvals?view=azure-devops) diff --git a/.Pipelines/credscan-exclusion.json b/.Pipelines/credscan-exclusion.json new file mode 100644 index 00000000..598dbc62 --- /dev/null +++ b/.Pipelines/credscan-exclusion.json @@ -0,0 +1,13 @@ +{ + "tool": "Credential Scanner", + "suppressions": [ + { + "file": "certificate-with-password.pfx", + "_justification": "Self-signed certificate used only in unit tests. Not a production credential." + }, + { + "file": "test_mi.py", + "_justification": "WWW-Authenticate challenge header value used as a mock HTTP response fixture in unit tests. Not a real credential." + } + ] +} diff --git a/.Pipelines/pipeline-publish.yml b/.Pipelines/pipeline-publish.yml new file mode 100644 index 00000000..a55ab2a2 --- /dev/null +++ b/.Pipelines/pipeline-publish.yml @@ -0,0 +1,30 @@ +# pipeline-publish.yml +# +# Release pipeline for the msal Python package — manually triggered only. +# Source: https://github.com/AzureAD/microsoft-authentication-library-for-python +# +# Delegates all stages to template-pipeline-stages.yml, which is shared with +# the (future) PR gate and post-merge CI pipelines. +# For one-time ADO setup, see ADO-PUBLISH-SETUP.md. + +parameters: +- name: packageVersion + displayName: 'Package version to publish (must match msal/sku.py, e.g. 1.36.0 or 1.36.0rc1)' + type: string + +- name: publishTarget + displayName: 'Publish target' + type: string + values: + - 'test.pypi.org (Preview / RC)' + - 'pypi.org (Production)' + +trigger: none # manual runs only — no automatic branch or tag triggers +pr: none + +stages: +- template: template-pipeline-stages.yml + parameters: + packageVersion: ${{ parameters.packageVersion }} + publishTarget: ${{ parameters.publishTarget }} + runPublish: true diff --git a/.Pipelines/template-pipeline-stages.yml b/.Pipelines/template-pipeline-stages.yml new file mode 100644 index 00000000..8a52314b --- /dev/null +++ b/.Pipelines/template-pipeline-stages.yml @@ -0,0 +1,346 @@ +# template-pipeline-stages.yml +# +# Unified pipeline stages template for the msal Python package. +# +# Called from: +# pipeline-publish.yml — release build (runPublish: true) +# (future) pipeline-ci.yml — post-merge CI (runPublish: false) +# (future) pipeline-pullrequest.yml — PR gate (runPublish: false) +# +# Parameters: +# packageVersion - Version to validate against msal/sku.py +# Required when runPublish is true; unused otherwise. +# publishTarget - 'test.pypi.org (Preview / RC)' or 'pypi.org (Production)' +# Required when runPublish is true; unused otherwise. +# runPublish - When true: also run Validate, Build, and Publish stages. +# When false (PR / merge builds): only the CI stage runs. +# +# Stage flow: +# +# runPublish: true → PreBuildCheck ─► Validate ─► CI ─► Build ─► PublishMSALPython +# └─► PublishPyPI +# runPublish: false → PreBuildCheck ─► CI (Validate / Build / Publish are skipped) + +parameters: +- name: packageVersion + type: string + default: '' +- name: publishTarget + type: string + default: '' +- name: runPublish + type: boolean + default: false + +stages: + +# ══════════════════════════════════════════════════════════════════════════════ +# Stage 0 · PreBuildCheck — SDL security scans (PoliCheck + CredScan) +# Always runs, mirrors MSAL.NET pre-build analysis. +# ══════════════════════════════════════════════════════════════════════════════ +- stage: PreBuildCheck + displayName: 'Pre-build security checks' + jobs: + - job: SecurityScan + displayName: 'PoliCheck + CredScan' + pool: + vmImage: windows-latest + variables: + Codeql.SkipTaskAutoInjection: true + steps: + - task: NodeTool@0 + displayName: 'Install Node.js (includes npm)' + inputs: + versionSpec: 'lts/*' + + - task: securedevelopmentteam.vss-secure-development-tools.build-task-policheck.PoliCheck@2 + displayName: 'Run PoliCheck' + inputs: + targetType: F + continueOnError: true + + - task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@3 + displayName: 'Run CredScan' + inputs: + suppressionsFile: '$(Build.SourcesDirectory)/.Pipelines/credscan-exclusion.json' + toolMajorVersion: V2 + debugMode: false + + - task: securedevelopmentteam.vss-secure-development-tools.build-task-postanalysis.PostAnalysis@2 + displayName: 'Post Analysis' + inputs: + GdnBreakGdnToolCredScan: true + GdnBreakGdnToolPoliCheck: true + +# ══════════════════════════════════════════════════════════════════════════════ +# Stage 1 · Validate — verify packageVersion matches msal/sku.py __version__ +# Skipped when runPublish is false (PR / merge builds). +# ══════════════════════════════════════════════════════════════════════════════ +- stage: Validate + displayName: 'Validate version' + dependsOn: PreBuildCheck + condition: and(${{ parameters.runPublish }}, eq(dependencies.PreBuildCheck.result, 'Succeeded')) + jobs: + - job: ValidateVersion + displayName: 'Check version matches source' + pool: + vmImage: ubuntu-latest + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.12' + displayName: 'Set up Python' + + - bash: | + PARAM_VER="${{ parameters.packageVersion }}" + SKU_VER=$(grep '__version__' msal/sku.py | sed 's/.*"\(.*\)".*/\1/') + + if [ -z "$PARAM_VER" ]; then + echo "##vso[task.logissue type=error]packageVersion is required. Enter the version to publish (must match msal/sku.py __version__)." + exit 1 + elif [ "$PARAM_VER" != "$SKU_VER" ]; then + echo "##vso[task.logissue type=error]Version mismatch: parameter '$PARAM_VER' != msal/sku.py '$SKU_VER'" + echo "Update msal/sku.py __version__ to match the packageVersion parameter, or correct the parameter." + exit 1 + else + echo "Version validated: $PARAM_VER" + fi + displayName: 'Verify version parameter matches msal/sku.py' + +# ══════════════════════════════════════════════════════════════════════════════ +# Stage 2 · CI — run the full test matrix across all supported Python versions. +# Always runs. Waits for Validate when runPublish is true; +# runs immediately when Validate is skipped (PR / merge builds). +# ══════════════════════════════════════════════════════════════════════════════ +- stage: CI + displayName: 'Run tests' + dependsOn: + - PreBuildCheck + - Validate + condition: | + and( + eq(dependencies.PreBuildCheck.result, 'Succeeded'), + in(dependencies.Validate.result, 'Succeeded', 'Skipped') + ) + jobs: + - job: Test + displayName: 'Run unit tests' + pool: + vmImage: ubuntu-latest + strategy: + matrix: + Python39: + python.version: '3.9' + Python310: + python.version: '3.10' + Python311: + python.version: '3.11' + Python312: + python.version: '3.12' + Python313: + python.version: '3.13' + Python314: + python.version: '3.14' + steps: + # Retrieve the MSID Lab certificate from Key Vault (via AuthSdkResourceManager SC). + # Gated on LAB_APP_CLIENT_ID being non-empty — if e2e tests are not enabled (the default), + # both steps are skipped and the pipeline has no Key Vault dependency. + - task: AzureKeyVault@2 + displayName: 'Retrieve lab certificate from Key Vault' + condition: and(succeeded(), ne(variables['LAB_APP_CLIENT_ID'], '')) + inputs: + azureSubscription: 'AuthSdkResourceManager' + KeyVaultName: 'msidlabs' + SecretsFilter: 'LabAuth' + RunAsPreJob: false + + - bash: | + set -euo pipefail + CERT_PATH="$(Agent.TempDirectory)/lab-auth.pfx" + printf '%s' "$(LabAuth)" | base64 -d > "$CERT_PATH" + echo "##vso[task.setvariable variable=LAB_APP_CLIENT_CERT_PFX_PATH]$CERT_PATH" + echo "Lab cert written to: $CERT_PATH ($(wc -c < "$CERT_PATH") bytes)" + displayName: 'Write lab certificate to disk' + condition: and(succeeded(), ne(variables['LAB_APP_CLIENT_ID'], '')) + + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + displayName: 'Set up Python' + + - script: | + python -m pip install --upgrade pip + pip install -r requirements.txt + displayName: 'Install dependencies' + + # Use bash: explicitly; set -o pipefail so that pytest failures aren't hidden by the pipe to tee. + # Without pipefail, tee exits 0 and the step can succeed even when tests fail. + # (set -o pipefail also works in script: steps, but bash: makes the shell choice explicit.) + - bash: | + pip install pytest pytest-azurepipelines + mkdir -p test-results + set -o pipefail + pytest -vv --junitxml=test-results/junit.xml 2>&1 | tee test-results/pytest.log + displayName: 'Run tests' + env: + # LAB_APP_CLIENT_ID is intentionally omitted to match the PR gate build + # behaviour (azure-pipelines.yml). Without it, _get_credential() in + # lab_config.py raises EnvironmentError and all e2e tests skip or error + # gracefully — identical to the PR build result. + # Uncomment and set this variable to enable full e2e runs on a + # lab-capable agent pool (requires CA-exempt network / internal agent). + # LAB_APP_CLIENT_ID: $(LAB_APP_CLIENT_ID) + LAB_APP_CLIENT_CERT_PFX_PATH: $(LAB_APP_CLIENT_CERT_PFX_PATH) + + - task: PublishTestResults@2 + displayName: 'Publish test results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: 'test-results/junit.xml' + failTaskOnFailedTests: true + testRunTitle: 'Python $(python.version)' + + - bash: rm -f "$(Agent.TempDirectory)/lab-auth.pfx" + displayName: 'Clean up lab certificate' + condition: and(always(), ne(variables['LAB_APP_CLIENT_ID'], '')) + +# ══════════════════════════════════════════════════════════════════════════════ +# Stage 3 · Build — build sdist + wheel (release only) +# ══════════════════════════════════════════════════════════════════════════════ +- stage: Build + displayName: 'Build package' + dependsOn: CI + condition: and(eq(dependencies.CI.result, 'Succeeded'), eq(${{ parameters.runPublish }}, true)) + jobs: + - job: BuildDist + displayName: 'Build sdist + wheel (Python 3.12)' + pool: + vmImage: ubuntu-latest + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.12' + displayName: 'Use Python 3.12' + + - script: | + python -m pip install --upgrade pip build twine + displayName: 'Install build toolchain' + + - script: | + python -m build + displayName: 'Build sdist and wheel' + + - script: | + python -m twine check dist/* + displayName: 'Verify distribution (twine check)' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish dist/ as pipeline artifact' + inputs: + targetPath: dist/ + artifact: python-dist + +# ══════════════════════════════════════════════════════════════════════════════ +# Stage 4a · Publish to test.pypi.org (Preview / RC) +# Runs when: runPublish is true AND publishTarget == 'test.pypi.org (Preview / RC)' +# ══════════════════════════════════════════════════════════════════════════════ +- stage: PublishMSALPython + displayName: 'Publish to test.pypi.org (Preview)' + dependsOn: Build + condition: > + and( + eq(dependencies.Build.result, 'Succeeded'), + eq('${{ parameters.publishTarget }}', 'test.pypi.org (Preview / RC)') + ) + jobs: + - deployment: DeployMSALPython + displayName: 'Upload to test.pypi.org' + pool: + vmImage: ubuntu-latest + # Optional: add approval checks in ADO → Pipelines → Environments → MSAL-Python + environment: MSAL-Python + strategy: + runOnce: + deploy: + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download python-dist artifact' + inputs: + artifactName: python-dist + targetPath: $(Pipeline.Workspace)/python-dist + + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.12' + displayName: 'Use Python 3.12' + + - script: | + python -m pip install --upgrade pip + python -m pip install twine + displayName: 'Install twine' + + - task: TwineAuthenticate@1 + displayName: 'Authenticate with MSAL-Test-Python-Upload' + inputs: + pythonUploadServiceConnection: MSAL-Test-Python-Upload + + - script: | + python -m twine upload \ + -r "MSAL-Test-Python-Upload" \ + --config-file $(PYPIRC_PATH) \ + --skip-existing \ + $(Pipeline.Workspace)/python-dist/* + displayName: 'Upload to MSAL-Test-Python-Upload (skip existing)' + +# ══════════════════════════════════════════════════════════════════════════════ +# Stage 4b · Publish to PyPI (Production) +# Runs when: runPublish is true AND publishTarget == 'pypi.org (Production)' +# ══════════════════════════════════════════════════════════════════════════════ +- stage: PublishPyPI + displayName: 'Publish to PyPI (Production)' + dependsOn: Build + condition: > + and( + eq(dependencies.Build.result, 'Succeeded'), + eq('${{ parameters.publishTarget }}', 'pypi.org (Production)') + ) + jobs: + - deployment: DeployPyPI + displayName: 'Upload to pypi.org' + pool: + vmImage: ubuntu-latest + # IMPORTANT: configure a required manual approval on this environment in + # ADO → Pipelines → Environments → MSAL-Python-Release → Approvals and checks. + environment: MSAL-Python-Release + strategy: + runOnce: + deploy: + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download python-dist artifact' + inputs: + artifactName: python-dist + targetPath: $(Pipeline.Workspace)/python-dist + + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.12' + displayName: 'Use Python 3.12' + + - script: | + python -m pip install --upgrade pip + python -m pip install twine + displayName: 'Install twine' + + - task: TwineAuthenticate@1 + displayName: 'Authenticate with MSAL-Prod-Python-Upload' + inputs: + pythonUploadServiceConnection: MSAL-Prod-Python-Upload + + - script: | + python -m twine upload \ + -r "MSAL-Prod-Python-Upload" \ + --config-file $(PYPIRC_PATH) \ + $(Pipeline.Workspace)/python-dist/* + displayName: 'Upload to MSAL-Prod-Python-Upload' diff --git a/msal/sku.py b/msal/sku.py index 01751048..19ff0138 100644 --- a/msal/sku.py +++ b/msal/sku.py @@ -1,6 +1,6 @@ -"""This module is from where we recieve the client sku name and version. +"""This module is from where we receive the client sku name and version. """ # The __init__.py will import this. Not the other way around. -__version__ = "1.35.0" +__version__ = "1.35.2rc1" SKU = "MSAL.Python"