-
Notifications
You must be signed in to change notification settings - Fork 317
Add Manual OneBranch Release Stage & Publish Support (Internal/Public) #3761
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds a manual release stage to the OneBranch signing pipeline, enabling controlled NuGet package publishing with approval gates. The implementation supports both internal and public publishing destinations, includes dry-run capability for testing, and integrates symbol publishing for the MDS product.
Key Changes:
- Adds manual release parameters (destination, dry run, product) to the signing pipeline
- Implements approval workflow with human validation before package publication
- Creates templated release infrastructure supporting multiple products (MDS, MSS, AKV)
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| eng/pipelines/dotnet-sqlclient-signing-pipeline.yml | Adds release parameters and invokes new release-stage template |
| eng/pipelines/common/templates/stages/release-stage.yml | Defines manual release stage with approval and publish jobs |
| eng/pipelines/common/templates/jobs/approval-job.yml | Implements manual validation job with release checklist |
| eng/pipelines/common/templates/jobs/publish-packages-job.yml | Orchestrates package download and conditional publishing to internal/public feeds |
| eng/pipelines/common/templates/steps/publish-internal-feed-step.yml | Handles internal feed publishing with dry-run support |
| eng/pipelines/common/templates/steps/publish-public-nuget-step.yml | Handles NuGet.org publishing with dry-run support |
| eng/pipelines/common/templates/steps/list-packages-step.yml | Lists packages for verification before publishing |
| eng/pipelines/common/templates/steps/publish-symbols-step.yml | Updates symbol publishing to use boolean type and add AKV product support |
eng/pipelines/common/templates/steps/publish-internal-feed-step.yml
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.
| echo "Directory does not exist yet: $dir" | ||
| fi | ||
| fi | ||
| for f in $(find "${{ parameters.packagesGlob }}" -name "*.nupkg"); do |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The find command is incorrectly used here. The first argument should be a directory path, not a glob pattern. This will fail when the script tries to execute. Consider using: find "$(dirname '${{ parameters.packagesGlob }}')" -type f -name "$(basename '${{ parameters.packagesGlob }}')" or a simpler approach with shell globbing like for f in ${{ parameters.packagesGlob }}; do.
| for f in $(find "${{ parameters.packagesGlob }}" -name "*.nupkg"); do | |
| for f in ${{ parameters.packagesGlob }}; do |
| for f in $(find "${{ parameters.packagesGlob }}" -name "*.nupkg"); do | ||
| echo "Push $f" | ||
| dotnet nuget push --source "$SRC" --api-key az "$f" | ||
| done |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dry run logic only prints files but doesn't skip the actual push operation. When dryRun is true, the script should exit after listing files, or the push loop (lines 40-43) should be conditionally executed only when dryRun is false. Currently, packages will be pushed even in dry run mode.
| for f in $(find "${{ parameters.packagesGlob }}" -name "*.nupkg"); do | |
| echo "Push $f" | |
| dotnet nuget push --source "$SRC" --api-key az "$f" | |
| done | |
| if [ "${{ parameters.dryRun }}" != "true" ]; then | |
| for f in $(find "${{ parameters.packagesGlob }}" -name "*.nupkg"); do | |
| echo "Push $f" | |
| dotnet nuget push --source "$SRC" --api-key az "$f" | |
| done | |
| fi |
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg | ||
| - ${{ if eq(parameters.publishDestination, 'Public') }}: | ||
| - template: ../steps/publish-public-nuget-step.yml | ||
| parameters: | ||
| dryRun: ${{ parameters.dryRun }} | ||
| publicNuGetSource: ${{ parameters.publicNuGetSource }} | ||
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded artifact path $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg contains 'MDS' in the path but is used for all product types (MDS, MSS, AKV). This will fail when publishing MSS or AKV packages. The path should either be parameterized based on the product type or use the downloaded artifact location from line 44: $(Pipeline.Workspace)/release/packages/*.nupkg.
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg | |
| - ${{ if eq(parameters.publishDestination, 'Public') }}: | |
| - template: ../steps/publish-public-nuget-step.yml | |
| parameters: | |
| dryRun: ${{ parameters.dryRun }} | |
| publicNuGetSource: ${{ parameters.publicNuGetSource }} | |
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg | |
| packagesGlob: $(Pipeline.Workspace)/release/packages/*.nupkg | |
| - ${{ if eq(parameters.publishDestination, 'Public') }}: | |
| - template: ../steps/publish-public-nuget-step.yml | |
| parameters: | |
| dryRun: ${{ parameters.dryRun }} | |
| publicNuGetSource: ${{ parameters.publicNuGetSource }} | |
| packagesGlob: $(Pipeline.Workspace)/release/packages/*.nupkg |
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg | ||
| - ${{ if eq(parameters.publishDestination, 'Public') }}: | ||
| - template: ../steps/publish-public-nuget-step.yml | ||
| parameters: | ||
| dryRun: ${{ parameters.dryRun }} | ||
| publicNuGetSource: ${{ parameters.publicNuGetSource }} | ||
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded artifact path $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg contains 'MDS' in the path but is used for all product types (MDS, MSS, AKV). This will fail when publishing MSS or AKV packages. The path should either be parameterized based on the product type or use the downloaded artifact location from line 44: $(Pipeline.Workspace)/release/packages/*.nupkg.
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg | |
| - ${{ if eq(parameters.publishDestination, 'Public') }}: | |
| - template: ../steps/publish-public-nuget-step.yml | |
| parameters: | |
| dryRun: ${{ parameters.dryRun }} | |
| publicNuGetSource: ${{ parameters.publicNuGetSource }} | |
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg | |
| packagesGlob: $(Pipeline.Workspace)/release/packages/*.nupkg | |
| - ${{ if eq(parameters.publishDestination, 'Public') }}: | |
| - template: ../steps/publish-public-nuget-step.yml | |
| parameters: | |
| dryRun: ${{ parameters.dryRun }} | |
| publicNuGetSource: ${{ parameters.publicNuGetSource }} | |
| packagesGlob: $(Pipeline.Workspace)/release/packages/*.nupkg |
| default: '$(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg' | ||
|
|
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default value for packagesGlob is hardcoded to an MDS-specific path $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg. This should not have a default value with 'MDS' hardcoded, as this template is intended to be reusable for all product types (MDS, MSS, AKV). Consider removing the default or making it generic.
| default: '$(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg' |
|
|
||
| - name: packagesGlob | ||
| type: string | ||
| default: '$(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg' |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default value for packagesGlob is hardcoded to an MDS-specific path $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg. This should not have a default value with 'MDS' hardcoded, as this template is intended to be reusable for all product types (MDS, MSS, AKV). Consider removing the default or making it generic.
| default: '$(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg' |
| ################################################################################# | ||
| parameters: | ||
| - name: dryRun | ||
| type: boolean |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Missing default value for required parameter. While dryRun parameter has a type of boolean, it lacks a default value. For consistency with other templates in this PR and to prevent errors when the parameter is not explicitly provided, consider adding default: false.
| type: boolean | |
| type: boolean | |
| default: false |
| exit 1 | ||
| fi | ||
| if [ "${{ parameters.dryRun }}" = "true" ]; then | ||
| echo "[DRY RUN] Listing packages targeted for push to: ${{ parameters.publicNuGetSource }}" |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message references ${{ parameters.publicNuGetSource }} but this parameter doesn't exist in this template. It should reference ${{ parameters.internalFeedSource }} instead to correctly show which feed is being targeted.
| echo "[DRY RUN] Listing packages targeted for push to: ${{ parameters.publicNuGetSource }}" | |
| echo "[DRY RUN] Listing packages targeted for push to: ${{ parameters.internalFeedSource }}" |
| ################################################################################# | ||
| parameters: | ||
| - name: dryRun | ||
| type: boolean |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Missing default value for required parameter. While dryRun parameter has a type of boolean, it lacks a default value. For consistency with other templates in this PR and to prevent errors when the parameter is not explicitly provided, consider adding default: false.
| type: boolean | |
| type: boolean | |
| default: false |
| dryRun: ${{ parameters.dryRun }} | ||
| publicNuGetSource: ${{ parameters.publicNuGetSource }} | ||
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg | ||
| - ${{ if and(parameters.publishSymbols, ne(parameters.dryRun, true)) }}: |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The condition ne(parameters.dryRun, true) should use eq(parameters.dryRun, false) for consistency with other conditionals in the codebase, or the double-negative logic could be simplified. Additionally, this condition prevents symbol publishing during dry runs, but the individual steps within publish-symbols-step.yml already have their own conditions. Consider whether this outer condition is necessary or if it should be documented why symbols aren't published during dry runs.
| - ${{ if and(parameters.publishSymbols, ne(parameters.dryRun, true)) }}: | |
| # Only publish symbols if requested and not a dry run | |
| - ${{ if and(parameters.publishSymbols, eq(parameters.dryRun, false)) }}: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We seem to have two "jobs" directories, and it's not clear which jobs belong where. Just food for thought as we re-write the pipelines. It's hard to tell which files are used by which pipelines when they're all smushed into the same tree.
| # See the LICENSE file in the project root for more information. # | ||
| ################################################################################# | ||
| parameters: | ||
| - name: approvalAliases |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should be documenting all parameters. Nothing fancy, just a sentence or two about what a parameter does.
| jobs: | ||
| - job: PublishPackages | ||
| displayName: 'Publish Packages' | ||
| dependsOn: AwaitApproval |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the approval step just live in this file? Is approval really a general task? It seems pretty specific to publishing packages.
| buildType: current | ||
| artifactName: ${{ parameters.packageFolderName }} | ||
| targetPath: $(Pipeline.Workspace)/release/packages | ||
| - script: | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it also be helpful to list the contents of targetPath from the previous step?
| dryRun: ${{ parameters.dryRun }} | ||
| internalFeedSource: ${{ parameters.internalFeedSource }} | ||
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg | ||
| - ${{ if eq(parameters.publishDestination, 'Public') }}: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer #{{ else }} ?
I would probably swap this to be:
${{ if eq(parameters.publishDestination, 'Public') }}:
# Do public stuff.
${{ else }}:
# Do non-public stuff.
| parameters: | ||
| dryRun: ${{ parameters.dryRun }} | ||
| internalFeedSource: ${{ parameters.internalFeedSource }} | ||
| packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For future consideration: We need to pick nicer names for these artifacts :)
| default: '$(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg' | ||
|
|
||
| steps: | ||
| - script: | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we start putting these elaborate PowerShell scripts into files so we can:
- Read them easily (syntax highlighting etc).
- Run them (i.e. to test that they work).
- Version them separately from the pipeline files.
| default: '$(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg' | ||
|
|
||
| steps: | ||
| - task: NuGetToolInstaller@1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be surprised if the images we use don't have NuGet installed already.
| values: | ||
| - MDS | ||
| - MSS | ||
| - AKV |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AKV has its own official build/sign/release pipeline. Do we need it here?
Description
Adds a manual, parameter‑gated release stage to the signing pipeline enabling:
Next Steps:
Investigate: