diff --git a/.github/agents/release-notes-agent.md b/.github/agents/release-notes-agent.md new file mode 100644 index 000000000..acb1a63c7 --- /dev/null +++ b/.github/agents/release-notes-agent.md @@ -0,0 +1,261 @@ +# Release Notes Generator Agent + +This agent automatically generates organized release notes from a list of Pull Requests for Azure Functions Python Worker components. + +## Purpose + +Streamline the release notes creation process by: +1. Analyzing PR file changes to determine component relevance +2. Filtering PRs by component (worker, runtime v1, or runtime v2) +3. Grouping PRs by category based on conventional commit prefixes +4. Generating clean, human-readable release notes in Markdown format + +## How to Use + +### Basic Usage + +Invoke the agent with a list of PRs and specify the component: + +``` +@workspace /release-notes + +Component: worker + +## What's Changed +* fix: fix protobuf import for V2 by @hallvictoria in https://github.com/Azure/azure-functions-python-worker/pull/1736 +* feat: allow event loop to be uvloop by @EvanR-Dev in https://github.com/Azure/azure-functions-python-worker/pull/1697 +* build: update version to 4.40.0 by @hallvictoria in https://github.com/Azure/azure-functions-python-worker/pull/1771 +``` + +### Supported Components + +- **`worker`** - Azure Functions Python Worker +- **`runtime v1`** - Python Runtime V1 +- **`runtime v2`** - Python Runtime V2 + +### Input Format + +The agent accepts PR lists in the following format: +``` +* : by @ in +``` + +Example: +``` +* fix: improve error handling by @johndoe in https://github.com/Azure/azure-functions-python-worker/pull/1234 +``` + +You can also use shorthand: +``` +* feat: new feature by @janedoe in #1235 +``` + +## Component Detection Logic + +The agent analyzes changed files in each PR to determine relevance: + +| Path | Affects Component | +|------|------------------| +| `runtimes/v1/**` | Runtime V1 | +| `runtimes/v2/**` | Runtime V2 | +| `workers/**` | Worker | +| Other paths | All components | + +**Examples:** +- PR changes only `workers/dispatcher.py` → Only relevant to **worker** +- PR changes only `runtimes/v1/setup.py` → Only relevant to **runtime v1** +- PR changes `setup.cfg` (root level) → Relevant to **all components** +- PR changes both `workers/loader.py` and `runtimes/v2/handler.py` → Relevant to **worker** and **runtime v2** + +### Worker-Specific Version Updates + +**Important:** Some PRs are worker-specific and will ONLY appear in worker releases: + +1. **Azure Functions SDK updates** - PRs that update the `azure-functions` package in `workers/pyproject.toml` + - Example: "Update Python SDK Version to 1.24.0" + - These are **excluded** from runtime v1 and runtime v2 releases + +2. **Worker version updates** - PRs that update version in `workers/*/version.py` + - Example: "Update version to 4.40.1" + - These are **excluded** from runtime v1 and runtime v2 releases + +**Why?** These PRs only bump version numbers in the worker component and don't affect the runtime components, so they shouldn't appear in runtime release notes. + +### Version Update Deduplication + +**Important:** If multiple PRs update the same package to different versions, only the **latest version** is included. + +**Example:** If PRs update `azure-functions` from 1.24.0b4 → 1.25.0b2 → 1.25.0b3, only the final update to 1.25.0b3 appears in release notes. + +**Applies to:** +- `azure-functions` (Python SDK) +- `azure-functions-runtime` (Runtime V2) +- `azure-functions-runtime-v1` (Runtime V1) +- `azurefunctions-extensions-*` (Extension packages) + +**Why?** Only the final version matters. Intermediate version bumps add noise without value. + +## Category Classification + +PRs are grouped by their title prefix: + +| Prefix | Category | Example | +|--------|----------|---------| +| `feat:` | Features | New capabilities | +| `fix:` | Bug Fixes | Bug fixes and corrections | +| `build:` | Build & Dependencies | Version updates, dependencies | +| `refactor:` | Refactoring | Code restructuring | +| `test:` | Tests | Test changes | +| `chore:` | Chores | Maintenance, cleanup | +| `docs:` | Documentation | Doc updates | +| `perf:` | Performance | Performance improvements | +| `ci:` | CI/CD | Pipeline changes | + +## Output Format + +The agent generates release notes in this structure: + +**For Worker Releases:** + +```markdown +# Release Notes + +## General + +### Build & Dependencies +* Update pyproject for azure-functions 2.x structure ([#1840](https://github.com/Azure/azure-functions-python-worker/pull/1840)) - @hallvictoria + +## Worker Version +* Update version to 4.43.0 ([#1831](https://github.com/Azure/azure-functions-python-worker/pull/1831)) - @hallvictoria + +## Python <= 3.12 + +### Features +* Allow event loop to be uvloop ([#1697](https://github.com/Azure/azure-functions-python-worker/pull/1697)) - @EvanR-Dev + +### Bug Fixes +* Fix protobuf import for V2 ([#1736](https://github.com/Azure/azure-functions-python-worker/pull/1736)) - @hallvictoria + +### Build & Dependencies +* Update Python SDK Version to 1.25.0b4 ([#1832](https://github.com/Azure/azure-functions-python-worker/pull/1832)) - @hallvictoria + +## Python 3.13+ + +### Features +* Support Python 3.14 ([#1766](https://github.com/Azure/azure-functions-python-worker/pull/1766)) - @hallvictoria + +### Bug Fixes +* Fix default cx deps path for 3.13 ([#1833](https://github.com/Azure/azure-functions-python-worker/pull/1833)) - @hallvictoria + +### Build & Dependencies +* Update Python SDK Version to 2.0.0 ([#1841](https://github.com/Azure/azure-functions-python-worker/pull/1841)) - @hallvictoria + +## Runtime and SDK Versions +azure-functions-runtime==1.1.0 +azure-functions-runtime-v1==1.1.0 +azure-functions==1.24.0 (Python < 3.10) +azure-functions==1.25.0b4 (Python 3.10-3.12) +azure-functions==2.0.0 (Python 3.13+) +``` + +**For Runtime V1/V2 Releases:** + +```markdown +# Release Notes + +## Features +* Allow event loop to be uvloop ([#1697](https://github.com/Azure/azure-functions-python-worker/pull/1697)) - @EvanR-Dev + +## Bug Fixes +* Fix protobuf import for V2 ([#1736](https://github.com/Azure/azure-functions-python-worker/pull/1736)) - @hallvictoria +``` + +**Note:** Worker releases are organized into multiple sections: + +**Section order:** +1. **General** (optional): PRs that don't touch `workers/` directory (e.g., CI/CD, root configs) +2. **Worker Version**: Worker component version updates (e.g., "Update version to 4.43.0") +3. **Python <= 3.12**: Changes to `workers/azure_functions_worker/` +4. **Python 3.13+**: Changes to `workers/proxy_worker/` +5. **Runtime and SDK Versions**: Dependency versions at the END + +**Python version classification:** +- **General section**: PRs NOT touching any files under `workers/` +- **Worker Version section**: PRs with title "Update version to 4.X.X" +- **Python <= 3.12**: PRs affecting ONLY `workers/azure_functions_worker/` +- **Python 3.13+**: PRs affecting ONLY `workers/proxy_worker/` +- **Both Python sections**: PRs affecting files in both worker directories, or `workers/` general files (e.g., `workers/pyproject.toml`, `workers/README.md`) + +**Special handling for SDK updates:** +- "Update Python SDK Version" PRs are classified based on which Python version range uses that version in `workers/pyproject.toml` +- Example: Update to 2.0.0 (used by Python 3.13+) appears ONLY in Python 3.13+ section +- Example: Update to 1.25.0b4 (used by Python 3.10-3.12) appears ONLY in Python <= 3.12 section + +The `azure-functions` SDK shows different versions for different Python version ranges. + +Runtime releases do not include these sections or Python version splits. + +## Prerequisites + +- GitHub CLI installed and authenticated (`gh auth login`) +- Access to the Azure Functions Python Worker repository +- PRs must include full GitHub URLs or PR numbers if in the current repo + +## Output + +The agent will: +1. Display the formatted release notes in the chat +2. **Save the release notes to a temporary file** for easy copying: + - **Windows**: `%TEMP%\release-notes-.md` + - **Linux/Mac**: `/tmp/release-notes-.md` + +You can open the file directly or copy its contents to your clipboard. + +## Advanced Usage + +### Multiple Components + +Generate release notes for multiple components in one session: + +``` +@workspace /release-notes worker +[paste PR list] + +@workspace /release-notes runtime v1 +[paste PR list] + +@workspace /release-notes runtime v2 +[paste PR list] +``` + +### Handling Large PR Lists + +For releases with many PRs (30+), the agent will: +- Process in batches to avoid rate limits +- Show progress updates +- Report any PRs that couldn't be fetched + +### Cross-Component Changes + +PRs that affect multiple components will appear in the release notes for all relevant components. The agent will note when a PR appears in multiple component releases. + +## Troubleshooting + +### "Authentication failed" +Run `gh auth login` to authenticate with GitHub. + +### "PR not found" +Ensure you have access to the repository and the PR number is correct. + +### "No PRs match this component" +Verify: +- The component name is exactly `worker`, `runtime v1`, or `runtime v2` +- The PRs actually touch files in that component's directory +- You're analyzing the correct repository + +## Skill Details + +This agent uses the `generate-release-notes` skill located at: +`.agents/skills/generate-release-notes/SKILL.md` + +For detailed workflow and technical implementation, refer to the skill documentation. diff --git a/.github/agents/skills/generate-release-notes/QUICKSTART.md b/.github/agents/skills/generate-release-notes/QUICKSTART.md new file mode 100644 index 000000000..6c705d28a --- /dev/null +++ b/.github/agents/skills/generate-release-notes/QUICKSTART.md @@ -0,0 +1,135 @@ +# Quick Start: Release Notes Generator + +Generate organized release notes from PR lists in seconds. + +## Usage + +``` +@workspace Can you generate release notes for [component]? + +Component: [worker | runtime v1 | runtime v2] + +[Paste your PR list here] +``` + +## Example + +``` +@workspace Can you generate release notes for the worker? + +Component: worker + +## What's Changed +* fix: fix protobuf import for V2 by @hallvictoria in https://github.com/Azure/azure-functions-python-worker/pull/1736 +* feat: allow event loop to be uvloop by @EvanR-Dev in https://github.com/Azure/azure-functions-python-worker/pull/1697 +* build: update version to 4.40.0 by @hallvictoria in https://github.com/Azure/azure-functions-python-worker/pull/1771 +* refactor: Library worker renaming by @gavin-aguiar in https://github.com/Azure/azure-functions-python-worker/pull/1733 +* test: add ServiceBus SDK tests by @hallvictoria in https://github.com/Azure/azure-functions-python-worker/pull/1678 +``` + +## Component Detection + +The agent automatically determines which PRs are relevant by analyzing file changes: + +- **`workers/`** → Worker component +- **`runtimes/v1/`** → Runtime V1 component +- **`runtimes/v2/`** → Runtime V2 component +- **Other paths** → All components + +## Output + +You'll get formatted release notes like: + +**For Worker Releases:** + +```markdown +# Release Notes + +## General + +### Build & Dependencies +* Update pyproject structure ([#1840](link)) - @hallvictoria + +## Worker Version +* Update version to 4.43.0 ([#1831](link)) - @hallvictoria + +## Python <= 3.12 + +### Features +* Allow event loop to be uvloop ([#1697](link)) - @EvanR-Dev + +### Bug Fixes +* Fix protobuf import for V2 ([#1736](link)) - @hallvictoria + +### Build & Dependencies +* Update Python SDK Version to 1.25.0b4 ([#1832](link)) - @hallvictoria + +### Refactoring +* Library worker renaming ([#1733](link)) - @gavin-aguiar + +### Tests +* Add ServiceBus SDK tests ([#1678](link)) - @hallvictoria + +## Python 3.13+ + +### Features +* Support Python 3.14 ([#1766](link)) - @hallvictoria + +### Bug Fixes +* Fix default cx deps path for 3.13 ([#1833](link)) - @hallvictoria + +### Build & Dependencies +* Update Python SDK Version to 2.0.0 ([#1841](link)) - @hallvictoria + +## Runtime and SDK Versions +azure-functions-runtime==1.1.0 +azure-functions-runtime-v1==1.1.0 +azure-functions==1.24.0 (Python < 3.10) +azure-functions==1.25.0b4 (Python 3.10-3.12) +azure-functions==2.0.0 (Python 3.13+) +``` + +**For Runtime Releases:** + +```markdown +# Release Notes + +## Features +* Allow event loop to be uvloop ([#1697](link)) - @EvanR-Dev + +## Bug Fixes +* Fix protobuf import for V2 ([#1736](link)) - @hallvictoria +``` + +## Prerequisites + +- GitHub CLI installed: `gh auth status` +- Repository access +- PRs include GitHub URLs or PR numbers + +## Output File + +Release notes are automatically saved to a temporary file: +- **Windows**: `%TEMP%\release-notes-.md` +- **Linux/Mac**: `/tmp/release-notes-.md` + +You can easily open and copy from this file! + +## Tips + +✅ **DO:** +- Use conventional commit prefixes (`feat:`, `fix:`, `build:`, etc.) +- Specify exact component name +- Paste full PR list at once +- Include GitHub URLs + +❌ **DON'T:** +- Mix different formats +- Forget the component name +- Use abbreviated PR info + +## Need Help? + +See full documentation: [release-notes-agent.md](../release-notes-agent.md) + +Or ask: `@workspace How do I use the release notes generator?` diff --git a/.github/agents/skills/generate-release-notes/SKILL.md b/.github/agents/skills/generate-release-notes/SKILL.md new file mode 100644 index 000000000..a9fee4734 --- /dev/null +++ b/.github/agents/skills/generate-release-notes/SKILL.md @@ -0,0 +1,618 @@ +--- +name: generate-release-notes +description: > + Auto-generate release notes from a list of PRs for Azure Functions Python Worker + components (worker, runtime v1, or runtime v2). Analyzes PR file changes to determine + component relevance, groups by category/prefix, and outputs organized release notes. + Use when the user asks to "generate release notes", "create release notes from PRs", + "format these PRs for release", or provides a list of PRs to organize. +tags: + - release-notes + - changelog + - pull-request + - github +category: Development +--- + +# Generate Release Notes + +Auto-generate organized release notes from a list of PRs for Azure Functions Python Worker components. + +## Workflow + +### 1. Parse Input + +Accept: +- **List of PRs**: Each PR should include: + - PR title (with prefix like `fix:`, `feat:`, `build:`, etc.) + - PR link (full GitHub URL or `#number`) + - PR author (GitHub username) +- **Component**: One of: + - `worker` - Python Worker component + - `runtime v1` - Runtime V1 component + - `runtime v2` - Runtime V2 component + +Example input format: +``` +## What's Changed +* fix: fix protobuf import for V2 by @hallvictoria in https://github.com/Azure/azure-functions-python-worker/pull/1736 +* feat: allow event loop to be uvloop by @EvanR-Dev in https://github.com/Azure/azure-functions-python-worker/pull/1697 +... +``` + +### 2. Extract PR Information + +For each PR in the list, extract: +- PR number (from URL or `#number`) +- PR title +- PR author +- PR link + +Store this information for later use. + +### 3. Analyze PR File Changes + +For each PR, determine which files were changed: + +**Using GitHub CLI:** +```powershell +gh pr view --json files | ConvertFrom-Json +``` + +This returns the list of changed files in the PR. + +### 4. Determine Component Relevance + +For each PR, analyze the changed files and classify based on paths: + +| Path Pattern | Component | Rule | +|-------------|-----------|------| +| `runtimes/v1/**` | runtime v1 | Any file inside `runtimes/v1/` directory | +| `runtimes/v2/**` | runtime v2 | Any file inside `runtimes/v2/` directory | +| `workers/**` | worker | Any file inside `workers/` directory | +| Other paths | all | Changes outside these directories affect all components | + +**Component matching logic:** +- If only `runtimes/v1/` files changed → runtime v1 only +- If only `runtimes/v2/` files changed → runtime v2 only +- If only `workers/` files changed → worker only +- If files from multiple directories changed → all relevant components +- If files outside these three directories changed → all components (worker, runtime v1, runtime v2) + +**Worker-specific exclusions:** + +Some PRs should ONLY appear in worker releases, even if they change root-level files: + +1. **Azure Functions SDK version updates** - PRs that update the `azure-functions` package version in `workers/pyproject.toml` + - Example title patterns: "Update Python SDK Version to X.Y.Z" + - File changed: `workers/pyproject.toml` (dependency update) + - Rule: Include ONLY in worker releases + +2. **Worker version updates** - PRs that update the worker version in `workers/*/version.py` + - Example title patterns: "Update version to X.Y.Z" + - Files changed: `workers/azure_functions_worker/version.py` or similar + - Rule: Include ONLY in worker releases + +**Detection logic for worker-specific PRs:** +``` +If PR title matches "Update Python SDK Version" OR "Update version to": + Check if files changed include: + - workers/pyproject.toml (for SDK updates) + - workers/*/version.py (for worker version updates) + If yes: + - Include in worker release ONLY + - Exclude from runtime v1 and runtime v2 releases +``` + +**Filter PRs** based on the user-specified component: +- Keep PRs that affect the specified component +- Exclude PRs that don't touch any files in that component's scope +- For runtime v1/v2 releases: Also exclude worker-specific version update PRs + +### 5. Extract PR Category from Title + +Parse the PR title prefix to determine the category: + +| Prefix | Category | Description | +|--------|----------|-------------| +| `feat:` | Features | New features or capabilities | +| `fix:` | Bug Fixes | Bug fixes and corrections | +| `build:` | Build & Dependencies | Version updates, dependency changes, build configuration | +| `refactor:` | Refactoring | Code restructuring without behavior changes | +| `test:` | Tests | Test additions or modifications | +| `chore:` | Chores | Maintenance tasks, cleanup | +| `docs:` | Documentation | Documentation updates | +| `perf:` | Performance | Performance improvements | +| `ci:` | CI/CD | Continuous integration/deployment changes | +| `style:` | Style | Code style, formatting | +| `revert:` | Reverts | Reverting previous changes | + +If no recognized prefix is found, use the first word or classify as "Other". + +### 6. Extract Runtime and SDK Versions (Worker Releases Only) + +**For worker component releases only**, read the current versions from `workers/pyproject.toml`: + +Extract the versions of these packages: +- `azure-functions-runtime` +- `azure-functions-runtime-v1` +- `azure-functions` (with Python version conditions) + +**Important:** The `azure-functions` SDK has different versions for different Python version ranges. Extract all variants. + +**Using PowerShell:** +```powershell +$pyprojectContent = Get-Content workers/pyproject.toml -Raw + +# Extract runtime versions (single version each) +$runtimeMatch = [regex]::Match($pyprojectContent, 'azure-functions-runtime==([^;"\s]+)') +$runtimeV1Match = [regex]::Match($pyprojectContent, 'azure-functions-runtime-v1==([^;"\s]+)') + +# Extract all azure-functions SDK versions with their Python version conditions +$sdkMatches = [regex]::Matches($pyprojectContent, '"azure-functions==([^;"]+);\s*([^"]+)"') +$sdkVersions = @() +foreach ($match in $sdkMatches) { + $version = $match.Groups[1].Value + $condition = $match.Groups[2].Value + $sdkVersions += @{Version=$version; Condition=$condition} +} +``` + +**Using Python:** +```python +import re +with open('workers/pyproject.toml', 'r') as f: + content = f.read() + + # Extract runtime versions + runtime = re.search(r'azure-functions-runtime==([^;"\s]+)', content) + runtime_v1 = re.search(r'azure-functions-runtime-v1==([^;"\s]+)', content) + + # Extract all azure-functions versions with Python conditions + sdk_pattern = r'"azure-functions==([^;"]+);\s*([^"]+)"' + sdk_matches = re.findall(sdk_pattern, content) + # sdk_matches = [(version, condition), ...] + # Example: [('1.24.0', "python_version < '3.10'"), ('1.25.0b4', "python_version >= '3.10' and python_version < '3.13'")] +``` + +**Format for output:** +- Runtime versions: single line each +- SDK versions: one line per Python version range with the condition in a human-readable format + +Store these versions for inclusion in the output. + +**Note:** Skip this step for runtime v1 and runtime v2 releases. + +### 7. Classify PRs by Python Version (Worker Releases Only) + +**For worker component releases only**, classify each PR by which Python versions it affects: + +**Python version classification rules:** + +1. **Special case - Worker version updates:** + - PRs with title "Update version to 4.X.X" (worker component version) + - These go in a separate "Worker Version" section + - Example: "Update version to 4.43.0" + +2. **Check if PR touches worker code:** + - If PR does NOT change any files under `workers/` directory → **General/Build section** + - These are repo-wide changes that don't affect worker code directly + - Examples: root-level config changes, CI/CD changes, documentation + +3. **Check file paths for worker code changes:** + - **Python <= 3.12 only**: PR changes ONLY files under `workers/azure_functions_worker/` + - **Python 3.13+ only**: PR changes ONLY files under `workers/proxy_worker/` + - **Both**: PR changes files in both directories, OR changes files under `workers/` that affect both (e.g., `workers/pyproject.toml`, `workers/README.md`) + +4. **Special handling for SDK version updates:** + - PRs with title "Update Python SDK Version to X.Y.Z" need additional classification + - Read `workers/pyproject.toml` to determine which Python version range uses this version + - Example: If updating to 2.0.0 and pyproject shows `azure-functions==2.0.0; python_version >= '3.13'`, then this PR is **Python 3.13+ only** + - Example: If updating to 1.25.0b4 and pyproject shows `azure-functions==1.25.0b4; python_version >= '3.10' and python_version < '3.13'`, then this PR is **Python <= 3.12 only** + +**Classification logic:** +```python +for pr in filtered_prs: + files = get_pr_files(pr.number) + + # Check if it's a worker version update + if re.match(r'[Uu]pdate\s+version\s+to\s+4\.\d+\.\d+', pr.title): + pr.section = 'worker_version' + continue + + # Check if PR touches workers/ directory at all + touches_workers = any(f.startswith('workers/') for f in files) + + if not touches_workers: + # General/build changes that don't touch worker code + pr.section = 'general' + continue + + # Check if it's an SDK version update + if 'Update Python SDK Version' in pr.title: + version = extract_version_from_title(pr.title) + pyproject = read_pyproject() + python_range = find_python_range_for_sdk_version(pyproject, version) + + if '3.13' in python_range and '3.10' not in python_range: + pr.python_versions = ['3.13+'] + elif '3.10' in python_range or '3.12' in python_range: + pr.python_versions = ['<=3.12'] + else: + pr.python_versions = ['<=3.12', '3.13+'] + else: + # Regular file-based classification for worker code + affects_legacy = any(f.startswith('workers/azure_functions_worker/') for f in files) + affects_proxy = any(f.startswith('workers/proxy_worker/') for f in files) + affects_workers_general = any( + f.startswith('workers/') and + not f.startswith('workers/azure_functions_worker/') and + not f.startswith('workers/proxy_worker/') + for f in files + ) + + if affects_legacy and not affects_proxy and not affects_workers_general: + pr.python_versions = ['<=3.12'] + elif affects_proxy and not affects_legacy and not affects_workers_general: + pr.python_versions = ['3.13+'] + else: + # Both directories or workers/ general files + pr.python_versions = ['<=3.12', '3.13+'] +``` + +**Notes:** +- PRs affecting both worker directories appear in both Python version sections +- PRs affecting `workers/` general files (e.g., `workers/pyproject.toml`) appear in both Python sections +- PRs NOT touching `workers/` at all go in the "General" section +- Worker version update PRs go in the "Worker Version" section +- SDK version updates are classified based on the Python version range in pyproject.toml +- This classification is ONLY for worker releases, not runtime releases + +### 8. Group PRs by Category + +Organize filtered PRs into categories based on their prefix. + +Recommended category order for output: +1. Features +2. Bug Fixes +3. Build & Dependencies +4. Refactoring +5. Tests +6. Chores +7. Other categories (alphabetically) + +### 9. Deduplicate Version Update PRs + +**For Build & Dependencies category only**, deduplicate PRs that update the same package multiple times: + +**Package patterns to check:** +- `Update Python SDK Version to X.Y.Z` → package: `azure-functions` +- `Update Python Runtime Version to X.Y.Z` → package: `azure-functions-runtime` +- `Update version to X.Y.Z` (runtime v1) → package: `azure-functions-runtime-v1` +- `Update azurefunctions-extensions- version to X.Y.Z` → package: `azurefunctions-extensions-` +- `Update version to X.Y.Z` → package: `` + +**Deduplication logic:** +1. Parse each PR title in the Build & Dependencies category +2. Extract the package name using these patterns: + - Title contains "Python SDK Version" → `azure-functions` + - Title contains "Python Runtime Version" and not "v1" → `azure-functions-runtime` + - Title contains "Python Runtime Version" and "v1" → `azure-functions-runtime-v1` + - Title matches "Update azurefunctions-extensions- version" → extract `azurefunctions-extensions-` + - Title matches "Update version" → extract package name +3. Group PRs by package name +4. For each package with multiple updates, keep only the PR with the **highest PR number** (most recent) +5. Discard older version updates for the same package + +**Example:** +``` +Input PRs (Build & Dependencies): +- Update Python SDK Version to 1.24.0b4 (#1755) +- Update Python SDK Version to 1.25.0b2 (#1798) +- Update Python SDK Version to 1.25.0b3 (#1822) +- Update azurefunctions-extensions-blob version to 1.1.1 (#1762) +- Update azurefunctions-extensions-blob version to 1.1.2 (#1821) + +After deduplication: +- Update Python SDK Version to 1.25.0b3 (#1822) ✓ (highest PR for azure-functions) +- Update azurefunctions-extensions-blob version to 1.1.2 (#1821) ✓ (highest PR for extensions-blob) + +Removed: +- #1755, #1798 (older azure-functions updates) +- #1762 (older extensions-blob update) +``` + +**Implementation approach:** +```python +# Pseudocode +package_updates = {} +for pr in build_dependency_prs: + package_name = extract_package_name(pr.title) + if package_name: + if package_name not in package_updates or pr.number > package_updates[package_name].number: + package_updates[package_name] = pr + +# Keep only the deduplicated PRs +deduplicated_prs = list(package_updates.values()) +``` + +**Note:** Only apply deduplication to PRs that match version update patterns. Keep other build/dependency PRs as-is. + +### 10. Format Output + +Generate clean, human-readable release notes in Markdown format. + +**For worker releases:** + +```markdown +# Release Notes + +## General + +### Features +* ([#<number>](<link>)) - @<author> +* ... + +### Bug Fixes +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Build & Dependencies +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +[... other categories ...] + +## Worker Version +* <title without prefix> ([#<number>](<link>)) - @<author> + +## Python <= 3.12 + +### Features +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Bug Fixes +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Build & Dependencies +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Refactoring +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Tests +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Chores +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +## Python 3.13+ + +### Features +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Bug Fixes +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Build & Dependencies +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Refactoring +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Tests +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +### Chores +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +## Runtime and SDK Versions +azure-functions-runtime==<version> +azure-functions-runtime-v1==<version> +azure-functions==<version> (Python < 3.10) +azure-functions==<version> (Python 3.10-3.12) +azure-functions==<version> (Python 3.13+) +``` + +**For runtime v1/v2 releases:** + +```markdown +# Release Notes + +## Features +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +## Bug Fixes +* <title without prefix> ([#<number>](<link>)) - @<author> +* ... + +[... other categories ...] +``` + +**Formatting rules:** +- **Worker releases only:** + - Section order: + 1. **General**: PRs not touching `workers/` directory (optional - only if PRs exist) + 2. **Worker Version**: Worker version update PRs (e.g., "Update version to 4.43.0") + 3. **Python <= 3.12**: PRs affecting `azure_functions_worker/` + 4. **Python 3.13+**: PRs affecting `proxy_worker/` + 5. **Runtime and SDK Versions**: At the END + - Within each section (except Worker Version and Runtime sections), organize by category (Features, Bug Fixes, etc.) + - PRs affecting both Python worker directories appear in both Python sections + - Only include sections and categories that have PRs +- Remove the prefix from the title (e.g., `fix: ` → ``) +- Capitalize the first letter of the title +- Include PR link in standard format `[#number](full-url)` +- Include author attribution with `@username` +- Sort PRs within each category by PR number (ascending) + +### 11. Save to Temporary File + +**Save the formatted release notes to a temporary markdown file** for easy copying: + +**File location:** +- Windows: `$env:TEMP\release-notes-<component>.md` +- Linux/Mac: `/tmp/release-notes-<component>.md` + +**Example:** +- Worker release: `release-notes-worker.md` +- Runtime v1 release: `release-notes-runtime-v1.md` +- Runtime v2 release: `release-notes-runtime-v2.md` + +**Using PowerShell:** +```powershell +$component = "worker" # or "runtime-v1" or "runtime-v2" +$outputPath = Join-Path $env:TEMP "release-notes-$component.md" +$releaseNotes | Out-File -FilePath $outputPath -Encoding utf8 +Write-Host "Release notes saved to: $outputPath" +``` + +**Using Python:** +```python +import tempfile +import os + +component = "worker" # or "runtime-v1" or "runtime-v2" +temp_dir = tempfile.gettempdir() +output_path = os.path.join(temp_dir, f"release-notes-{component}.md") + +with open(output_path, 'w', encoding='utf-8') as f: + f.write(release_notes) + +print(f"Release notes saved to: {output_path}") +``` + +**After saving:** +- Display the file path to the user +- Optionally open the file in the default editor or VS Code +- User can easily copy the contents from the file + +### 12. Handle Edge Cases + +**PR without recognizable prefix:** +- Place in "Other" category +- Keep original title + +**PR affects multiple components:** +- Include in all relevant component release notes +- Note in output if the same PR appears in multiple components + +**Large PR lists (>30 PRs):** +- Process in batches to avoid rate limits +- Show progress updates to user + +**Authentication errors:** +- Check `gh auth status` +- Prompt user to authenticate if needed + +**PR not found:** +- Note the PR number that couldn't be fetched +- Continue with remaining PRs +- Report missing PRs at the end + +## Example Workflow + +### Example 1: Worker Release + +User provides: +``` +Component: worker + +PRs: +* fix: fix protobuf import by @user1 in https://github.com/org/repo/pull/100 +* feat: add new feature for proxy worker by @user2 in https://github.com/org/repo/pull/101 +* fix: legacy worker bug fix by @user3 in https://github.com/org/repo/pull/105 +* build: update version to 4.40.0 by @user4 in https://github.com/org/repo/pull/102 +* build: update Python SDK Version to 1.24.0b4 by @user5 in https://github.com/org/repo/pull/103 +* build: update Python SDK Version to 1.25.0b2 by @user6 in https://github.com/org/repo/pull/150 +* build: update Python SDK Version to 1.25.0b3 by @user7 in https://github.com/org/repo/pull/160 +* build: update pyproject for azure-functions 2.x structure by @user8 in https://github.com/org/repo/pull/200 +``` + +Agent: +1. Extracts PR numbers: 100, 101, 102, 103, 105, 150, 160, 200 +2. Fetches file changes for each PR +3. PR #100: Changed `setup.cfg` (root level, not in workers/) → General section +4. PR #101: Changed `workers/proxy_worker/handler.py` → Python 3.13+ only +5. PR #102: Changed `workers/azure_functions_worker/version.py` + title matches \"Update version to 4.X.X\" → Worker Version section +6. PR #103, #150, #160: Changed `workers/pyproject.toml` (SDK updates) → Check version in pyproject +7. PR #105: Changed `workers/azure_functions_worker/dispatcher.py` → Python <= 3.12 only +8. PR #200: Changed `.github/workflows/ci.yml` (not in workers/) → General section +9. **Classifies PRs by section:**\n - PR #100: Root-level file, not in workers/ → General section\n - PR #101: Only `proxy_worker/` → Python 3.13+ only\n - PR #102: Title \"Update version to 4.40.0\" → Worker Version section\n - PR #103, #150, #160: SDK updates - check pyproject.toml:\n - If version 1.25.0b3 is for Python 3.10-3.12 → Python <= 3.12 only\n - PR #105: Only `azure_functions_worker/` → Python <= 3.12 only\n - PR #200: CI file, not in workers/ → General section\n10. **Deduplicates version updates:**\n - Package `azure-functions`: PRs #103, #150, #160\n - Keeps only #160 (highest PR number)\n - Removes #103 and #150\n11. **Reads `workers/pyproject.toml` to extract versions:**\n - `azure-functions-runtime==1.1.0`\n - `azure-functions-runtime-v1==1.1.0`\n - `azure-functions==1.24.0` (Python < 3.10)\n - `azure-functions==1.25.0b4` (Python 3.10-3.12)\n - `azure-functions==2.0.0` (Python 3.13+)\n12. Groups by section and category, then outputs:\n\n```markdown\n# Release Notes\n\n## General\n\n### Bug Fixes\n* Fix protobuf import ([#100](link)) - @user1\n\n### Build & Dependencies\n* Update pyproject for azure-functions 2.x structure ([#200](link)) - @user8\n\n## Worker Version\n* Update version to 4.40.0 ([#102](link)) - @user4\n\n## Python <= 3.12\n\n### Bug Fixes\n* Legacy worker bug fix ([#105](link)) - @user3\n\n### Build & Dependencies\n* Update Python SDK Version to 1.25.0b3 ([#160](link)) - @user7\n\n## Python 3.13+\n\n### Features\n* Add new feature for proxy worker ([#101](link)) - @user2\n\n## Runtime and SDK Versions\nazure-functions-runtime==1.1.0\nazure-functions-runtime-v1==1.1.0\nazure-functions==1.24.0 (Python < 3.10)\nazure-functions==1.25.0b4 (Python 3.10-3.12)\nazure-functions==2.0.0 (Python 3.13+)\n```\n\n**Note:** \n- PR #100 and #200 in General section (don't touch workers/ directory)\n- PR #102 in Worker Version section (matches \"Update version to 4.X.X\")\n- PR #105 only in Python <= 3.12 (only affects `azure_functions_worker/`)\n- PR #101 only in Python 3.13+ (only affects `proxy_worker/`)\n- PR #160 only in Python <= 3.12 (SDK version 1.25.0b3 is for Python 3.10-3.12)\n- PRs #103 and #150 were excluded due to deduplication\n- Runtime and SDK Versions section is at the END + +### Example 2: Runtime V2 Release with Deduplication + +User provides: +``` +Component: runtime v2 + +PRs: +* fix: fix protobuf import by @user1 in https://github.com/org/repo/pull/100 +* feat: add new feature by @user2 in https://github.com/org/repo/pull/101 +* build: update version to 1.0.0b1 by @user3 in https://github.com/org/repo/pull/110 +* build: update version to 1.0.0b2 by @user4 in https://github.com/org/repo/pull/120 +* build: update version to 1.1.0b1 by @user5 in https://github.com/org/repo/pull/130 +* build: update azurefunctions-extensions-blob version to 1.1.1 by @user6 in https://github.com/org/repo/pull/140 +* build: update azurefunctions-extensions-blob version to 1.1.2 by @user7 in https://github.com/org/repo/pull/145 +* build: update Python SDK Version to 1.24.0 by @user8 in https://github.com/org/repo/pull/150 +``` + +Agent: +1. Extracts PR numbers: 100, 101, 110, 120, 130, 140, 145, 150 +2. Fetches file changes for each PR +3. PR #100: Changed `setup.cfg` (root level) → all components ✓ +4. PR #101: Changed `runtimes/v2/handler.py` → runtime v2 ✓ +5. PR #110, #120, #130: Changed `runtimes/v2/version.py` → runtime v2 ✓ +6. PR #140, #145: Changed `runtimes/v2/pyproject.toml` (extensions-blob) → runtime v2 ✓ +7. PR #150: Changed `workers/pyproject.toml` (azure-functions) → worker-specific only ✗ (exclude) +8. **Deduplicates version updates:** + - Package `azure-functions-runtime-v2`: PRs #110, #120, #130 → Keeps only #130 + - Package `azurefunctions-extensions-blob`: PRs #140, #145 → Keeps only #145 +9. Groups by category and outputs: + +```markdown +# Release Notes + +## Features +* Add new feature ([#101](link)) - @user2 + +## Bug Fixes +* Fix protobuf import ([#100](link)) - @user1 + +## Build & Dependencies +* Update version to 1.1.0b1 ([#130](link)) - @user5 +* Update azurefunctions-extensions-blob version to 1.1.2 ([#145](link)) - @user7 +``` + +**Notes:** +- PRs #110 and #120 were excluded (older `azure-functions-runtime-v2` updates) +- PR #140 was excluded (older `azurefunctions-extensions-blob` update) +- PR #150 was excluded (worker-specific `azure-functions` SDK update) + +## Tips for Best Results + +- Ensure PRs follow conventional commit format with prefixes +- Have GitHub CLI installed and authenticated (`gh auth login`) +- Provide clear component name (exact match: `worker`, `runtime v1`, or `runtime v2`) +- For PRs from different repos, include full URLs +- If the repo is not the current workspace, the agent will need to clone or access it +- The release notes will be saved to a temporary file for easy copying: `$env:TEMP\release-notes-<component>.md` (Windows) or `/tmp/release-notes-<component>.md` (Linux/Mac) + +## Notes + +- This skill is specific to the Azure Functions Python Worker repository structure +- Assumes repository access permissions for fetching PR data +- Can be adapted for other repositories by modifying the component path patterns in Step 4 diff --git a/.github/agents/skills/generate-release-notes/category-reference.md b/.github/agents/skills/generate-release-notes/category-reference.md new file mode 100644 index 000000000..7927a16a5 --- /dev/null +++ b/.github/agents/skills/generate-release-notes/category-reference.md @@ -0,0 +1,249 @@ +# Release Notes Category Reference + +This document provides the category classification rules for PR prefixes when generating release notes. + +## Standard Categories + +### Features (`feat:`) +**Description:** New features, capabilities, or enhancements that add functionality. + +**Examples:** +- `feat: allow event loop to be uvloop` +- `feat: support python 3.14` +- `feat: support sb settlement client` + +**Keywords:** add, support, implement, introduce, enable + +--- + +### Bug Fixes (`fix:`) +**Description:** Bug fixes, error corrections, and issue resolutions. + +**Examples:** +- `fix: fix protobuf import for V2` +- `fix: improve function error messages` +- `fix: 3.13 AppInsights logging` + +**Keywords:** fix, correct, resolve, repair, patch + +--- + +### Build & Dependencies (`build:`) +**Description:** Version updates, dependency changes, build configuration, and release preparation. + +**Examples:** +- `build: update version to 4.40.0` +- `build: update Python SDK Version to 1.24.0` +- `build: use official grpc for python 3.14` + +**Keywords:** update, upgrade, bump, version, dependency + +**Special Note:** Some build PRs are worker-specific: +- PRs updating `azure-functions` package in `workers/pyproject.toml` (e.g., "Update Python SDK Version") +- PRs updating worker version in `workers/*/version.py` (e.g., "Update version to X.Y.Z") +- These PRs should ONLY appear in worker releases, not runtime v1 or runtime v2 releases + +**Deduplication:** When multiple PRs update the same package (e.g., multiple `azure-functions` version updates), only the latest version (highest PR number) is included in the release notes. Older version updates for the same package are automatically excluded. + +--- + +### Refactoring (`refactor:`) +**Description:** Code restructuring, reorganization, or improvements without changing behavior. + +**Examples:** +- `refactor: Library worker renaming` +- `refactor: Moving threadpool configs to the library worker` + +**Keywords:** refactor, rename, move, restructure, reorganize + +--- + +### Tests (`test:`) +**Description:** Test additions, modifications, or test infrastructure changes. + +**Examples:** +- `test: add ServiceBus SDK tests` +- `test: add 3.14 to tests` +- `test: add test only using worker dependencies` + +**Keywords:** test, spec, e2e, integration test, unit test + +--- + +### Chores (`chore:`) +**Description:** Maintenance tasks, cleanup, tooling, or non-functional changes. + +**Examples:** +- `chore: add EOL log` +- `chore: validate artifact` +- `chore: remove 3.7 & 3.8 from worker nuget` + +**Keywords:** chore, cleanup, remove, deprecate, housekeeping + +--- + +### Documentation (`docs:`) +**Description:** Documentation updates, README changes, or comment improvements. + +**Examples:** +- `docs: update API documentation` +- `docs: fix typo in README` +- `docs: add migration guide` + +**Keywords:** docs, documentation, readme, guide + +--- + +### Performance (`perf:`) +**Description:** Performance improvements and optimizations. + +**Examples:** +- `perf: optimize function loading` +- `perf: reduce memory allocation` + +**Keywords:** perf, optimize, performance, speed, faster + +--- + +### CI/CD (`ci:`) +**Description:** Continuous integration, deployment pipeline, or workflow changes. + +**Examples:** +- `ci: add 3.14 to tests` +- `ci: allow downloading artifact from partially successful pipelines` + +**Keywords:** ci, pipeline, workflow, action, deploy + +--- + +## Category Priority Order + +When outputting release notes, use this order: + +1. **Features** - Most visible to users +2. **Bug Fixes** - Important for stability +3. **Build & Dependencies** - Affects compatibility +4. **Refactoring** - Code quality improvements +5. **Tests** - Quality assurance +6. **Chores** - Maintenance +7. **Documentation** - Information updates +8. **Performance** - Optimization +9. **CI/CD** - Process improvements +10. **Other** - Anything that doesn't fit above + +## Handling Edge Cases + +### No Prefix +If a PR title has no recognizable prefix: +- Classify as "Other" +- Keep the original title intact +- Example: `Update documentation` → Other category + +### Multiple Prefixes +If a PR title contains multiple prefixes (rare): +- Use the first prefix +- Example: `build: fix: update grpc` → Build & Dependencies + +### Custom Prefixes +For project-specific prefixes not in the standard list: +- Create a new category using the prefix name +- Capitalize the first letter +- Place alphabetically after standard categories +- Example: `security: add vulnerability scan` → Security category + +### Alternative Prefix Styles +Some teams use different styles: +- **Emoji prefixes:** `✨ add new feature` → Features +- **[Type] format:** `[FEATURE] add support` → Features +- **JIRA references:** `PROJ-123: fix bug` → Extract after the colon + +When in doubt, look for keywords in the title to infer the category. + +## Consolidation Rules + +Some categories can be merged for cleaner output: + +### Build & Dependencies +Combine these into one category: +- `build:` +- `deps:` +- `dependency:` +- Version updates + +### Bug Fixes +Combine these into one category: +- `fix:` +- `bugfix:` +- `hotfix:` + +### Documentation +Combine these into one category: +- `docs:` +- `doc:` +- `documentation:` + +## Title Formatting + +When displaying in release notes: +1. **Remove the prefix:** `fix: improve error messages` → `improve error messages` +2. **Capitalize first letter:** `improve error messages` → `Improve error messages` +3. **Keep original casing for proper nouns:** `python 3.14` → `Python 3.14` +4. **Preserve technical terms:** `gRPC`, `AppInsights`, `ServiceBus` + +## Real-World Examples + +From actual Azure Functions Python Worker releases: + +```markdown +## Features +* Allow event loop to be uvloop ([#1697](link)) - @EvanR-Dev +* Support Python 3.14 ([#1766](link)) - @hallvictoria +* Support ServiceBus settlement client ([#1763](link)) - @hallvictoria + +## Bug Fixes +* Fix protobuf import for V2 ([#1736](link)) - @hallvictoria +* Logging fix for Python 3.13 ([#1745](link)) - @gavin-aguiar +* Improve function error messages ([#1743](link)) - @hallvictoria + +## Build & Dependencies +* Update version to 4.40.0 ([#1771](link)) - @hallvictoria +* Update Python SDK Version to 1.24.0 ([#1755](link)) - @hallvictoria +* Update azurefunctions-extensions-base to 1.1.0 ([#1765](link)) - @hallvictoria +``` + +## Deduplication Rules + +### Version Update Deduplication + +When generating release notes, if multiple PRs update the same package to different versions, only the **latest version** (highest PR number) is included. + +**Packages that are deduplicated:** +- `azure-functions` (Python SDK) +- `azure-functions-runtime` (Runtime V2) +- `azure-functions-runtime-v1` (Runtime V1) +- `azurefunctions-extensions-*` (Extension packages like blob, servicebus, etc.) + +**Example:** + +**Input PRs:** +```markdown +* Update Python SDK Version to 1.24.0b4 ([#1755](link)) - @hallvictoria +* Update Python SDK Version to 1.25.0b2 ([#1798](link)) - @hallvictoria +* Update Python SDK Version to 1.25.0b3 ([#1822](link)) - @hallvictoria +* Update azurefunctions-extensions-blob version to 1.1.1 ([#1762](link)) - @hallvictoria +* Update azurefunctions-extensions-blob version to 1.1.2 ([#1821](link)) - @hallvictoria +``` + +**Output (after deduplication):** +```markdown +* Update Python SDK Version to 1.25.0b3 ([#1822](link)) - @hallvictoria +* Update azurefunctions-extensions-blob version to 1.1.2 ([#1821](link)) - @hallvictoria +``` + +**Excluded PRs:** +- #1755 and #1798 (older `azure-functions` versions) +- #1762 (older `azurefunctions-extensions-blob` version) + +**Why?** Only the final version matters in release notes. Including all intermediate version bumps creates noise without adding value. + +**Important:** Deduplication only applies to version updates. Other build PRs (e.g., "Use official grpc for python 3.14") are kept regardless of duplicates. diff --git a/eng/ci/worker-release.yml b/eng/ci/worker-release.yml index fc8c84951..f7eef48ff 100644 --- a/eng/ci/worker-release.yml +++ b/eng/ci/worker-release.yml @@ -13,6 +13,14 @@ parameters: displayName: 'V2 Library Release (PyPI)' type: boolean default: false + - name: V2LibraryVersion + displayName: 'V2 Library version to release (e.g., 0.1.0)' + type: string + default: '' + - name: V1LibraryVersion + displayName: 'V1 Library version to release (e.g., 0.1.0)' + type: string + default: '' resources: repositories: @@ -43,25 +51,79 @@ extends: jobs: - template: /eng/templates/official/jobs/publish-release.yml@self condition: eq(${{ parameters.WorkerRelease }}, True) - - stage: ReleasePythonV2Library + - stage: ReleasePythonV2Library_BumpVersion dependsOn: [] - displayName: 'Release V2 Library (PyPI)' + displayName: 'V2 Library - Bump Version' + condition: eq(${{ parameters.V2LibraryRelease }}, True) jobs: - - template: /eng/templates/official/jobs/publish-library-release.yml@self + - template: /eng/templates/official/release/bump-version.yml@self parameters: - PROJECT_DIRECTORY: 'runtimes/v2' - VERSION_FILE: 'runtimes/v2/azure_functions_runtime/version.py' - PROJECT_NAME: 'azure-functions-runtime' - BRANCH_NAME: 'release-v2' + libraryVersion: ${{ parameters.V2LibraryVersion }} + projectDisplayName: 'Azure Functions Runtime V2' + versionFilePath: 'runtimes/v2/azure_functions_runtime/version.py' + releaseBranchPrefix: 'release-v2' + createPullRequest: true + baseBranch: 'dev' + - stage: ReleasePythonV2Library_Build + dependsOn: ReleasePythonV2Library_BumpVersion + displayName: 'V2 Library - Build' condition: eq(${{ parameters.V2LibraryRelease }}, True) - - stage: ReleasePythonV1Library + jobs: + - template: /eng/templates/official/release/build-artifacts.yml@self + parameters: + artifactName: 'azure-functions-runtime' + projectDisplayName: 'Azure Functions Runtime V2' + libraryVersion: ${{ parameters.V2LibraryVersion }} + releaseBranchPrefix: 'release-v2' + projectDirectory: 'runtimes/v2' + - stage: ReleasePythonV2Library_Release + dependsOn: ReleasePythonV2Library_Build + displayName: 'V2 Library - Release (PyPI)' + condition: eq(${{ parameters.V2LibraryRelease }}, True) + jobs: + - template: /eng/templates/official/release/publish-release.yml@self + parameters: + libraryVersion: ${{ parameters.V2LibraryVersion }} + projectDisplayName: 'Azure Functions Runtime V2' + releaseBranchPrefix: 'release-v2' + tagPrefix: 'runtimes/v2-' + blobPath: 'azure-functions/python/azure-functions-runtime' + artifactName: 'azure-functions-runtime' + - stage: ReleasePythonV1Library_BumpVersion dependsOn: [] - displayName: 'Release V1 Library (PyPI)' + displayName: 'V1 Library - Bump Version' + condition: eq(${{ parameters.V1LibraryRelease }}, True) + jobs: + - template: /eng/templates/official/release/bump-version.yml@self + parameters: + libraryVersion: ${{ parameters.V1LibraryVersion }} + projectDisplayName: 'Azure Functions Runtime V1' + versionFilePath: 'runtimes/v1/azure_functions_runtime_v1/version.py' + releaseBranchPrefix: 'release-v1' + createPullRequest: true + baseBranch: 'dev' + - stage: ReleasePythonV1Library_Build + dependsOn: ReleasePythonV1Library_BumpVersion + displayName: 'V1 Library - Build' + condition: eq(${{ parameters.V1LibraryRelease }}, True) + jobs: + - template: /eng/templates/official/release/build-artifacts.yml@self + parameters: + artifactName: 'azure-functions-runtime-v1' + projectDisplayName: 'Azure Functions Runtime V1' + libraryVersion: ${{ parameters.V1LibraryVersion }} + releaseBranchPrefix: 'release-v1' + projectDirectory: 'runtimes/v1' + - stage: ReleasePythonV1Library_Release + dependsOn: ReleasePythonV1Library_Build + displayName: 'V1 Library - Release (PyPI)' + condition: eq(${{ parameters.V1LibraryRelease }}, True) jobs: - - template: /eng/templates/official/jobs/publish-library-release.yml@self + - template: /eng/templates/official/release/publish-release.yml@self parameters: - PROJECT_DIRECTORY: 'runtimes/v1' - VERSION_FILE: 'runtimes/v1/azure_functions_runtime_v1/version.py' - PROJECT_NAME: 'azure-functions-runtime-v1' - BRANCH_NAME: 'release-v1' - condition: eq(${{ parameters.V1LibraryRelease }}, True) \ No newline at end of file + libraryVersion: ${{ parameters.V1LibraryVersion }} + projectDisplayName: 'Azure Functions Runtime V1' + releaseBranchPrefix: 'release-v1' + tagPrefix: 'runtimes/v1-' + blobPath: 'azure-functions/python/azure-functions-runtime-v1' + artifactName: 'azure-functions-runtime-v1' \ No newline at end of file diff --git a/eng/templates/official/jobs/publish-library-release.yml b/eng/templates/official/jobs/publish-library-release.yml deleted file mode 100644 index 345ec2fe4..000000000 --- a/eng/templates/official/jobs/publish-library-release.yml +++ /dev/null @@ -1,108 +0,0 @@ -parameters: - PROJECT_DIRECTORY: '' - VERSION_FILE: '' - PROJECT_NAME: '' - BRANCH_NAME: '' - -jobs: - -- job: "CreateReleaseBranch" - displayName: 'Create Release Branch' - pool: - name: 1es-pool-azfunc - image: 1es-ubuntu-22.04 - os: linux - steps: - - template: /eng/templates/shared/github-release-branch.yml@self - parameters: - PROJECT_DIRECTORY: ${{ parameters.PROJECT_DIRECTORY }} - PROJECT_NAME: ${{ parameters.PROJECT_NAME }} - VERSION_FILE: ${{ parameters.VERSION_FILE }} - BRANCH_NAME: ${{ parameters.BRANCH_NAME }} - -- job: "CheckReleaseBranch" - dependsOn: ['CreateReleaseBranch'] - displayName: '(Manual) Check Release Branch' - pool: server - steps: - - task: ManualValidation@1 - displayName: '(Optional) Modify release branch' - inputs: - notifyUsers: '' # No email notifications sent - instructions: | - 1. Check if the https://github.com/Azure/azure-functions-python-worker build succeeds and passes all unit tests. - 2. If not, modify the release branch. - 3. Ensure branch contains all necessary changes. - -- job: "CreateReleaseTag" - dependsOn: ['CheckReleaseBranch'] - steps: - - template: /eng/templates/shared/github-release-note.yml@self - parameters: - BRANCH_NAME: ${{ parameters.BRANCH_NAME }} - PROJECT_DIRECTORY: ${{ parameters.PROJECT_DIRECTORY }} - -- job: "CheckGitHubRelease" - dependsOn: ['CreateReleaseTag'] - displayName: '(Manual) Check GitHub release note' - pool: server - steps: - - task: ManualValidation@1 - displayName: 'Write GitHub release note' - inputs: - notifyUsers: '' - instructions: 'Please head to https://github.com/Azure/azure-functions-python-worker/releases to finish the release note' - -- job: "PyPIPackage" - dependsOn: ['CheckGitHubRelease'] - displayName: 'PyPI Package' - steps: - - powershell: | - Write-Host "##vso[task.setvariable variable=BranchName]refs/heads/${{ parameters.BRANCH_NAME }}/$(NewWorkerVersion)" - displayName: "Set branch variable" - - powershell: | - Write-Host "BranchName: $(BranchName)" - displayName: 'Display BranchName variable' - - task: DownloadPipelineArtifact@2 - displayName: 'Download Python V2 Library release-*/x.y.z Artifact' - inputs: - buildType: specific - project: '3f99e810-c336-441f-8892-84983093ad7f' - definition: 652 - specificBuildWithTriggering: true - buildVersionToDownload: latestFromBranch - branchName: '$(BranchName)' - allowPartiallySucceededBuilds: true - allowFailedBuilds: true - targetPath: 'PythonRuntimeArtifact' - - task: UsePythonVersion@0 - displayName: 'Use Python 3.13' - inputs: - versionSpec: 3.13 - - task: PipAuthenticate@1 - displayName: 'Pip Authenticate' - inputs: - artifactFeeds: 'internal/PythonWorker_Internal_PublicPackages' - - pwsh: | - $newLibraryVersion = "$(NewWorkerVersion)" - $pypiToken = "$(PypiToken)" - - # Setup local Python environment - Write-Host "Setup local Python environment" - python -m pip install -U pip - pip install twine - - # Publish artifacts to PyPi - twine upload --repository-url https://upload.pypi.org/legacy/ --username "__token__" --password "$pypiToken" PythonRuntimeArtifact/${{ parameters.PROJECT_NAME }}/${{ parameters.PROJECT_DIRECTORY }}/dist/* - Start-Sleep -Seconds 3 - - # Checking if the new version is uploaded - Write-Host "Check if new version is uploaded" - $response = Invoke-WebRequest -Headers @{"Cache-Control"="no-cache"} -Method Get -Uri "https://pypi.org/project/azure-functions-runtime/$newLibraryVersion/" - - # Return Value - if ($response.StatusCode -ne 200) { - Write-Host "Failed to verify https://pypi.org/project/azure-functions-runtime/$newLibraryVersion/" - exit -1 - } - displayName: 'Publish package to pypi.org' \ No newline at end of file diff --git a/eng/templates/official/release/build-artifacts.yml b/eng/templates/official/release/build-artifacts.yml new file mode 100644 index 000000000..7b49a145a --- /dev/null +++ b/eng/templates/official/release/build-artifacts.yml @@ -0,0 +1,70 @@ +parameters: + - name: artifactName + type: string + default: 'azure-functions-runtime' + displayName: 'Pipeline artifact name produced by the Build stage' + - name: projectDisplayName + type: string + default: 'Azure Functions Runtime' + displayName: 'Human-readable project name for job titles' + - name: libraryVersion + type: string + default: '' + displayName: 'Library version to build (if set, checks out release branch)' + - name: releaseBranchPrefix + type: string + default: 'release' + displayName: 'Prefix for release branch (e.g., release or release-v2)' + - name: projectDirectory + type: string + default: '.' + displayName: 'Directory containing pyproject.toml (e.g., runtimes/v2)' + - name: githubOrg + type: string + default: 'Azure' + displayName: 'GitHub organization that owns the source repository' + - name: githubRepo + type: string + default: 'azure-functions-python-worker' + displayName: 'GitHub repository name' + +jobs: + - job: "Build" + displayName: 'Build ${{ parameters.projectDisplayName }}' + + pool: + name: 1es-pool-azfunc + image: 1es-ubuntu-22.04 + os: linux + + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory) + outputs: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/${{ parameters.projectDirectory }} + artifactName: ${{ parameters.artifactName }} + + steps: + - ${{ if ne(parameters.libraryVersion, '') }}: + - checkout: none + - bash: | + git clone --branch "${{ parameters.releaseBranchPrefix }}/${{ parameters.libraryVersion }}" --single-branch \ + "https://$(GithubUser):$(GithubPat)@github.com/${{ parameters.githubOrg }}/${{ parameters.githubRepo }}.git" . + displayName: 'Checkout ${{ parameters.releaseBranchPrefix }}/${{ parameters.libraryVersion }} branch' + + - task: UsePythonVersion@0 + inputs: + versionSpec: "3.13" + - bash: | + python --version + displayName: 'Check python version' + - task: PipAuthenticate@1 + displayName: 'Pip Authenticate' + inputs: + artifactFeeds: 'internal/PythonWorker_Internal_PublicPackages' + - bash: | + python -m pip install -U pip + python -m pip install build + cd ${{ parameters.projectDirectory }} + python -m build + displayName: 'Build ${{ parameters.projectDisplayName }}' diff --git a/eng/templates/official/release/bump-version.yml b/eng/templates/official/release/bump-version.yml new file mode 100644 index 000000000..15fec9323 --- /dev/null +++ b/eng/templates/official/release/bump-version.yml @@ -0,0 +1,145 @@ +parameters: + - name: libraryVersion + type: string + - name: projectDisplayName + type: string + default: 'Azure Functions Runtime' + displayName: 'Human-readable project name for commit messages and logs' + - name: versionFilePath + type: string + displayName: 'Path to version file (e.g., runtimes/v2/azure_functions_runtime/version.py)' + - name: versionPattern + type: string + default: "VERSION = '([0-9])+\\.([0-9])+.*'" + displayName: 'Regex pattern to match version string in version file' + - name: versionReplacement + type: string + default: "VERSION = '{{VERSION}}'" + displayName: 'Replacement pattern for version (use {{VERSION}} as placeholder)' + - name: releaseBranchPrefix + type: string + default: 'release' + displayName: 'Prefix for release branch (e.g., release or release-v2)' + - name: createPullRequest + type: boolean + default: true + displayName: 'Create a draft PR to base branch after creating release branch' + - name: baseBranch + type: string + default: 'dev' + displayName: 'Base branch for PR creation (e.g., main or dev)' + - name: githubOrg + type: string + default: 'Azure' + displayName: 'GitHub organization that owns the source repository' + - name: githubRepo + type: string + default: 'azure-functions-python-worker' + displayName: 'GitHub repository name (used in URLs and as the local clone folder)' + +jobs: + - job: "CreateReleaseBranch" + displayName: 'Create Release Branch' + pool: + name: 1es-pool-azfunc + image: 1es-ubuntu-22.04 + os: linux + steps: + - powershell: | + $githubUser = "$(GithubUser)" + $githubToken = "$(GithubPat)" + $newLibraryVersion = "${{ parameters.libraryVersion }}" + $branchPrefix = "${{ parameters.releaseBranchPrefix }}" + + # Strict version pattern: major.minor.patch with optional PEP 440 or SemVer pre-release suffix. + # PEP 440: a/b/rc must be followed by a number (e.g., 1.5.0a1, 1.5.0rc2, 1.5.0.dev4, 1.5.0.post1) + # SemVer: hyphen-separated identifiers (e.g., 1.5.0-alpha.1, 1.5.0-rc.1) + $versionValidation = '^\d+\.\d+\.\d+((a|b|rc)\d+|\.dev\d+|\.post\d+|-[0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?$' + + if ($newLibraryVersion -notmatch $versionValidation) { + Write-Host "NewLibraryVersion '$newLibraryVersion' is malformed. Expected major.minor.patch with an optional pre-release suffix." + Write-Host "Valid examples: 1.5.0, 1.5.0-alpha.1, 1.5.0rc1, 1.5.0a2, 1.5.0b3, 1.5.0.dev4, 1.5.0.post1" + exit -1 + } + + # Validate that the resulting branch ref is a valid Git ref name + $releaseBranch = "$branchPrefix/$newLibraryVersion" + git check-ref-format --branch $releaseBranch 2>&1 | Out-Null + if ($LASTEXITCODE -ne 0) { + Write-Host "The derived branch name '$releaseBranch' is not a valid Git ref name (see 'git check-ref-format' rules)." + Write-Host "Please use a version string that produces a valid branch name." + exit -1 + } + + # Create GitHub credential + git config --global user.name "AzureFunctions" + git config --global user.email "azurefunctions@microsoft.com" + $credential = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("${githubUser}:${githubToken}")) + + # Heading to Artifact Repository + Write-Host "Operating based on ${{ parameters.githubRepo }}" + git checkout -b "$releaseBranch" + + # Change version file + $versionFilePath = "${{ parameters.versionFilePath }}" + $versionPattern = '${{ parameters.versionPattern }}' + $versionReplacement = '${{ parameters.versionReplacement }}' -replace '\{\{VERSION\}\}', $newLibraryVersion + + Write-Host "Change version number in $versionFilePath to $newLibraryVersion" + Write-Host " Pattern: $versionPattern" + Write-Host " Replacement: $versionReplacement" + + ((Get-Content $versionFilePath) -replace $versionPattern, $versionReplacement -join "`n") + "`n" | Set-Content -NoNewline $versionFilePath + git add $versionFilePath + + git commit -m "build: update ${{ parameters.projectDisplayName }} version to $newLibraryVersion" + + # Create release branch + Write-Host "Creating release branch $releaseBranch" + git remote set-url origin "https://${githubUser}:${githubToken}@github.com/${{ parameters.githubOrg }}/${{ parameters.githubRepo }}.git" + git push --set-upstream origin "$releaseBranch" + + # Create PR if requested + $createPR = [System.Convert]::ToBoolean("${{ parameters.createPullRequest }}") + if ($createPR) { + Write-Host "Creating PR draft in GitHub" + $prBody = @{ + head = "$releaseBranch" + base = "${{ parameters.baseBranch }}" + title = "build: update ${{ parameters.projectDisplayName }} version to $newLibraryVersion" + body = "Version $newLibraryVersion" + draft = $true + maintainer_can_modify = $true + } | ConvertTo-Json -Compress + + $headers = @{ + "Authorization" = "Basic $credential" + "Content-Type" = "application/json" + "Accept" = "application/vnd.github.v3+json" + "User-Agent" = "AzureDevOpsPipeline" + } + + $response = Invoke-WebRequest -Headers $headers -Method Post -Body $prBody -Uri "https://api.github.com/repos/${{ parameters.githubOrg }}/${{ parameters.githubRepo }}/pulls" + + if ($response.StatusCode -ne 201) { + Write-Host "##[warning]Failed to create PR draft. HTTP $($response.StatusCode)" + } else { + $draftUrl = ($response.Content | ConvertFrom-Json).html_url + Write-Host "PR draft created: $draftUrl" + } + } + displayName: 'Push ${{ parameters.releaseBranchPrefix }}/x.y.z' + + - job: "CheckReleaseBranch" + dependsOn: ['CreateReleaseBranch'] + displayName: '(Manual) Check Release Branch' + pool: server + steps: + - task: ManualValidation@1 + displayName: '(Optional) Modify release branch' + inputs: + notifyUsers: '' # No email notifications sent + instructions: | + 1. Check if the https://github.com/${{ parameters.githubOrg }}/${{ parameters.githubRepo }}/tree/${{ parameters.releaseBranchPrefix }}/${{ parameters.libraryVersion }} build succeeds and passes all unit tests. + 2. If not, modify the ${{ parameters.releaseBranchPrefix }}/${{ parameters.libraryVersion }} branch. + 3. Ensure ${{ parameters.releaseBranchPrefix }}/${{ parameters.libraryVersion }} branch contains all necessary changes. diff --git a/eng/templates/official/release/publish-release.yml b/eng/templates/official/release/publish-release.yml new file mode 100644 index 000000000..6e1bce22c --- /dev/null +++ b/eng/templates/official/release/publish-release.yml @@ -0,0 +1,278 @@ +parameters: + - name: libraryVersion + type: string + - name: projectDisplayName + type: string + default: 'Azure Functions Runtime' + displayName: 'Human-readable project name for release notes' + - name: releaseBranchPrefix + type: string + default: 'release' + displayName: 'Prefix for release branch (e.g., release or release-v2)' + - name: tagPrefix + type: string + default: '' + displayName: 'Optional prefix for git tags (e.g., runtimes/v2-)' + - name: blobPath + type: string + default: 'azure-functions/python/azure-functions-runtime' + displayName: 'Blob path under drops container' + - name: githubOrg + type: string + default: 'Azure' + displayName: 'GitHub organization that owns the source repository' + - name: githubRepo + type: string + default: 'azure-functions-python-worker' + displayName: 'GitHub repository name (used in URLs and as the local clone folder)' + - name: artifactName + type: string + default: 'azure-functions-runtime' + displayName: 'Pipeline artifact name produced by the Build stage' + - name: partnerReleasePipelineUrl + type: string + default: 'https://dev.azure.com/azure-sdk/internal/_build?definitionId=6991' + displayName: 'Azure SDK partner-release pipeline URL (used in manual PyPI publish instructions)' + +jobs: + - job: "CreateReleaseTag" + displayName: 'Create Release Tag' + steps: + - powershell: | + $githubUser = "$(GithubUser)" + $githubToken = "$(GithubPat)" + $newLibraryVersion = "${{ parameters.libraryVersion }}" + $releaseBranchPrefix = "${{ parameters.releaseBranchPrefix }}" + $tagPrefix = "${{ parameters.tagPrefix }}" + $releaseTag = "${tagPrefix}${newLibraryVersion}" + + # Create GitHub credential + git config --global user.name "AzureFunctions" + git config --global user.email "azurefunctions@microsoft.com" + + # Clone Repository + git clone "https://${githubUser}:${githubToken}@github.com/${{ parameters.githubOrg }}/${{ parameters.githubRepo }}" + Write-Host "Cloned ${{ parameters.githubRepo }} into local" + Set-Location "${{ parameters.githubRepo }}" + git checkout "origin/${releaseBranchPrefix}/$newLibraryVersion" + + # Create release tag + Write-Host "Creating release tag $releaseTag" + git tag -a "$releaseTag" -m "$newLibraryVersion" + + # Push tag to remote + git push origin $releaseTag + displayName: 'Tag and push ${{ parameters.tagPrefix }}x.y.z' + + - download: current + artifact: ${{ parameters.artifactName }} + displayName: 'Download build artifacts' + + - pwsh: | + $ErrorActionPreference = 'Stop' + $ProgressPreference = 'SilentlyContinue' + + $githubUser = "$(GithubUser)" + $githubToken = "$(GithubPat)" + $newLibraryVersion = "${{ parameters.libraryVersion }}" + $tagPrefix = "${{ parameters.tagPrefix }}" + $releaseTag = "${tagPrefix}${newLibraryVersion}" + $projectDisplayName = "${{ parameters.projectDisplayName }}" + + $credential = [System.Convert]::ToBase64String( + [System.Text.Encoding]::ASCII.GetBytes("${githubUser}:${githubToken}")) + + $apiHeaders = @{ + "Accept" = "application/vnd.github+json" + "Authorization" = "Basic $credential" + "X-GitHub-Api-Version" = "2022-11-28" + "Content-Type" = "application/json" + } + + # ---- Locate and validate assets BEFORE creating the release ---- + $distFolder = Join-Path '$(Pipeline.Workspace)' (Join-Path '${{ parameters.artifactName }}' 'dist') + if (-not (Test-Path -LiteralPath $distFolder)) { + throw "Distribution folder not found: $distFolder" + } + + $assetsToUpload = @( + Get-ChildItem -LiteralPath $distFolder -File | + Where-Object { $_.Name -like '*.whl' -or $_.Name -like '*.tar.gz' } | + Sort-Object Name + ) + if ($assetsToUpload.Count -eq 0) { + throw "No .whl or .tar.gz files found in $distFolder" + } + Write-Host "Assets to upload:" + $assetsToUpload | ForEach-Object { Write-Host " - $($_.Name) ($($_.Length) bytes)" } + + # ---- Create draft release ---- + Write-Host "Creating GitHub draft release for $releaseTag" + $releaseName = "Release ${projectDisplayName} ${newLibraryVersion}" + $body = (@{tag_name="$releaseTag";name="$releaseName";body="- Fill in Release Note Here";draft=$true} | ConvertTo-Json -Compress) + $createResponse = Invoke-WebRequest -Headers $apiHeaders -Method Post -Body "$body" ` + -Uri "https://api.github.com/repos/${{ parameters.githubOrg }}/${{ parameters.githubRepo }}/releases" + if ($createResponse.StatusCode -ne 201) { + throw "Failed to create release. HTTP $($createResponse.StatusCode)" + } + $release = $createResponse.Content | ConvertFrom-Json + Write-Host "Draft release created: $($release.html_url)" + + # ---- Upload assets; fail hard on duplicate-name (PyPI-style) ---- + $uploadUrlBase = $release.upload_url -replace '\{\?name,label\}$', '' + $uploadHeaders = @{ + "Accept" = "application/vnd.github+json" + "Authorization" = "Basic $credential" + "X-GitHub-Api-Version" = "2022-11-28" + } + + # Wrap upload loop so any failure deletes the incomplete draft release. + # This keeps the world clean for retries: an operator can rerun the job + # without manually purging a stranded empty/partial draft. + try { + foreach ($asset in $assetsToUpload) { + $assetUploadUrl = '{0}?name={1}' -f $uploadUrlBase, [System.Uri]::EscapeDataString($asset.Name) + Write-Host "Uploading $($asset.Name)..." + try { + $uploadResponse = Invoke-WebRequest -Headers $uploadHeaders -Method Post ` + -Uri $assetUploadUrl -ContentType 'application/octet-stream' -InFile $asset.FullName + } catch { + $status = $null + if ($_.Exception.Response) { + $status = [int]$_.Exception.Response.StatusCode + } + if ($status -eq 422) { + throw "Asset '$($asset.Name)' already exists on release $newLibraryVersion. GitHub releases must be unique per filename (same policy as PyPI). If this is intentional, delete the asset from the GitHub release UI and rerun, or bump the version." + } + throw "Upload failed for $($asset.Name): HTTP $status - $($_.Exception.Message)" + } + if ($uploadResponse.StatusCode -ne 201) { + throw "Upload failed for $($asset.Name). HTTP $($uploadResponse.StatusCode)" + } + Write-Host " -> uploaded" + } + } catch { + $uploadError = $_ + if ($release -and $release.id) { + $releaseDeleteUri = "https://api.github.com/repos/${{ parameters.githubOrg }}/${{ parameters.githubRepo }}/releases/$($release.id)" + Write-Host "##[warning]Asset upload failed. Cleaning up incomplete draft release $($release.id)..." + try { + Invoke-WebRequest -Headers $apiHeaders -Method Delete -Uri $releaseDeleteUri | Out-Null + Write-Host "Draft release $($release.id) deleted." + } catch { + Write-Host "##[warning]Failed to delete draft release $($release.id): $($_.Exception.Message). Manual cleanup required at https://github.com/${{ parameters.githubOrg }}/${{ parameters.githubRepo }}/releases" + } + } + throw $uploadError + } + + Write-Host "All assets attached to draft release: $($release.html_url)" + displayName: 'Create GitHub release draft and attach assets' + + - job: "CheckGitHubRelease" + dependsOn: ['CreateReleaseTag'] + displayName: '(Manual) Check GitHub release note' + pool: server + steps: + - task: ManualValidation@1 + displayName: 'Write GitHub release note' + inputs: + notifyUsers: '' + instructions: 'Please head to https://github.com/${{ parameters.githubOrg }}/${{ parameters.githubRepo }}/releases to finish the release note' + + - job: PublishRelease + dependsOn: ['CheckGitHubRelease'] + displayName: 'Upload artifacts to partner blob for release' + + pool: + name: 1es-pool-azfunc + image: 1es-ubuntu-22.04 + os: linux + + steps: + - checkout: none + + - download: current + artifact: ${{ parameters.artifactName }} + displayName: 'Download build artifacts' + + - task: AzurePowerShell@5 + displayName: 'Upload files to partner drop folder' + inputs: + azureSubscription: 'azure-sdk-partner-drops' + ScriptType: 'InlineScript' + azurePowerShellVersion: LatestVersion + pwsh: true + Inline: | + $version = '${{ parameters.libraryVersion }}' + $blobPath = '${{ parameters.blobPath }}' + $distFolder = '$(Pipeline.Workspace)/${{ parameters.artifactName }}/dist' + + Write-Host "Uploading Python artifacts for version: $version" + Write-Host "Source folder: $distFolder" + Write-Host "Destination: https://azuresdkpartnerdrops.blob.core.windows.net/drops/$blobPath/$version/" + + # List files to be uploaded + Get-ChildItem -Path $distFolder -Recurse | ForEach-Object { + Write-Host " - $($_.Name)" + } + + # Upload all distribution files (whl and tar.gz) to partner blob + azcopy copy "$distFolder/*" "https://azuresdkpartnerdrops.blob.core.windows.net/drops/$blobPath/$version/" + + $blobPathFull = "$blobPath/$version" + $partnerReleaseUrl = '${{ parameters.partnerReleasePipelineUrl }}' + + Write-Host "" + Write-Host "##[section]Upload complete. The partner-release pipeline will be triggered automatically in the next job." + Write-Host " BlobPath: $blobPathFull" + Write-Host " Partner pipeline: $partnerReleaseUrl" + env: + AZCOPY_AUTO_LOGIN_TYPE: 'PSCRED' + + - job: "TriggerPartnerRelease" + dependsOn: ['PublishRelease'] + displayName: 'Trigger partner-release pipeline' + + pool: + name: 1es-pool-azfunc + image: 1es-ubuntu-22.04 + os: linux + + steps: + - checkout: none + + - pwsh: | + Write-Host "Target folder: ${{ parameters.blobPath }}/${{ parameters.libraryVersion }}" + Write-Host "Storage container: https://ms.portal.azure.com/#blade/Microsoft_Azure_Storage/ContainerMenuBlade/overview/storageAccountId/%2Fsubscriptions%2Fa18897a6-7e44-457d-9260-f2854c0aca42%2FresourceGroups%2Fazuresdkpartnerdrops%2Fproviders%2FMicrosoft.Storage%2FstorageAccounts%2Fazuresdkpartnerdrops/path/drops" + Write-Host "Partner pipeline: ${{ parameters.partnerReleasePipelineUrl }}" + displayName: 'Log partner pipeline information' + + - task: AzureCLI@2 + displayName: 'Run partner pipeline for PyPI publish' + inputs: + azureSubscription: 'azure-sdk-partner-drops' + scriptType: pscore + scriptLocation: inlineScript + inlineScript: | + $ErrorActionPreference = "Stop" + $blobPath = "${{ parameters.blobPath }}/${{ parameters.libraryVersion }}" + Write-Host "Running partner pipeline for blob path: $blobPath" + + $result = az pipelines run ` + --project internal ` + --organization https://dev.azure.com/azure-sdk ` + --id 6991 ` + --branch refs/heads/main ` + --parameters "BlobPath=$blobPath" + + if (!$result) { + Write-Host "##vso[task.LogIssue type=error;]Failed to start partner pipeline." + exit 1 + } + + $result = $result | ConvertFrom-Json + Write-Host "##[section]Partner pipeline started successfully." + Write-Host "Build ID: $($result.id)" + Write-Host "Partner pipeline may require manual approval to proceed." + Write-Host "Please check the pipeline at: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=$($result.id)"