diff --git a/eng/pipelines/common/templates/jobs/publish-packages-job.yml b/eng/pipelines/common/templates/jobs/publish-packages-job.yml new file mode 100644 index 0000000000..49885bf1c1 --- /dev/null +++ b/eng/pipelines/common/templates/jobs/publish-packages-job.yml @@ -0,0 +1,108 @@ +################################################################################# +# 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: + # Approval aliases for manual validation before publishing packages + - name: approvalAliases + type: string + default: '[ADO.Net]\\SqlClient Admins' + + # Where to publish the packages: 'Internal' or 'Public' feed + - name: publishDestination + type: string + + # Boolean value to indicate whether to perform a dry run or actual publish + - name: dryRun + type: boolean + + # Boolean value to indicate if the build is a preview release build + - name: isPreview + type: boolean + + # Internal feed source URL for publishing packages to Azure DevOps Feed + - name: internalFeedSource + type: string + + # Public NuGet source URL for publishing packages to public feed + - name: publicNuGetSource + type: string + + # Boolean value to indicate whether to publish symbols + - name: publishSymbols + type: boolean + + # Name of the folder containing the packages to be published + - name: packageFolderName + type: string + + # NuGet package version to be published + - name: nugetPackageVersion + type: string + + # Product name associated with the packages + - 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. + + - job: PublishPackages + displayName: "Publish Packages" + variables: + - name: targetDownloadPath + value: "$(Pipeline.Workspace)/release/packages" + dependsOn: AwaitApproval + condition: succeeded() + pool: + vmImage: "ubuntu-latest" + steps: + - task: DownloadPipelineArtifact@2 + displayName: "Download Signed Packages" + inputs: + buildType: current + artifactName: ${{ parameters.packageFolderName }} + targetPath: ${{ variables.targetDownloadPath }} + - script: | + echo "NuGet Package Version: ${{ parameters.nugetPackageVersion }}" + echo "Downloaded signed packages to: ${{ variables.targetDownloadPath }}" + displayName: "Echo NuGet Package Version" + # Push to Public NuGet Feed if publishDestination is 'Public' + - ${{ if eq(parameters.publishDestination, 'Public') }}: + - template: ../steps/publish-public-nuget-step.yml + parameters: + dryRun: ${{ parameters.dryRun }} + publicNuGetSource: ${{ parameters.publicNuGetSource }} + packagesGlob: ${{ variables.targetDownloadPath }}/*.nupkg + # Else Push to Internal Feed + - ${{ else }}: + - template: ../steps/publish-internal-feed-step.yml + parameters: + dryRun: ${{ parameters.dryRun }} + internalFeedSource: ${{ parameters.internalFeedSource }} + packagesGlob: ${{ variables.targetDownloadPath }}/*.nupkg + # Publish Symbols if publishSymbols is true and is not a dry run + - ${{ if and(parameters.publishSymbols, not(parameters.dryRun)) }}: + - 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 }} diff --git a/eng/pipelines/common/templates/stages/release-stage.yml b/eng/pipelines/common/templates/stages/release-stage.yml new file mode 100644 index 0000000000..6e626f9b06 --- /dev/null +++ b/eng/pipelines/common/templates/stages/release-stage.yml @@ -0,0 +1,70 @@ +################################################################################# +# 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: + # Boolean value to indicate whether to run the release stage + - name: runRelease + type: boolean + default: false + + # Where to publish the packages: 'Internal' or 'Public' feed + - name: publishDestination + type: string + + # Boolean value to indicate whether to perform a dry run or actual publish + - name: dryRun + type: boolean + default: true + + # Approval aliases for manual validation before publishing packages + - name: approvalAliases + type: string + + # Internal feed source URL for publishing packages to Azure DevOps Feed + - name: internalFeedSource + type: string + + # Public NuGet source URL for publishing packages to public feed + - name: publicNuGetSource + type: string + + # Boolean value to indicate whether to publish symbols + - name: publishSymbols + type: boolean + default: false + + # Boolean value to indicate if the build is a preview release build + - name: isPreview + type: boolean + + # Product name associated with the packages + - name: product + type: string + + # NuGet package version to be published + - name: nugetPackageVersion + type: string + + # Name of the folder containing the packages to be published + - name: packageFolderName + type: string + +stages: + - stage: Release ${{ parameters.product }} + displayName: "Release (Manual)" + condition: and(succeeded(), eq('${{ variables.Build.Reason }}', 'Manual'), eq(${{ parameters.runRelease }}, true)) + jobs: + - template: ../jobs/publish-packages-job.yml + parameters: + approvalAliases: ${{ parameters.approvalAliases }} + publishDestination: ${{ parameters.publishDestination }} + dryRun: ${{ parameters.dryRun }} + isPreview: ${{ parameters.isPreview }} + internalFeedSource: ${{ parameters.internalFeedSource }} + publicNuGetSource: ${{ parameters.publicNuGetSource }} + publishSymbols: ${{ parameters.publishSymbols }} + packageFolderName: ${{ parameters.packageFolderName }} + nugetPackageVersion: ${{ parameters.nugetPackageVersion }} + product: ${{ parameters.product }} diff --git a/eng/pipelines/common/templates/steps/publish-internal-feed-step.yml b/eng/pipelines/common/templates/steps/publish-internal-feed-step.yml new file mode 100644 index 0000000000..b0e6d98acf --- /dev/null +++ b/eng/pipelines/common/templates/steps/publish-internal-feed-step.yml @@ -0,0 +1,31 @@ +################################################################################# +# 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. # +################################################################################# + +# Template to publish NuGet packages to an internal Azure DevOps feed + +parameters: + # Boolean value to indicate whether to perform a dry run or actual publish + - name: dryRun + type: boolean + default: true + + # Internal feed source URL for publishing packages to Azure DevOps Feed + - name: internalFeedSource + type: string + + # Glob pattern to identify packages to be published + - name: packagesGlob + type: string + +steps: + - task: PowerShell@2 + displayName: Publish to Internal Feed + pwsh: true + filePath: /tools/scripts/publishPackagesToFeed.ps1 + arguments: > + -dryRun ${{ parameters.dryRun }} + -internalFeedSource '${{ parameters.internalFeedSource }}' + -packagesGlob '${{ parameters.packagesGlob }}' diff --git a/eng/pipelines/common/templates/steps/publish-public-nuget-step.yml b/eng/pipelines/common/templates/steps/publish-public-nuget-step.yml new file mode 100644 index 0000000000..fc6947bb22 --- /dev/null +++ b/eng/pipelines/common/templates/steps/publish-public-nuget-step.yml @@ -0,0 +1,59 @@ +################################################################################# +# 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. # +################################################################################# + +# Template to publish NuGet packages to a public NuGet feed + +parameters: + # Boolean value to indicate whether to perform a dry run or actual publish + - name: dryRun + type: boolean + default: true + + # Public NuGet source URL for publishing packages to public feed + - name: publicNuGetSource + type: string + + # Service connection name for authenticating to NuGet.org + - name: nugetServiceConnection + type: string + default: "ADO Nuget Org Connection" + + # Glob pattern to identify packages to be published + - name: packagesGlob + type: string + +steps: + - task: NuGetToolInstaller@1 + 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 }}" diff --git a/eng/pipelines/common/templates/steps/publish-symbols-step.yml b/eng/pipelines/common/templates/steps/publish-symbols-step.yml index a3cd272958..6c7bb472ff 100644 --- a/eng/pipelines/common/templates/steps/publish-symbols-step.yml +++ b/eng/pipelines/common/templates/steps/publish-symbols-step.yml @@ -6,45 +6,66 @@ # doc: https://www.osgwiki.com/wiki/Symbols_Publishing_Pipeline_to_SymWeb_and_MSDL # #################################################################################### parameters: + # Symbol account name in Azure DevOps organization where symbols will be published - name: SymAccount type: string - default: 'SqlClientDrivers' + default: "SqlClientDrivers" + # Boolean value to indicate whether to publish symbols - name: publishSymbols - type: string + type: boolean + default: false + # Version of the symbols being published - name: symbolsVersion type: string - default: '$(NuGetPackageVersion)' + default: "$(NugetPackageVersion)" + # Symbol server name (without .trafficmanager.net) - name: symbolServer type: string - default: '$(SymbolServer)' - + default: "$(SymbolServer)" + + # Token URI for authenticating to the symbol server - name: symbolTokenUri type: string - default: '$(SymbolTokenUri)' - + default: "$(SymbolTokenUri)" + + # Artifact name for the symbols being published - name: symbolsArtifactName type: string - + + # Servers to which symbols should be published - name: publishToServers type: object - default: + default: internal: true public: true + # Type of reference for which symbols are being published - name: referenceType default: project values: - - project - - package - + - project + - package + + # Product name associated with the symbols + # Symbols publishing Step is currently only configured for the MDS driver, as they are required to be published to 3 locations: + # 1. Azure DevOps Org, + # 2. MS Internal Symbols server + # 3. MS Public symbol server + # For other products: Symbols are uploaded to NuGet.org symbol server via snuget package push during package publishing. - name: product default: MDS values: - - MDS - - MSS + - MDS + - MSS + - AKV + + # Azure subscription for publishing symbols to internal and public symbol servers + - name: azureSubscription + type: string + default: "Symbols publishing Workload Identity federation service-ADO.Net" - name: buildConfiguration type: string @@ -53,84 +74,66 @@ parameters: - Release steps: -- powershell: 'Write-Host "##vso[task.setvariable variable=ArtifactServices.Symbol.AccountName;]${{parameters.SymAccount}}"' - displayName: 'Update Symbol.AccountName with ${{parameters.SymAccount}}' - condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) - -- ${{ if eq(parameters.product, 'MDS') }}: - - task: PublishSymbols@2 - displayName: 'Upload symbols to ${{parameters.SymAccount }} org' - inputs: - SymbolsFolder: '$(Build.SourcesDirectory)\artifacts\${{parameters.referenceType }}\bin' - SearchPattern: | - Windows_NT/${{ parameters.buildConfiguration }}.AnyCPU/**/Microsoft.Data.SqlClient.pdb - Unix/${{ parameters.buildConfiguration }}.AnyCPU/**/Microsoft.Data.SqlClient.pdb - IndexSources: false - SymbolServerType: TeamServices - SymbolsMaximumWaitTime: 60 - SymbolExpirationInDays: 1825 # 5 years - SymbolsProduct: Microsoft.Data.SqlClient - SymbolsVersion: ${{parameters.symbolsVersion }} - SymbolsArtifactName: ${{parameters.symbolsArtifactName }} - Pat: $(System.AccessToken) - condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) - -- task: AzureCLI@2 - displayName: 'Publish symbols' - condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) - inputs: - azureSubscription: 'Symbols publishing Workload Identity federation service-ADO.Net' - scriptType: ps - scriptLocation: inlineScript - inlineScript: | - $publishToInternalServer = "${{parameters.publishToServers.internal }}".ToLower() - $publishToPublicServer = "${{parameters.publishToServers.public }}".ToLower() - - echo "Publishing request name: ${{parameters.symbolsArtifactName }}" - echo "Publish to internal server: $publishToInternalServer" - echo "Publish to public server: $publishToPublicServer" - - $symbolServer = "${{parameters.symbolServer }}" - $tokenUri = "${{parameters.symbolTokenUri }}" - # Registered project name in the symbol publishing pipeline: https://portal.microsofticm.com/imp/v3/incidents/incident/520844254/summary - $projectName = "Microsoft.Data.SqlClient.SNI" - - # Get the access token for the symbol publishing service - $symbolPublishingToken = az account get-access-token --resource $tokenUri --query accessToken -o tsv - - echo "> 1.Symbol publishing token acquired." - - echo "Registering the request name ..." - $requestName = "${{parameters.symbolsArtifactName }}" - $requestNameRegistrationBody = "{'requestName': '$requestName'}" - Invoke-RestMethod -Method POST -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" -Body $requestNameRegistrationBody - - echo "> 2.Registration of request name succeeded." - - echo "Publishing the symbols ..." - $publishSymbolsBody = "{'publishToInternalServer': $publishToInternalServer, 'publishToPublicServer': $publishToPublicServer}" - echo "Publishing symbols request body: $publishSymbolsBody" - Invoke-RestMethod -Method POST -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests/$requestName" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" -Body $publishSymbolsBody - - echo "> 3.Request to publish symbols succeeded." - - # The following REST calls are used to check publishing status. - echo "> 4.Checking the status of the request ..." - - Invoke-RestMethod -Method GET -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests/$requestName" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" - - echo "Use below tables to interpret the values of xxxServerStatus and xxxServerResult fields from the response." - - echo "PublishingStatus" - echo "-----------------" - echo "0 NotRequested; The request has not been requested to publish." - echo "1 Submitted; The request is submitted to be published" - echo "2 Processing; The request is still being processed" - echo "3 Completed; The request has been completed processing. It can be failed or successful. Check PublishingResult to get more details" - - echo "PublishingResult" - echo "-----------------" - echo "0 Pending; The request has not completed or has not been requested." - echo "1 Succeeded; The request has published successfully" - echo "2 Failed; The request has failed to publish" - echo "3 Cancelled; The request was cancelled" + - powershell: 'Write-Host "##vso[task.setvariable variable=ArtifactServices.Symbol.AccountName;]${{parameters.SymAccount}}"' + displayName: "Update Symbol.AccountName with ${{parameters.SymAccount}}" + condition: and(succeeded(), eq(parameters.publishSymbols, true)) + + - ${{ if and(eq(parameters.publishSymbols, true), eq(parameters.product, 'MDS')) }}: + - task: PublishSymbols@2 + displayName: "Upload MDS symbols to ${{parameters.SymAccount }} org" + inputs: + SymbolsFolder: '$(Build.SourcesDirectory)\artifacts\${{parameters.referenceType }}\bin' + SearchPattern: | + Windows_NT/${{ parameters.buildConfiguration }}.AnyCPU/**/Microsoft.Data.SqlClient.pdb + Unix/${{ parameters.buildConfiguration }}.AnyCPU/**/Microsoft.Data.SqlClient.pdb + IndexSources: false + SymbolServerType: TeamServices + SymbolsMaximumWaitTime: 60 + SymbolExpirationInDays: 1825 # 5 years + SymbolsProduct: Microsoft.Data.SqlClient + SymbolsVersion: ${{parameters.symbolsVersion }} + SymbolsArtifactName: ${{parameters.symbolsArtifactName }} + Pat: $(System.AccessToken) + condition: and(succeeded(), eq(parameters.publishSymbols, true)) + - task: AzureCLI@2 + displayName: "Publish MDS symbols" + condition: and(succeeded(), eq(parameters.publishSymbols, true)) + inputs: + azureSubscription: ${{ parameters.azureSubscription }} + scriptType: ps + scriptLocation: inlineScript + inlineScript: | + $publishToInternalServer = "${{parameters.publishToServers.internal }}".ToLower() + $publishToPublicServer = "${{parameters.publishToServers.public }}".ToLower() + + echo "Publishing request name: ${{parameters.symbolsArtifactName }}" + echo "Publish to internal server: $publishToInternalServer" + echo "Publish to public server: $publishToPublicServer" + + $symbolServer = "${{parameters.symbolServer }}" + $tokenUri = "${{parameters.symbolTokenUri }}" + $projectName = "Microsoft.Data.SqlClient.SNI" + + $symbolPublishingToken = az account get-access-token --resource $tokenUri --query accessToken -o tsv + + echo "> 1.Symbol publishing token acquired." + + echo "Registering the request name ..." + $requestName = "${{parameters.symbolsArtifactName }}" + $requestNameRegistrationBody = "{'requestName': '$requestName'}" + Invoke-RestMethod -Method POST -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" -Body $requestNameRegistrationBody + + echo "> 2.Registration of request name succeeded." + + echo "Publishing the symbols ..." + $publishSymbolsBody = "{'publishToInternalServer': $publishToInternalServer, 'publishToPublicServer': $publishToPublicServer}" + echo "Publishing symbols request body: $publishSymbolsBody" + Invoke-RestMethod -Method POST -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests/$requestName" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" -Body $publishSymbolsBody + + echo "> 3.Request to publish symbols succeeded." + + echo "> 4.Checking the status of the request ..." + Invoke-RestMethod -Method GET -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests/$requestName" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" + + echo "PublishingStatus"; echo "-----------------"; echo "0 NotRequested"; echo "1 Submitted"; echo "2 Processing"; echo "3 Completed" + echo "PublishingResult"; echo "-----------------"; echo "0 Pending"; echo "1 Succeeded"; echo "2 Failed"; echo "3 Cancelled" diff --git a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml index abf2d148d9..22421eeb73 100644 --- a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml @@ -8,77 +8,127 @@ name: $(Year:YY)$(DayOfYear)$(Rev:.r) trigger: branches: include: - - internal/main + - internal/main paths: include: - - src - - eng - - tools - - .config - - build.proj - - Nuget.config - - '*.cmd' - - '*.sh' + - src + - eng + - tools + - .config + - build.proj + - Nuget.config + - "*.cmd" + - "*.sh" schedules: -- cron: '30 4 * * Mon' - displayName: Weekly Sunday 9:30 PM (UTC - 7) Build - branches: - include: - - internal/main - always: true + - cron: "30 4 * * Mon" + displayName: Weekly Sunday 9:30 PM (UTC - 7) Build + branches: + include: + - internal/main + always: true -- cron: '30 3 * * Mon-Fri' - displayName: Mon-Fri 8:30 PM (UTC - 7) Build - branches: - include: - - internal/main - -parameters: # parameters are shown up in ADO UI in a build queue time -- name: 'debug' - displayName: 'Enable debug output' - type: boolean - default: false - -- name: publishSymbols - displayName: 'Publish symbols' - type: boolean - default: false - -- name: CurrentNetFxVersion - displayName: 'Lowest supported .NET Framework version (MDS validation)' - type: string - default: 'net462' - -- name: oneBranchType - displayName: 'Select OneBranch template' - default: Official - values: - - NonOfficial - - Official - -- name: isPreview - displayName: 'Is this a preview build?' - type: boolean - default: false - -# The timeout, in minutes, for each test job. -- name: testJobTimeout - displayName: 'Test job timeout (in minutes)' - type: number - default: 90 + - cron: "30 3 * * Mon-Fri" + displayName: Mon-Fri 8:30 PM (UTC - 7) Build + branches: + include: + - internal/main + +# These parameters are shown up in ADO UI in a build queue time +parameters: + # Enable debug output for this build + - name: "debug" + displayName: "Enable debug output" + type: boolean + default: false + + # Boolean value to indicate whether to publish symbols + - name: publishSymbols + displayName: "Publish symbols" + type: boolean + default: false + + # Lowest supported .NET Framework version for this build (for MDS validation) + - name: CurrentNetFxVersion + displayName: "Lowest supported .NET Framework version (MDS validation)" + type: string + default: "net462" + + # OneBranch template type: Official or NonOfficial + - name: oneBranchType + displayName: "Select OneBranch template" + default: Official + values: + - NonOfficial + - Official + + # Is this a preview build? + - name: isPreview + displayName: "Is this a preview build?" + type: boolean + default: false + + # The timeout, in minutes, for each test job. + - name: testJobTimeout + displayName: 'Test job timeout (in minutes)' + type: number + default: 90 + + # Manual Release Parameters + # Release stage runs ONLY when build is manually queued AND runRelease = true. + - name: runRelease + displayName: "Run manual release stage" + type: boolean + default: false + + # Publish destination: Internal or Public + - name: publishDestination + displayName: "Publish destination" + type: string + default: Internal + values: + - Internal + - Public + + # Boolean value to indicate whether to perform a dry run or actual publish of NuGet Packages + - name: dryRun + displayName: "Dry run (no publish)" + type: boolean + default: true + + # Internal feed source URL for publishing packages to Azure DevOps Feed + - name: internalFeedSource + displayName: "Internal feed source URL" + type: string + default: "" + + # Public NuGet source URL for publishing packages to public feed + - name: publicNuGetSource + displayName: "Public NuGet source URL" + type: string + default: "https://api.nuget.org/v3/index.json" + + # Product name associated with the packages + - name: product + displayName: "Product code (MDS|MSS|AKV)" + type: string + default: "MDS" + values: + - MDS + - MSS + - AKV variables: - template: /eng/pipelines/libraries/variables.yml@self - name: packageFolderName - value: drop_buildMDS_build_signed_package + value: drop_build${{ parameters['product'] }}_build_signed_package - name: PublishSymbols value: ${{ parameters['publishSymbols'] }} - name: CurrentNetFxVersion value: ${{ parameters['CurrentNetFxVersion'] }} resources: - repositories: + repositories: - repository: templates type: git name: OneBranch.Pipelines/GovernedTemplates @@ -120,7 +170,7 @@ extends: break: true # always break the build on policheck issues. You can disable it by setting to 'false' exclusionsFile: $(REPOROOT)\.config\PolicheckExclusions.xml asyncSdl: - enabled: false + enabled: false credscan: enabled: ${{ not(parameters['isPreview']) }} suppressionsFile: $(REPOROOT)/.config/CredScanSuppressions.json @@ -139,38 +189,53 @@ extends: tsaOptionsPath: $(REPOROOT)\.config\tsaoptions.json disableLegacyManifest: true stages: - - stage: buildMDS - displayName: 'Build MDS' - jobs: - - template: eng/pipelines/common/templates/jobs/build-signed-package-job.yml@self - parameters: - symbolsFolder: $(symbolsFolder) - softwareFolder: $(softwareFolder) - publishSymbols: ${{ parameters['publishSymbols'] }} - isPreview: ${{ parameters['isPreview'] }} - - - stage: mds_package_validation - displayName: 'MDS Package Validation' - dependsOn: buildMDS - jobs: - - template: eng/pipelines/common/templates/jobs/validate-signed-package-job.yml@self + # TODO: Build other products as well based on parameters['product'] + # This pipeline currently only builds MDS product. + - ${{ if eq(parameters['product'], 'MDS') }}: + - stage: buildMDS + displayName: "Build MDS" + jobs: + - template: eng/pipelines/common/templates/jobs/build-signed-package-job.yml@self + parameters: + symbolsFolder: $(symbolsFolder) + softwareFolder: $(softwareFolder) + publishSymbols: ${{ parameters['publishSymbols'] }} + isPreview: ${{ parameters['isPreview'] }} + - stage: mds_package_validation + displayName: "MDS Package Validation" + dependsOn: buildMDS + jobs: + - template: eng/pipelines/common/templates/jobs/validate-signed-package-job.yml@self + parameters: + packageFolderName: $(packageFolderName) + isPreview: ${{ parameters['isPreview'] }} + downloadPackageStep: + download: current + artifact: $(packageFolderName) + patterns: "**/*.*nupkg" + displayName: "Download NuGet Package" + # Disabling as of 10/15/2025 due to OneBranch apparently disallowing MSBuild tasks in validation stages. + # - template: eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml@self + # parameters: + # packageFolderName: $(packageFolderName) + # isPreview: ${{ parameters['isPreview'] }} + # timeout: ${{ parameters.testJobTimeout }} + # downloadPackageStep: + # download: current + # artifact: $(packageFolderName) + # patterns: '**/*.nupkg' + # displayName: 'Download NuGet Package' + # Manual Release Stage (templated) + - template: eng/pipelines/common/templates/stages/release-stage.yml@self parameters: + runRelease: ${{ parameters.runRelease }} + publishDestination: ${{ parameters.publishDestination }} + dryRun: ${{ parameters.dryRun }} + approvalAliases: '[ADO.Net]\\SqlClient Admins' + internalFeedSource: ${{ parameters.internalFeedSource }} + publicNuGetSource: ${{ parameters.publicNuGetSource }} + publishSymbols: ${{ parameters.publishSymbols }} + isPreview: ${{ parameters.isPreview }} packageFolderName: $(packageFolderName) - isPreview: ${{ parameters['isPreview'] }} - downloadPackageStep: - download: current - artifact: $(packageFolderName) - patterns: '**/*.*nupkg' - displayName: 'Download NuGet Package' - -# Disabling as of 10/15/2025 due to OneBranch apparently disallowing MSBuild tasks in validation stages. -# - template: eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml@self -# parameters: -# packageFolderName: $(packageFolderName) -# isPreview: ${{ parameters['isPreview'] }} -# timeout: ${{ parameters.testJobTimeout }} -# downloadPackageStep: -# download: current -# artifact: $(packageFolderName) -# patterns: '**/*.nupkg' -# displayName: 'Download NuGet Package' + nugetPackageVersion: $(NugetPackageVersion) + product: ${{ parameters.product }} diff --git a/tools/scripts/downloadLatestNuget.ps1 b/tools/scripts/downloadLatestNuget.ps1 index faddf3bb09..d978897873 100644 --- a/tools/scripts/downloadLatestNuget.ps1 +++ b/tools/scripts/downloadLatestNuget.ps1 @@ -2,7 +2,6 @@ # The .NET Foundation licenses this file to you under the MIT license. # See the LICENSE file in the project root for more information. # Script: downloadLatestNuget.ps1 -# Author: Keerat Singh # Date: 07-Dec-2018 # Comments: This script downloads the latest NuGet Binary. # @@ -24,4 +23,4 @@ Function DownloadLatestNuget() Write-Output "Destination: $nugetDestPath" Start-BitsTransfer -Source $nugetSrcPath -Destination $nugetDestPath\nuget.exe } -DownloadLatestNuget \ No newline at end of file +DownloadLatestNuget diff --git a/tools/scripts/publishPackagesToAzDOFeed.ps1 b/tools/scripts/publishPackagesToAzDOFeed.ps1 new file mode 100644 index 0000000000..cc5f8f2669 --- /dev/null +++ b/tools/scripts/publishPackagesToAzDOFeed.ps1 @@ -0,0 +1,93 @@ +# 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. + +# Script: publishPackagesToAzDOFeed.ps1 +# Date: 10-12-2025 +# Comments: This script publishes packages to an internal Azure DevOps Feed. + +param( + [bool]$dryRun = $true, + [string]$internalFeedSource, + [string]$packagesGlob = "artifacts/packages/**/*.nupkg" +) + +Function PublishToInternalFeed() { + $SRC = $internalFeedSource + + if ([string]::IsNullOrEmpty($SRC)) { + Write-Host "Internal feed source parameter not set." -ForegroundColor Red + exit 1 + } + + if ($dryRun) { + Write-Host "[DRY RUN] Listing packages targeted for push to: $internalFeedSource" -ForegroundColor Cyan + } else { + Write-Host "Listing packages targeted for push to: $internalFeedSource" -ForegroundColor Cyan + } + Write-Host "Using glob pattern: $packagesGlob" -ForegroundColor Cyan + + # Parse the glob pattern to extract directory and filename pattern + $glob = $packagesGlob + $lastSlashIndex = $glob.LastIndexOf('/') + + if ($lastSlashIndex -ge 0) { + $dir = $glob.Substring(0, $lastSlashIndex) + $namePattern = $glob.Substring($lastSlashIndex + 1) + } else { + $dir = "." + $namePattern = $glob + } + + # Handle ** wildcard for recursive search + $recurse = $dir -like '*/**' + if ($recurse) { + $dir = $dir -replace '/?\*\*/?', '' + } + + Write-Host "Resolved directory: $dir" -ForegroundColor Yellow + Write-Host "Filename pattern: $namePattern" -ForegroundColor Yellow + + if (Test-Path $dir -PathType Container) { + Write-Host "Matched files:" -ForegroundColor Green + + # Find matching .nupkg files + $packages = Get-ChildItem -Path $dir -Filter "*.nupkg" -Recurse:$recurse -File -ErrorAction SilentlyContinue + + if ($packages) { + foreach ($package in $packages) { + Write-Host " - $($package.FullName)" -ForegroundColor Gray + } + + if (-not $dryRun) { + Write-Host "`nPushing packages to feed..." -ForegroundColor Cyan + foreach ($package in $packages) { + Write-Host "`nPushing packages to feed..." -ForegroundColor Cyan + $anyPushFailed = $false + foreach ($package in $packages) { + Write-Host "Pushing package: $($package.FullName)" -ForegroundColor Yellow + dotnet nuget push $package.FullName --source $SRC --api-key az + + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to push package: $($package.FullName)" -ForegroundColor Red + $anyPushFailed = $true + } else { + Write-Host "Successfully pushed: $($package.Name)" -ForegroundColor Green + } + } + if ($anyPushFailed) { + Write-Host "`nOne or more packages failed to push." -ForegroundColor Red + exit 1 + } + } else { + Write-Host "`n[DRY RUN] No packages were pushed. Set -dryRun `$false to push." -ForegroundColor Yellow + } + } else { + Write-Host "No .nupkg files found matching the pattern." -ForegroundColor Yellow + } + } else { + Write-Host "Directory does not exist: $dir" -ForegroundColor Red + } +} + +PublishToInternalFeed