Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions eng/pipelines/common/templates/jobs/approval-job.yml
Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

I apologize for this. I made the new folders to separate the new (and model) pipelines from the old ones. For new (and properly built) pipelines, put stuff in eng/pipelines/jobs. Old stuff stays in eng/pipelines/common/templates/jobs.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#################################################################################
# Licensed to the .NET Foundation under one or more agreements. #
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
parameters:
- name: approvalAliases
Copy link
Contributor

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.

type: string
default: '[ADO.Net]\\SqlClient Admins'

- name: publishDestination
type: string

- name: dryRun
type: boolean

- name: isPreview
type: boolean

- name: publishSymbols
type: boolean

- name: nugetPackageVersion
type: string

- name: product
type: string

jobs:
- job: AwaitApproval
displayName: 'Await Release Approval'
pool: server
steps:
- task: ManualValidation@0
displayName: 'Manual Approval'
timeoutInMinutes: 4320 # 3 days
inputs:
notifyUsers: ${{ parameters.approvalAliases }}
instructions: |
Release Checklist:
* Destination: ${{ parameters.publishDestination }}
* Preview build: ${{ parameters.isPreview }}
* Dry run: ${{ parameters.dryRun }}
* Symbols: ${{ parameters.publishSymbols }}
* NuGet package version: ${{ parameters.nugetPackageVersion }}
* Product: ${{ parameters.product }}
Approve to continue or Reject to abort.
65 changes: 65 additions & 0 deletions eng/pipelines/common/templates/jobs/publish-packages-job.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#################################################################################
# Licensed to the .NET Foundation under one or more agreements. #
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
parameters:
- name: publishDestination
type: string

- name: dryRun
type: boolean

- name: internalFeedSource
type: string

- name: publicNuGetSource
type: string

- name: publishSymbols
type: boolean

- name: packageFolderName
type: string

- name: nugetPackageVersion
type: string

- name: product
type: string

jobs:
- job: PublishPackages
displayName: 'Publish Packages'
dependsOn: AwaitApproval
Copy link
Contributor

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.

condition: succeeded()
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download Signed Packages'
inputs:
buildType: current
artifactName: ${{ parameters.packageFolderName }}
targetPath: $(Pipeline.Workspace)/release/packages
- script: |
Copy link
Contributor

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?

echo "NuGet Package Version: ${{ parameters.nugetPackageVersion }}"
displayName: 'Echo NuGet Package Version'
- ${{ if ne(parameters.publishDestination, 'Public') }}:
- template: ../steps/publish-internal-feed-step.yml
parameters:
dryRun: ${{ parameters.dryRun }}
internalFeedSource: ${{ parameters.internalFeedSource }}
packagesGlob: $(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg
Copy link
Contributor

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 :)

- ${{ if eq(parameters.publishDestination, 'Public') }}:
Copy link
Contributor

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.

- 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
Comment on lines +53 to +59
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +59
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
- ${{ if and(parameters.publishSymbols, ne(parameters.dryRun, true)) }}:
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
- ${{ 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)) }}:

Copilot uses AI. Check for mistakes.
- template: ../steps/publish-symbols-step.yml
parameters:
publishSymbols: ${{ parameters.publishSymbols }}
symbolsArtifactName: ${{ parameters.product }}_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.nugetPackageVersion }}_$(System.TimelineId)
product: ${{ parameters.product }}
66 changes: 66 additions & 0 deletions eng/pipelines/common/templates/stages/release-stage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#################################################################################
# Licensed to the .NET Foundation under one or more agreements. #
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
parameters:
- name: runRelease
type: boolean
default: false

- name: publishDestination
type: string

- name: dryRun
type: boolean
default: false

- name: approvalAliases
type: string

- name: internalFeedSource
type: string

- name: publicNuGetSource
type: string

- name: publishSymbols
type: boolean
default: false

- name: isPreview
type: boolean

- name: product
type: string

- name: nugetPackageVersion
type: string

- name: packageFolderName
type: string

stages:
- stage: releaseMDS
displayName: 'Release (Manual)'
condition: and(succeeded(), eq(variables['Build.Reason'],'Manual'), eq(${{ parameters.runRelease }}, true))
jobs:
- template: ../jobs/approval-job.yml
parameters:
approvalAliases: ${{ parameters.approvalAliases }}
publishDestination: ${{ parameters.publishDestination }}
dryRun: ${{ parameters.dryRun }}
isPreview: ${{ parameters.isPreview }}
publishSymbols: ${{ parameters.publishSymbols }}
nugetPackageVersion: ${{ parameters.nugetPackageVersion }}
product: ${{ parameters.product }}
- template: ../jobs/publish-packages-job.yml
parameters:
publishDestination: ${{ parameters.publishDestination }}
dryRun: ${{ parameters.dryRun }}
internalFeedSource: ${{ parameters.internalFeedSource }}
publicNuGetSource: ${{ parameters.publicNuGetSource }}
publishSymbols: ${{ parameters.publishSymbols }}
packageFolderName: ${{ parameters.packageFolderName }}
nugetPackageVersion: ${{ parameters.nugetPackageVersion }}
product: ${{ parameters.product }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#################################################################################
# Licensed to the .NET Foundation under one or more agreements. #
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
parameters:
- name: dryRun
type: boolean
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
type: boolean
type: boolean
default: false

Copilot uses AI. Check for mistakes.

- name: internalFeedSource
type: string

- name: packagesGlob
type: string
default: '$(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg'
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
default: '$(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg'

Copilot uses AI. Check for mistakes.

steps:
- script: |
Copy link
Contributor

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.

set -e
SRC='${{ parameters.internalFeedSource }}'
if [ -z "$SRC" ]; then
echo "Internal feed source parameter not set."
exit 1
fi
if [ "${{ parameters.dryRun }}" = "true" ]; then
echo "[DRY RUN] Listing packages targeted for push to: ${{ parameters.publicNuGetSource }}"
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
echo "[DRY RUN] Listing packages targeted for push to: ${{ parameters.publicNuGetSource }}"
echo "[DRY RUN] Listing packages targeted for push to: ${{ parameters.internalFeedSource }}"

Copilot uses AI. Check for mistakes.
echo "Using glob pattern: ${{ parameters.packagesGlob }}"
# Derive directory and filename pattern from the glob for a precise find (handles nested patterns minimally)
glob='${{ parameters.packagesGlob }}'
dir="${glob%/*}"
name="${glob##*/}"
echo "Resolved directory: $dir"
echo "Filename pattern: $name"
if [ -d "$dir" ]; then
echo "Matched files:" || true
# Print all matched files to identify what would be pushed
find "$dir" -type f -name "$name" -print || true
else
echo "Directory does not exist yet: $dir"
fi
fi
for f in $(find "${{ parameters.packagesGlob }}" -name "*.nupkg"); do
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
for f in $(find "${{ parameters.packagesGlob }}" -name "*.nupkg"); do
for f in ${{ parameters.packagesGlob }}; do

Copilot uses AI. Check for mistakes.
echo "Push $f"
dotnet nuget push --source "$SRC" --api-key az "$f"
done
Comment on lines +42 to +45
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
displayName: 'Publish to Internal Feed'
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#################################################################################
# Licensed to the .NET Foundation under one or more agreements. #
# The .NET Foundation licenses this file to you under the MIT license. #
# See the LICENSE file in the project root for more information. #
#################################################################################
parameters:
- name: dryRun
type: boolean
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
type: boolean
type: boolean
default: false

Copilot uses AI. Check for mistakes.

- name: publicNuGetSource
type: string

- name: nugetServiceConnection
type: string
default: 'ADO Nuget Org Connection'

- name: packagesGlob
type: string
default: '$(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg'

Comment on lines +19 to +20
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
default: '$(System.DefaultWorkingDirectory)/_dotnet-sqlclient-Official/drop_buildMDS_build_signed_package/*.nupkg'

Copilot uses AI. Check for mistakes.
steps:
- task: NuGetToolInstaller@1
Copy link
Contributor

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.

displayName: 'Install Latest Nuget'
inputs:
checkLatest: true
- script: |
echo "[DRY RUN] Listing packages targeted for push to: ${{ parameters.publicNuGetSource }}"
echo "Using glob pattern: ${{ parameters.packagesGlob }}"
# Derive directory and filename pattern from the glob for a precise find (handles nested patterns minimally)
glob='${{ parameters.packagesGlob }}'
dir="${glob%/*}"
name="${glob##*/}"
echo "Resolved directory: $dir"
echo "Filename pattern: $name"
if [ -d "$dir" ]; then
echo "Matched files:" || true
# Print all matched files to identify what would be pushed
find "$dir" -type f -name "$name" -print || true
else
echo "Directory does not exist yet: $dir"
fi
displayName: 'Dry Run - List Packages'
condition: and(succeeded(), eq(${{ parameters.dryRun }}, true))

- task: NuGetCommand@2
displayName: 'Push to Nuget.org'
condition: and(succeeded(), eq(${{ parameters.dryRun }}, false))
inputs:
command: push
packagesToPush: '${{ parameters.packagesGlob }}'
nuGetFeedType: external
publishFeedCredentials: '${{ parameters.nugetServiceConnection }}'
Loading
Loading