From f6a74cc8c27571550788e52d04bb977d369f2344 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:11:04 -0300 Subject: [PATCH 01/16] Abstractions Package - C# project changes (#3626) --- .editorconfig | 2 +- BUILDGUIDE.md | 81 +++-- NuGet.config | 21 +- build.proj | 162 ++++++++-- .../jobs/build-signed-package-job.yml | 1 + .../templates/jobs/ci-build-nugets-job.yml | 2 + .../steps/generate-nuget-package-step.yml | 14 +- .../libraries/ci-build-variables.yml | 10 + eng/pipelines/libraries/common-variables.yml | 12 +- src/Directory.Build.props | 1 - src/Directory.Packages.props | 7 + .../Abstractions/README.md | 285 ++++++++++++++++++ .../Abstractions/doc/Sample.xml | 18 ++ .../Abstractions/src/Abstractions.csproj | 71 +++++ .../src/AbstractionsVersions.props | 70 +++++ .../Abstractions/src/Sample.cs | 20 ++ .../test/Abstractions.Test.csproj | 26 ++ .../Abstractions/test/SampleTest.cs | 19 ++ src/Microsoft.Data.SqlClient.sln | 43 ++- .../add-ons/Directory.Build.props | 1 - .../ref/Microsoft.Data.SqlClient.csproj | 18 +- .../src/Microsoft.Data.SqlClient.csproj | 16 + .../netfx/ref/Microsoft.Data.SqlClient.csproj | 17 ++ .../netfx/src/Microsoft.Data.SqlClient.csproj | 17 ++ tools/props/Versions.props | 70 +++-- tools/specs/Microsoft.Data.SqlClient.nuspec | 4 + ...waysEncrypted.AzureKeyVaultProvider.nuspec | 34 ++- tools/targets/GenerateMdsPackage.targets | 13 + tools/targets/GenerateNugetPackage.targets | 31 -- .../targets/GenerateSqlServerPackage.targets | 13 + .../GenerateAKVProviderNugetPackage.targets | 12 - .../add-ons/GenerateAkvPackage.targets | 13 + 32 files changed, 951 insertions(+), 173 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/Sample.xml create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Sample.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SampleTest.cs create mode 100644 tools/targets/GenerateMdsPackage.targets delete mode 100644 tools/targets/GenerateNugetPackage.targets create mode 100644 tools/targets/GenerateSqlServerPackage.targets delete mode 100644 tools/targets/add-ons/GenerateAKVProviderNugetPackage.targets create mode 100644 tools/targets/add-ons/GenerateAkvPackage.targets diff --git a/.editorconfig b/.editorconfig index cbc3fa7858..1b76fecaaa 100644 --- a/.editorconfig +++ b/.editorconfig @@ -162,7 +162,7 @@ dotnet_diagnostic.xUnit1031.severity=none dotnet_diagnostic.xUnit1030.severity=none # Xml files -[*.{xml,csproj,stylecop,resx,ruleset,props,targets,config,nuspec}] +[*.{xml,slnx,proj,csproj,stylecop,resx,ruleset,props,targets,config,nuspec}] indent_size = 2 # Shell scripts diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index efeb747cad..7984813fb1 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -16,28 +16,33 @@ Once the environment is setup properly, execute the desired set of commands belo ### Targets +The following build targets are defined in `build.proj`: + |Target|Description| |-|-| |`BuildAllConfigurations`|Default target. Builds the .NET Framework and .NET drivers for all target frameworks and operating systems.| +|`BuildAbstractions`|Restore, build, and pack the Abstractions package, publishing the resulting NuGet into `packages/`.| |`BuildNetCore`|Builds the .NET driver for all target frameworks.| |`BuildNetCoreAllOS`|Builds the .NET driver for all target frameworks and operating systems.| |`BuildNetFx`|Builds the .NET Framework driver for all target frameworks.| |`BuildTestsNetCore`|Builds tests for the .NET driver.| |`BuildTestsNetFx`|Builds tests for the .NET Framework driver.| -|`Clean`|Cleans generated files.| -|`Restore`|Restores Nuget packages.| +|`Clean`|Cleans all generated files.| +|`Restore`|Restores NuGet packages.| |`RunTests`|Runs the unit, functional, and manual tests for the .NET Framework and .NET drivers| |`RunUnitTests`|Runs just the unit tests for the .NET Framework and .NET drivers| |`RunFunctionalTests`|Runs just the functional tests for the .NET Framework and .NET drivers| |`RunManualTests`|Runs just the manual tests for the .NET Framework and .NET drivers| |`BuildAkv`|Builds the Azure Key Vault Provider package for all supported platforms.| - ### Parameters + +The following parameters may be defined as MSBuild properties to configure the +build: + |Name|Supported Values|Default|Description| |-|-|-|-| |`Configuration`|`Debug`, `Release`|`Debug`|Sets the release configuration.| -|`BuildNetFx`|`true`, `false`|`true` (Windows), `false` (other)|If false, skips building the .NET Framework driver on Windows.| |`OSGroup`|`Unix`, `Windows_NT`, `AnyOS`|typically defaults to the client system's OS, unless using `BuildAllConfigurations` or an `AnyOS` specific target|The operating system to target.| |`Platform`|`AnyCPU`, `x86`, `x64`, `ARM`, `ARM64`|`AnyCPU`|May only be set when using package reference type or running tests.| |`TestSet`|`1`, `2`, `3`, `AE`|all|Build or run a subset of the manual tests. Omit (default) to target all tests.| @@ -45,12 +50,11 @@ Once the environment is setup properly, execute the desired set of commands belo |`TF`|`net8.0`, `net462`, `net47`, `net471`, `net472`, `net48`, `net481`|`net8.0` in netcore, `net462` in netfx|Sets the target framework when building or running tests. Not applicable when building the drivers.| |`ResultsDirectory`|An absolute file path|./TestResults relative to current directory|Specifies where to write test results.| - ## Example Workflows using MSBuild (Recommended) + Using the default configuration and running all tests: ```bash -msbuild msbuild -t:BuildTestsNetFx -p:TF=net462 msbuild -t:BuildTestsNetCore msbuild -t:RunTests @@ -59,28 +63,25 @@ msbuild -t:RunTests Using the Release configuration: ```bash -msbuild -p:configuration=Release -msbuild -t:BuildTestsNetFx -p:TF=net462 -p:configuration=Release -msbuild -t:BuildTestsNetCore -p:configuration=Release -msbuild -t:RunTests -p:configuration=Release +msbuild -t:BuildTestsNetFx -p:TF=net462 -p:Configuration=Release +msbuild -t:BuildTestsNetCore -p:Configuration=Release +msbuild -t:RunTests -p:Configuration=Release ``` Running only the unit tests: ```bash -msbuild msbuild -t:BuildTestsNetFx -p:TF=net462 msbuild -t:BuildTestsNetCore msbuild -t:RunUnitTests ``` -Using a specific dotnet version/architecture: +Using a specific .NET runtime to run tests: ```bash -msbuild -p:configuration=Release -msbuild -t:BuildTestsNetFx -p:TF=net462 -p:configuration=Release -msbuild -t:BuildTestsNetCore -p:configuration=Release -msbuild -t:RunTests -p:configuration=Release -p:DotnetPath=C:\net8-win-x86\ +msbuild -t:BuildTestsNetFx -p:TF=net462 +msbuild -t:BuildTestsNetCore +msbuild -t:RunTests -p:DotnetPath=C:\net8-win-x86\ ``` ### Running Manual Tests @@ -119,15 +120,13 @@ Manual Tests require the below setup to run: |IsManagedInstance | (Optional) When set to `true` **TVP** related tests will use on non-Azure bs files to compare test results. this is needed when testing against Managed Instances or TVP Tests will fail on Test set 3. The default value is `false`. | |PowerShellPath | The full path to PowerShell.exe. This is not required if the path is present in the PATH environment variable. | `D:\\escaped\\absolute\\path\\to\\PowerShell.exe` | - ## Example workflows using the Dotnet SDK -#### Run Functional Tests +### Run Functional Tests - Windows (`netfx x86`): ```bash -msbuild dotnet test "src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj" -p:Platform="x86" -p:Configuration="Release" -p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" ``` @@ -152,7 +151,8 @@ dotnet test "src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.S ```bash dotnet test "src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj" -p:Platform="AnyCPU" -p:Configuration="Release" -p:TestTargetOS="Unixnetcoreapp" --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonlinuxtests&category!=nonuaptests" ``` -#### Run Manual Tests + +### Run Manual Tests - Windows (`netfx x86`): @@ -194,35 +194,39 @@ dotnet test "src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlCl Tests can be built and run with custom "Reference Type" property that enables different styles of testing: -- "Project" => Build and run tests with Microsoft.Data.SqlClient as Project Reference -- "Package" => Build and run tests with Microsoft.Data.SqlClient as Package Reference with configured "TestMicrosoftDataSqlClientVersion" in "Versions.props" file. +- "Project" => Build and run tests with Microsoft.Data.SqlClient as a Project Reference +- "Package" => Build and run tests with Microsoft.Data.SqlClient as a Package Reference with configured "TestMicrosoftDataSqlClientVersion" in "Versions.props" file. > ************** IMPORTANT NOTE BEFORE PROCEEDING WITH "PACKAGE" REFERENCE TYPE *************** > CREATE A NUGET PACKAGE WITH BELOW COMMAND AND ADD TO LOCAL FOLDER + UPDATE NUGET CONFIG FILE TO READ FROM THAT LOCATION > > ```bash -> msbuild -p:configuration=Release +> msbuild -p:Configuration=Release > ``` A non-AnyCPU platform reference can only be used with package reference type. Otherwise, the specified platform will be replaced with AnyCPU in the build process. ### Building Tests with Reference Type -For .NET, all 4 reference types are supported: +For .NET: ```bash +# Project is the default reference type. The below commands are equivalent: +msbuild -t:BuildTestsNetCore msbuild -t:BuildTestsNetCore -p:ReferenceType=Project -# Default setting uses Project Reference. +# Package reference type: msbuild -t:BuildTestsNetCore -p:ReferenceType=Package ``` -For .NET Framework, below reference types are supported: +For .NET Framework: ```bash +# Project is the default reference type. The below commands are equivalent: +msbuild -t:BuildTestsNetFx -p:TF=net462 msbuild -t:BuildTestsNetFx -p:TF=net462 -p:ReferenceType=Project -# Default setting uses Project Reference. +# Package reference type: msbuild -t:BuildTestsNetFx -p:TF=net462 -p:ReferenceType=Package ``` @@ -241,26 +245,25 @@ Tests can be built and run with custom Target Frameworks. See the below examples ### Building Tests with custom target framework ```bash -msbuild -t:BuildTestsNetFx -p:TF=net462 # Build the tests for custom .NET Framework target +msbuild -t:BuildTestsNetFx -p:TF=net462 ``` ```bash -msbuild -t:BuildTestsNetCore -p:TF=net8.0 # Build the tests for custom .NET target +msbuild -t:BuildTestsNetCore -p:TF=net8.0 ``` ### Running Tests with custom target framework (traditional) ```bash +# Run tests with custom .NET Framework target dotnet test -p:TargetNetFxVersion=net462 ... -# Use above property to run Functional Tests with custom .NET Framework target +# Run tests with custom .NET target dotnet test -p:TargetNetCoreVersion=net8.0 ... -# Use above property to run Functional Tests with custom .NET target ``` - ## Using Managed SNI on Windows Managed SNI can be enabled on Windows by enabling the below AppContext switch: @@ -285,20 +288,6 @@ When connecting to a server, if a protocol lower than TLS 1.2 is negotiated, a s `Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning` -### Troubleshooting Docker issues - -There may be times where connection cannot be made to SQL Server, we found below ideas helpful: - -- Clear Docker images to create clean image from time-to-time, and clear docker cache if needed by running `docker system prune` in Command Prompt. - -- If you face `Microsoft.Data.SqlClient.SNI.dll not found` errors when debugging, try updating the below properties in the netcore\Microsoft.Data.SqlClient.csproj file and try again: - - ```xml - Unix - false - true - ``` - ## Collecting Code Coverage ### Using VSTest diff --git a/NuGet.config b/NuGet.config index d93875f3fb..3e762e7fe9 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,11 +1,30 @@  + + - + + + + + + + + diff --git a/build.proj b/build.proj index 79635218ff..af26f1dbfc 100644 --- a/build.proj +++ b/build.proj @@ -2,8 +2,9 @@ - - + + + @@ -29,7 +30,7 @@ true Configuration=$(Configuration);AssemblyVersion=$(SqlServerAssemblyVersion);AssemblyFileVersion=$(SqlServerAssemblyFileVersion);Version=$(SqlServerPackageVersion); Configuration=$(Configuration);AssemblyFileVersion=$(AssemblyFileVersion);TargetsWindows=$(TargetsWindows);TargetsUnix=$(TargetsUnix); - BuildProjectReferences=false;$(ProjectProperties);BuildForRelease=false;TargetNetCoreVersion=$(TargetNetCoreVersion);TargetNetFxVersion=$(TargetNetFxVersion) + $(ProjectProperties);BuildForRelease=false;TargetNetCoreVersion=$(TargetNetCoreVersion);TargetNetFxVersion=$(TargetNetFxVersion) TestResults + + @@ -89,32 +92,96 @@ - - - + + + - - + + + + + + + AbstractionsPackageVersion=$(AbstractionsPackageVersion) + + + + + $(AbstractionsProperties);AbstractionsAssemblyFileVersion=$(AbstractionsAssemblyFileVersion) + + + + + + + + + + + + + + + + + + + + - + - + - + - + @@ -127,7 +194,10 @@ - + @@ -144,27 +214,37 @@ - + - + + - + - - + + @@ -173,7 +253,9 @@ - + @@ -182,12 +264,18 @@ - + - + @@ -196,7 +284,10 @@ - + @@ -353,14 +444,14 @@ - + - - - - - - + + + + + + @@ -395,7 +486,10 @@ - + @@ -405,7 +499,9 @@ - + @@ -415,7 +511,9 @@ - + diff --git a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml index 91eb864337..21539540df 100644 --- a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml @@ -51,6 +51,7 @@ jobs: - template: ../steps/generate-nuget-package-step.yml@self parameters: OutputDirectory: $(artifactDirectory) + properties: 'AbstractionsPackageVersion=$(abstractionsPackageVersion)' - template: ../steps/esrp-code-signing-step.yml@self parameters: diff --git a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml index cb3790262c..5642d5be47 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -58,6 +58,7 @@ jobs: nuspecPath: 'tools/specs/Microsoft.Data.SqlClient.nuspec' OutputDirectory: $(packagePath) generateSymbolsPackage: false + properties: 'AbstractionsPackageVersion=$(abstractionsPackageVersion)' displayName: 'Generate NuGet package M.D.SqlClient' - template: ../steps/generate-nuget-package-step.yml@self @@ -67,6 +68,7 @@ jobs: nuspecPath: 'tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec' OutputDirectory: $(packagePath) generateSymbolsPackage: false + properties: 'MdsPackageVersion=$(mdsPackageVersion)' installNuget: false displayName: 'Generate NuGet package AKV Provider' diff --git a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml index 4e32f989c3..8a8fd00f6f 100644 --- a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml +++ b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml @@ -32,11 +32,11 @@ parameters: type: boolean default: true - - name: referenceType - default: project - values: - - project - - package + # Semi-colon separated properties to pass to nuget via the -properties + # argument. + - name: properties + type: string + default: '' steps: - ${{ if parameters.installNuget }}: @@ -55,6 +55,6 @@ steps: inputs: command: custom ${{ if parameters.generateSymbolsPackage }}: - arguments: 'pack -Symbols -SymbolPackageFormat snupkg ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};ReferenceType=${{parameters.referenceType}}"' + arguments: 'pack -Symbols -SymbolPackageFormat snupkg ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};${{parameters.properties}}"' ${{else }}: - arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};ReferenceType=${{parameters.referenceType}}"' + arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};${{parameters.properties}}"' diff --git a/eng/pipelines/libraries/ci-build-variables.yml b/eng/pipelines/libraries/ci-build-variables.yml index 122281e083..d3ae6fcbc3 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -23,3 +23,13 @@ variables: value: false - name: packagePath value: '$(Build.SourcesDirectory)/packages' + + # TODO(ADO-38703): Remove these when the other pipeline changes arrive. + - name: baseBuildNumber + value: $[ split(variables['Build.BuildNumber'], '.')[0] ] + - name: abstractionsPackageVersion + value: 1.0.0.$(baseBuildNumber) + - name: mdsPackageVersion + value: $(NugetPackageVersion) + - name: akvPackageVersion + value: $(NugetPackageVersion) diff --git a/eng/pipelines/libraries/common-variables.yml b/eng/pipelines/libraries/common-variables.yml index 20753536b8..a986c714af 100644 --- a/eng/pipelines/libraries/common-variables.yml +++ b/eng/pipelines/libraries/common-variables.yml @@ -34,7 +34,7 @@ variables: - name: Patch value: '0' - # Update this for preview releases. + # Update this for preview releases. - name: Preview value: '-preview' - name: Revision @@ -48,3 +48,13 @@ variables: value: '$(Major).$(Minor)$(Patch).$(Build.BuildNumber)' - name: nuspecPath value: '$(REPOROOT)/tools/specs/Microsoft.Data.SqlClient.nuspec' + + # TODO(ADO-38703): Remove these when the other pipeline changes arrive. + - name: baseBuildNumber + value: $[ split(variables['Build.BuildNumber'], '.')[0] ] + - name: abstractionsPackageVersion + value: 1.0.0.$(baseBuildNumber) + - name: mdsPackageVersion + value: $(NugetPackageVersion) + - name: akvPackageVersion + value: $(NugetPackageVersion) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 4c210df737..86f3529cbb 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -20,7 +20,6 @@ > msbuild -p:configuration=Release --> Project - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index db7bf67c00..c01b28e57e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -7,6 +7,13 @@ + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md new file mode 100644 index 0000000000..fbb8bfb738 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md @@ -0,0 +1,285 @@ +# MDS Azure Extension Design + +## Overview + +For the MDS 7.0.0 release, we are proposing the following package architecture +changes that will decouple several large dependencies from MDS and move them +into a new `Azure` extension package: + +- Create a new `Abstractions` package that all other MDS packages depend on. + - This will contain types and definitions common to the other MDS packages, + such as base classes, enums, delegates, etc. +- Create a new `Azure` package that will own the following implementations: + - Azure Authentication + - Azure Attestation + - Azure Key Vault interactions +- Move the above implementations out of MDS and into the new `Azure` package. +- Move the existing `AzureKeyVaultProvider` (AKV) implementation into the new + `Azure` extension package. + +This will reduce the main MDS package dependency tree along with a moderate +package size reduction. + +## Motivation + +Issue: [#1108](https://github.com/dotnet/SqlClient/issues/1108) + +Customers and the developer community have voiced concerns with MDS being +tightly coupled to Azure dependencies. Many customers do not use Azure and do +not want to deploy unnecessary DLLs with their applications. + +Moving the Azure dependent implementations into a separate `Azure` extension +package achieves two goals: + +- Remove Azure packages as direct dependencies of MDS and reduce the MDS + dependency tree. +- Clearly expose existing MDS extension points, prove their functionality, and + demonstrate how to use them. + +The following dependencies will be removed from the main MDS package: + +- `Azure.Identity` + - `Azure.Core` (transitive) + - `Microsoft.Identity.Client` (transitive) +- `Microsoft.IdentityModel.JsonWebTokens` + - `Microsoft.IdentityModel.Tokens` (transitive) + - `Microsoft.IdentityModel.Logging` (transitive) +- `Microsoft.IdentityModel.Protocols.OpenIdConnect` + - `Microsoft.IdentityModel.Protocols` (transitive) + +The following dependencies will be removed from the AKV Provider package: + +- `Azure.Core` +- `Azure.Security.KeyVault.Keys` + +## Package Architecture + +```mermaid +classDiagram +class MDS +class MDS.Extensions.Abstractions +class MDS.Extensions.Azure +class AKV Provider + +MDS --> MDS.Extensions.Abstractions +MDS ..> MDS.Extensions.Azure +MDS ..> AKV Provider +MDS.Extensions.Azure --> MDS.Extensions.Abstractions +AKV Provider --> MDS.Extensions.Azure + +MDS: Depend on MDS.Extensions.Abstractions +MDS: Load Azure or AKV assembly +MDS.Extensions.Abstractions: Azure Authentication Types +MDS.Extensions.Abstractions: Azure Attestation Types +MDS.Extensions.Abstractions: Azure Key Vault Types +MDS.Extensions.Azure: Depend on MDS.Extensions.Abstractions +MDS.Extensions.Azure: Authentication Implementation +MDS.Extensions.Azure: Attestation Implementation +MDS.Extensions.Azure: Key Vault Implementation +AKV Provider: Depend on MDS.Extensions.Azure +``` + +In previous MDS versions, the AKV package depended directly on the main MDS +package through a ranged version (for example [6.0.0, 7.0.0) - all 6.x +versions). With the new package architecture this is no longer the case. +Extension packages will not depend on the main MDS package, nor will the main +MDS package depend on any extension packages. All dependencies between MDS and +its extensions will occur through the `Abstractions` package. + +This new looser coupling gives applications the flexibility to depend on only +the main MDS package, or on MDS and a subset of it extension packages if +desired. + +## Consuming + +There are several ways that applications may consume MDS and its extensions: + +- MDS without Azure features +- MDS with MDS-supplied Azure features +- MDS with externally supplied Azure features + +Applications never need to directly depend on the `Abstractions` base package. +This will be transitively depended on by other MDS packages. + +### Without Azure Features + +Applications that do not use any Azure features will no longer bring in those +unwanted dependencies transitively. Simply include the main MDS package by +itself: + +```xml + + + +``` + +Calls to MDS APIs that require Azure features will throw an exception, since +no Azure feature implementation is present. + +### With MDS Azure Features + +Applications that wish to use MDS-supplied Azure features will need to include +the new `Azure` extension package as a direct dependency alongside the main MDS +package: + +```xml + + + + +``` + +MDS will automatically detect the `Azure` extension assemblies and load them. + +### With External Azure Features + +Applications that wish to use Azure features supplied by another (non-MDS) +package will need to include that package as a direct dependency alongside the +main MDS package: + +```xml + + + + +``` + +Additionally, applications will need to instruct MDS to use the external Azure +feature implementations via the appropriate APIs at runtime: + +- Authentication: [SqlAuthenticationProvider](https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlauthenticationprovider?view=sqlclient-dotnet-core-6.0) +- Attestation: _**New API will be exposed.**_ +- Key Valut: [SqlColumnEncryptionKeyStoreProvider](https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlcolumnencryptionkeystoreprovider?view=sqlclient-dotnet-core-6.0) + +## Versioning Strategy + +The MDS suite of packages will be versioned independently. This provides +flexibility to update APIs and implementations for packages as needed, avoiding +unnecessary version bumps and releases. The initial release of these packages +will have the following versions: + +|Package|Version|Comment| +|-|-|-| +|`Microsoft.Data.SqlClient.Extensions.Abstractions`|1.0.0|First version of this package.| +|`Microsoft.Data.SqlClient`|7.0.0|Major version bump due to breaking changes described in this document.| +|`Microsoft.Data.SqlClient.Extensions.Azure`|1.0.0|First version of this package.| +|`Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider`|7.0.0|_**Deprecated.**_| + +Going forward, each package will be versioned appropriately based on the nature +of the changes included with subsequent releases. + +**Note**: The `AzureKeyVaultProvider` package will remain at 7.0.0. It will be +deprecated and eventually removed, as it has been replaced by the `Azure` +extension package. + +## Intradependence + +The main MDS package and the new `Azure` package will depend on the +`Abstractions` package. When APIs are added, modified, or removed from the +`Abstractions` package, corresponding changes will be made to the dependent +packages as well. Those dependent packages will then take a strict dependency +on the appropriate `Abstractions` package version. This ensures that only +compatible extensions package versions can co-exist with the main MDS package. + +For example, imagine that a new extensible conenction pooling feature is added +to MDS. The `Abstractions` package would be updated to include any new pooling +APIs, the main MDS package would be updated to accept extensible pooling, and +the new pooling implementation would be included in a new `ConnectionPooling` +extension package. The versions of these packages would look something like +this: + +|Package|Version| +|-|-| +|`Microsoft.Data.SqlClient.Extensions.Abstractions`|1.1.0| +|`Microsoft.Data.SqlClient`|7.1.0| +|`Microsoft.Data.SqlClient.Extensions.ConnectionPooling`|1.0.0| + +Both the main MDS package and the new `ConnectionPooling` package would depend +on `Abstractions` v1.1.0. + +An application wishing to use the new `ConnectionPooling` v1.0.0 package must +also update the main MDS package to v7.1.0. The application would not be able +to use `ConnectionPooling` v1.0.0 and MDS v7.0.0. + +## Backwards Compatibility + +There are several backwards compatibility scenarios to consider for applications +that rely on MDS Azure features currently implemented in the main MDS package +and the AKV package. The new extensions package architecture aims to reduce the +friction for these apps, but not all scenarios will be seamless. + +All of the scenarios below assume that the application is upgrading to the +latest versions of MDS packages. + +### Apps using MDS Azure Authentication + +Applications currently using the MDS-supplied Azure Authentication features will +need to add a dependency on the `Azure` extension package to their project +alongside the main MDS package: + +```xml + + + + +``` + +All Azure Authentication namespaces and types will remain the same, so this +should be the only change necessary for applications. + +### Apps using MDS Azure Attestation + +Applications currently using the MDS-supplied Azure Attestation features will +need to add a dependency on the `Azure` extension package to their project +alongside the main MDS package: + +```xml + + + + +``` + +All Azure Attestation namespaces and types will remain the same, so this should +be the only change necessary for applications. + +### Apps using AKV Provider + +Applications currently using the MDS-supplied AKV provider will have two options +when upgrading to MDS v7.0.0. Both options rely on the main MDS package finding +and loading an appropriate DLL (assembly) at runtime. The absence of an +appropriate DLL will cause Azure Key Vault operations to throw an exception. + +#### Use Azure Extension + +This is the preferred approach. The application would be updated to depend +on the main MDS package and the `Azure` extension package: + +```xml + + + + +``` + +The `Azure` extension package will contain the same namespaces and types as the +current AKV provider and will be a drop-in replacement. The main MDS v7.0.0 +package will look for the `Azure` extension assembly and automatically load it. + +#### Use AKV Provider v7.0.0 + +This is a temporary solution. The AKV provider v7.0.0 will be marked as +deprecated and removed entirely at some point in the future. The applictaion +would remain dependent on the AKV provider, but must update to the v7.0.0 +package. Previous AKV package versions do not support main MDS package versions +beyond the v6.x range. + +```xml + + + + +``` + +This AKV Provider v7.0.0 package will be empty and simply depend on the `Azure` +extension package to transitively provide the Azure Key Vault features. diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/Sample.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/Sample.xml new file mode 100644 index 0000000000..8d5f5c44d5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/Sample.xml @@ -0,0 +1,18 @@ + + + + + + Sample class to demonstrate packaging and pipelines. + + + + Construct with a name. + The name. + + + Gets the name. + The name. + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj new file mode 100644 index 0000000000..485a3de83a --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj @@ -0,0 +1,71 @@ + + + + + + + + netstandard2.0 + + + + + enable + enable + + + + + Microsoft.Data.SqlClient.Extensions.Abstractions + Microsoft.Data.SqlClient.Extensions.Abstractions + + + $(_DefaultMajorVersion).0.0.0 + + $(AbstractionsAssemblyFileVersion) + $(AbstractionsAssemblyFileVersion) + $(AbstractionsPackageVersion) + + + + + <_Parameter1>true + + + + + + + + + $(AssemblyName) + $(AbstractionsPackageVersion) + $(PackagesDir) + true + snupkg + + Microsoft Corporation + Microsoft Corporation + Microsoft.Data.SqlClient Extensions Abstractions + https://github.com/dotnet/SqlClient + MIT + dotnet.png + + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props new file mode 100644 index 0000000000..29ff5899af --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/AbstractionsVersions.props @@ -0,0 +1,70 @@ + + + + + + + + + + + + + <_DefaultMajorVersion>1 + + + <_OurPackageVersion Condition="'$(AbstractionsPackageVersion)' != ''">$(AbstractionsPackageVersion) + <_OurPackageVersion Condition="'$(AbstractionsPackageVersion)' == ''">$(_DefaultMajorVersion).0.0.$(BuildNumber)-dev + + + + <_OurAssemblyFileVersion Condition="'$(AbstractionsAssemblyFileVersion)' != ''">$(AbstractionsAssemblyFileVersion) + + <_OurAssemblyFileVersion Condition="'$(AbstractionsAssemblyFileVersion)' == '' and '$(AbstractionsPackageVersion)' != ''">$(AbstractionsPackageVersion.Split('-')[0]) + + <_OurAssemblyFileVersion Condition="'$(AbstractionsAssemblyFileVersion)' == '' and '$(AbstractionsPackageVersion)' == ''">$(_DefaultMajorVersion).0.0.$(BuildNumber) + + + $(_OurPackageVersion) + $(_OurAssemblyFileVersion) + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Sample.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Sample.cs new file mode 100644 index 0000000000..bf22119436 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Sample.cs @@ -0,0 +1,20 @@ +namespace Microsoft.Data.SqlClient.Extensions.Abstractions; + +/// +public class Sample +{ + /// + public Sample(string name) + { + Name = name; + } + + /// + public string Name { get; private set; } + + // Update the name. + internal void SetName(string name) + { + Name = name; + } +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj new file mode 100644 index 0000000000..118b215737 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj @@ -0,0 +1,26 @@ + + + + net462;net47;net471;net472;net48;net481;net8.0;net9.0 + enable + enable + false + true + Microsoft.Data.SqlClient.Extensions.Abstractions.Test + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SampleTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SampleTest.cs new file mode 100644 index 0000000000..ab8e9da052 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SampleTest.cs @@ -0,0 +1,19 @@ +namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test; + +public class SampleTest +{ + [Fact] + public void Construction() + { + Assert.Equal("test", new Sample("test").Name); + } + + [Fact] + public void SetName() + { + var sample = new Sample("test"); + Assert.Equal("test", sample.Name); + sample.SetName("new name"); + Assert.Equal("new name", sample.Name); + } +} diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index 94900021ac..d7a2f70689 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -1,6 +1,7 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.0.31912.275 +VisualStudioVersion = 17.14.36203.30 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient\netfx\src\Microsoft.Data.SqlClient.csproj", "{407890AC-9876-4FEF-A6F1-F36A876BAADE}" EndProject @@ -314,6 +315,17 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlClient.Stress.Runner", "Microsoft.Data.SqlClient\tests\StressTests\SqlClient.Stress.Runner\SqlClient.Stress.Runner.csproj", "{4A9C11F4-9577-ABEC-C070-83A194746D9B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlClient.Stress.Tests", "Microsoft.Data.SqlClient\tests\StressTests\SqlClient.Stress.Tests\SqlClient.Stress.Tests.csproj", "{FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlClient.Extensions", "Microsoft.Data.SqlClient.Extensions", "{19F1F1E5-3013-7660-661A-2A15F7D606C1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Abstractions", "Abstractions", "{556B486E-F9B0-7EA9-6A25-DA560C312761}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{210228A5-979A-DE06-EE1F-B35C65E1583C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abstractions", "Microsoft.Data.SqlClient.Extensions\Abstractions\src\Abstractions.csproj", "{B21E7C94-D805-427E-928A-8DE8EA2F08CC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{59667E4C-0BD2-9F48-FB50-9E55DD8B1011}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abstractions.Test", "Microsoft.Data.SqlClient.Extensions\Abstractions\test\Abstractions.Test.csproj", "{04ACBF75-CFF2-41AB-B652-776BC0533490}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -643,6 +655,30 @@ Global {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Release|x64.Build.0 = Release|Any CPU {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Release|x86.ActiveCfg = Release|Any CPU {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C}.Release|x86.Build.0 = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|x64.ActiveCfg = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|x64.Build.0 = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|x86.ActiveCfg = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Debug|x86.Build.0 = Debug|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|Any CPU.Build.0 = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|x64.ActiveCfg = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|x64.Build.0 = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|x86.ActiveCfg = Release|Any CPU + {B21E7C94-D805-427E-928A-8DE8EA2F08CC}.Release|x86.Build.0 = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|x64.ActiveCfg = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|x64.Build.0 = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|x86.ActiveCfg = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Debug|x86.Build.0 = Debug|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|Any CPU.Build.0 = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x64.ActiveCfg = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x64.Build.0 = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x86.ActiveCfg = Release|Any CPU + {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -699,6 +735,11 @@ Global {92D9C6D6-6925-1AD1-69FA-485F83943BD2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {4A9C11F4-9577-ABEC-C070-83A194746D9B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {556B486E-F9B0-7EA9-6A25-DA560C312761} = {19F1F1E5-3013-7660-661A-2A15F7D606C1} + {210228A5-979A-DE06-EE1F-B35C65E1583C} = {556B486E-F9B0-7EA9-6A25-DA560C312761} + {B21E7C94-D805-427E-928A-8DE8EA2F08CC} = {210228A5-979A-DE06-EE1F-B35C65E1583C} + {59667E4C-0BD2-9F48-FB50-9E55DD8B1011} = {556B486E-F9B0-7EA9-6A25-DA560C312761} + {04ACBF75-CFF2-41AB-B652-776BC0533490} = {59667E4C-0BD2-9F48-FB50-9E55DD8B1011} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {01D48116-37A2-4D33-B9EC-94793C702431} diff --git a/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props b/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props index 7776439adc..dfeb60b38c 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props +++ b/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props @@ -9,7 +9,6 @@ true true Project - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFramework)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index 0944a4aea0..f4fa41bf45 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -46,7 +46,23 @@ - + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 44560401db..d46e4c5f37 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -1083,6 +1083,22 @@ + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index 380a2aa746..e8dbcf7070 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -49,5 +49,22 @@ + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 8b4a48c705..0886b0a973 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -1059,6 +1059,23 @@ + + + + + + + + diff --git a/tools/props/Versions.props b/tools/props/Versions.props index ad42c4964a..7a3ba3cc9a 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -1,29 +1,12 @@ - + + + - 7.0.0 0 - - - $(MdsVersionDefault).$(BuildNumber) - - 7.0.0.0 - - $(AssemblyFileVersion) - $(MdsVersionDefault)-dev - $(NugetPackageVersion) + + @@ -33,8 +16,47 @@ 1.0.0-dev $(SqlServerPackageVersion) + + + + + + + 7.0.0 + + $(MdsVersionDefault).$(BuildNumber)-dev + + + + + $(MdsVersionDefault).$(BuildNumber) + + + 7.0.0.0 + + $(AssemblyFileVersion) + + + $(MdsPackageVersion) + + + - $(NugetPackageVersion) + 7.0.0 + $(AkvVersionDefault).$(BuildNumber)-dev - $(NugetPackageVersion) + $(MdsPackageVersion) diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index fb3fe267fc..78365bd21e 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -32,6 +32,7 @@ + @@ -48,6 +49,7 @@ + @@ -62,6 +64,7 @@ + @@ -76,6 +79,7 @@ + diff --git a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec index 0816fd3086..a313d0af12 100644 --- a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec +++ b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec @@ -25,17 +25,23 @@ Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyStoreProvider.SqlColumnEncrypti sqlclient microsoft.data.sqlclient azurekeyvaultprovider akvprovider alwaysencrypted - + - + + + + + + + @@ -46,22 +52,22 @@ Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyStoreProvider.SqlColumnEncrypti - - - - - + + + + + - - - + + + - - - + + + - + + + + + + + + + diff --git a/tools/targets/GenerateNugetPackage.targets b/tools/targets/GenerateNugetPackage.targets deleted file mode 100644 index 4c8cea4159..0000000000 --- a/tools/targets/GenerateNugetPackage.targets +++ /dev/null @@ -1,31 +0,0 @@ - - - - - $(NugetPackageVersion)-debug - - - - - - - - - - - - - $(SqlServerPackageVersion)-debug - - - - - - - - - - - diff --git a/tools/targets/GenerateSqlServerPackage.targets b/tools/targets/GenerateSqlServerPackage.targets new file mode 100644 index 0000000000..ea6655dcee --- /dev/null +++ b/tools/targets/GenerateSqlServerPackage.targets @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/tools/targets/add-ons/GenerateAKVProviderNugetPackage.targets b/tools/targets/add-ons/GenerateAKVProviderNugetPackage.targets deleted file mode 100644 index 78da74bf32..0000000000 --- a/tools/targets/add-ons/GenerateAKVProviderNugetPackage.targets +++ /dev/null @@ -1,12 +0,0 @@ - - - - - $(NugetPackageVersion)-debug - - - - - - diff --git a/tools/targets/add-ons/GenerateAkvPackage.targets b/tools/targets/add-ons/GenerateAkvPackage.targets new file mode 100644 index 0000000000..fd822442a2 --- /dev/null +++ b/tools/targets/add-ons/GenerateAkvPackage.targets @@ -0,0 +1,13 @@ + + + + + + + + + + + + From c66066a908e64d175853cebf09aab32bd31faf5d Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 16 Oct 2025 13:23:37 -0300 Subject: [PATCH 02/16] Abstractions Package - Pipeline Changes (#3628) --- build.proj | 1 - eng/pipelines/akv-official-pipeline.yml | 9 +- .../jobs/build-signed-package-job.yml | 54 ++++- .../templates/jobs/ci-build-nugets-job.yml | 83 +++++-- .../templates/jobs/ci-run-tests-job.yml | 107 +++++---- .../jobs/run-tests-package-reference-job.yml | 8 +- .../jobs/validate-signed-package-job.yml | 84 +++---- .../templates/stages/ci-run-tests-stage.yml | 53 +++-- ...ld-all-configurations-signed-dlls-step.yml | 37 +-- .../templates/steps/build-all-tests-step.yml | 44 ++-- .../build-and-run-tests-netcore-step.yml | 12 +- .../steps/build-and-run-tests-netfx-step.yml | 12 +- .../templates/steps/ci-prebuild-step.yml | 24 +- .../templates/steps/ci-project-build-step.yml | 39 ++-- .../templates/steps/code-analyze-step.yml | 50 ++-- .../steps/copy-dlls-for-test-step.yml | 4 +- .../steps/generate-nuget-package-step.yml | 19 +- .../templates/steps/publish-symbols-step.yml | 2 +- .../templates/steps/run-all-tests-step.yml | 22 +- .../update-nuget-config-local-feed-step.yml | 80 +++---- eng/pipelines/dotnet-sqlclient-ci-core.yml | 218 ++++++++++-------- ...qlclient-ci-package-reference-pipeline.yml | 84 +++++-- ...qlclient-ci-project-reference-pipeline.yml | 74 ++++-- .../dotnet-sqlclient-signing-pipeline.yml | 15 +- eng/pipelines/jobs/build-akv-official-job.yml | 37 +-- .../jobs/pack-abstractions-package-ci-job.yml | 148 ++++++++++++ eng/pipelines/jobs/stress-tests-ci-job.yml | 2 +- .../jobs/test-abstractions-package-ci-job.yml | 174 ++++++++++++++ .../libraries/ci-build-variables.yml | 19 +- eng/pipelines/libraries/common-variables.yml | 51 ++-- .../libraries/mds-validation-variables.yml | 2 +- .../build-abstractions-package-ci-stage.yml | 119 ++++++++++ .../steps/compound-build-akv-step.yml | 18 +- .../steps/compound-nuget-pack-step.yml | 38 ++- .../steps/roslyn-analyzers-akv-step.yml | 15 +- .../variables/akv-official-variables.yml | 6 +- .../ref/Microsoft.Data.SqlClient.csproj | 3 - .../src/Microsoft.Data.SqlClient.csproj | 3 - .../netfx/ref/Microsoft.Data.SqlClient.csproj | 3 - .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 - tools/props/Versions.props | 6 +- tools/specs/Microsoft.Data.SqlClient.nuspec | 143 ++++++------ ...waysEncrypted.AzureKeyVaultProvider.nuspec | 35 ++- tools/targets/GenerateMdsPackage.targets | 2 +- .../add-ons/GenerateAkvPackage.targets | 2 +- 45 files changed, 1319 insertions(+), 645 deletions(-) create mode 100644 eng/pipelines/jobs/pack-abstractions-package-ci-job.yml create mode 100644 eng/pipelines/jobs/test-abstractions-package-ci-job.yml create mode 100644 eng/pipelines/stages/build-abstractions-package-ci-stage.yml diff --git a/build.proj b/build.proj index af26f1dbfc..2a41791844 100644 --- a/build.proj +++ b/build.proj @@ -450,7 +450,6 @@ - diff --git a/eng/pipelines/akv-official-pipeline.yml b/eng/pipelines/akv-official-pipeline.yml index d7bc900bb8..316ca9e6ea 100644 --- a/eng/pipelines/akv-official-pipeline.yml +++ b/eng/pipelines/akv-official-pipeline.yml @@ -105,7 +105,8 @@ extends: roslyn: enabled: ${{ parameters.runSdlTasks }} break: true - # Requires RoslynAnalyzers task to be added after build task + # Requires RoslynAnalyzers task to be added somewhere in + # the build stage. publishLogs: enabled: ${{ parameters.runSdlTasks }} @@ -113,7 +114,7 @@ extends: sbom: enabled: ${{ parameters.runSdlTasks }} packageName: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider' - packageVersion: ${{ variables.nugetPackageVersion }} + packageVersion: ${{ variables.akvPackageVersion }} tsa: # OneBranch publishes all sdl results to TSA. If TSA is disabled all SDL tools will @@ -127,11 +128,11 @@ extends: jobs: - template: /eng/pipelines/jobs/build-akv-official-job.yml@self parameters: + akvAssemblyFileVersion: '${{ variables.assemblyFileVersion }}' + akvPackageVersion: '${{ variables.akvPackageVersion }}' apiScanDllPath: '${{ variables.apiScanDllPath }}' apiScanPdbPath: '${{ variables.apiScanPdbPath }}' - assemblyFileVersion: '${{ variables.assemblyFileVersion }}' buildConfiguration: '${{ parameters.buildConfiguration }}' - nugetPackageVersion: '${{ variables.nugetPackageVersion }}' mdsPackageVersion: '${{ variables.mdsPackageVersion }}' publishSymbols: '${{ parameters.publishSymbols }}' signingAppRegistrationClientId: '$(SigningAppRegistrationClientId)' diff --git a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml index 21539540df..b90fe026a8 100644 --- a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml @@ -27,8 +27,10 @@ jobs: variables: - template: ../../../libraries/variables.yml@self - ${{ if parameters.isPreview }}: - - name: NugetPackageVersion - value: $(PreviewNugetPackageVersion) + - name: abstractionsPackageVersion + value: $(abstractionsPackagePreviewVersion) + - name: mdsPackageVersion + value: $(previewMdsPackageVersion) steps: - script: SET @@ -37,20 +39,56 @@ jobs: - powershell: | Write-Host "##vso[task.setvariable variable=CDP_BUILD_TYPE_COPY;isOutput=true]$($env:CDP_BUILD_TYPE)" name: GetBuildType - - - template: ../steps/build-all-configurations-signed-dlls-step.yml@self - + + # Build our tooling, which is required by the analysis step below, but + # shouldn't be analyzed itself. + - task: MSBuild@1 + displayName: 'Build Tooling' + inputs: + solution: '**/build.proj' + configuration: $(Configuration) + msbuildArguments: -t:BuildTools + + # Perform analysis before building, since this step will clobber build output - template: ../steps/code-analyze-step.yml@self + + # Update the root NuGet.config to use the packages/ directory as a source. + # When we build MDS in Package mode, it depends on the Abstractions package, + # which we will build and package into packages/. + - template: ../steps/update-nuget-config-local-feed-step.yml parameters: - analyzeType: all + packagePath: $(REPOROOT)/packages + # Build the Abstractions package. + - task: MSBuild@1 + displayName: Build Abstractions + inputs: + solution: '**/build.proj' + configuration: $(Configuration) + msbuildArguments: -t:BuildAbstractions + + # Build MDS in Package mode, producing signed DLLs. + - template: ../steps/build-all-configurations-signed-dlls-step.yml@self + parameters: + # These variables are sourced from common-variables.yml. + abstractionsAssemblyFileVersion: $(abstractionsAssemblyFileVersion) + abstractionsPackageVersion: $(abstractionsPackageVersion) + configuration: $(Configuration) + mdsAssemblyFileVersion: $(mdsAssemblyFileVersion) + mdsPackageVersion: $(mdsPackageVersion) + referenceType: Package + - template: ../steps/esrp-code-signing-step.yml@self parameters: artifactType: dll - template: ../steps/generate-nuget-package-step.yml@self parameters: - OutputDirectory: $(artifactDirectory) + nuspecPath: $(nuspecPath) + packageVersion: $(mdsPackageVersion) + outputDirectory: $(artifactDirectory) + displayName: 'Create MDS NuGet Package' + referenceType: Package properties: 'AbstractionsPackageVersion=$(abstractionsPackageVersion)' - template: ../steps/esrp-code-signing-step.yml@self @@ -65,4 +103,4 @@ jobs: - template: ../steps/publish-symbols-step.yml@self parameters: publishSymbols: ${{ parameters['PublishSymbols'] }} - symbolsArtifactName: mds_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_$(NuGetPackageVersion)_$(System.TimelineId) + symbolsArtifactName: mds_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_$(mdsPackageVersion)_$(System.TimelineId) diff --git a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml index 5642d5be47..dc68462f1a 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -4,6 +4,17 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: + + - name: 'debug' + type: boolean + default: false + + - name: referenceType + type: string + values: + - Package + - Project + - name: poolName type: string default: $(ci_var_defaultPoolName) @@ -12,9 +23,13 @@ parameters: type: string default: ADO-MMS22-SQL19 - - name: artifactName + - name: abstractionsArtifactName type: string - default: Artifacts + default: Abstractions.Artifact + + - name: mdsArtifactName + type: string + default: MDS.Artifact - name: platform type: string @@ -28,8 +43,15 @@ parameters: type: stepList default: [] + - name: abstractionsPackageVersion + type: string + + - name: mdsPackageVersion + type: string + jobs: -- job: build_nugets +- job: build_mds_akv_packages_job + displayName: Build MDS & AKV Packages pool: name: ${{parameters.poolName }} @@ -44,36 +66,65 @@ jobs: - ${{ if ne(parameters.prebuildSteps, '') }}: - ${{ parameters.prebuildSteps }} # extra steps to run before the build like downloading sni and the required configuration + # If we're testing in Package mode, download the Abstractions package artifacts into packages/, + # and then setup the top-level NuGet.config to look in packages/ for local + # NuGet dependencies. + - ${{ if eq(parameters.referenceType, 'Package') }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Abstractions Package Artifact + inputs: + artifactName: ${{ parameters.abstractionsArtifactName }} + targetPath: $(packagePath) + # Note that packages/ will have been created by the above step, which is a + # pre-requisite for configuring NuGet. + - template: ../steps/ci-prebuild-step.yml@self + parameters: + debug: ${{ parameters.debug }} + referenceType: ${{ parameters.referenceType }} + - template: ../steps/ci-project-build-step.yml@self parameters: platform: ${{ parameters.platform }} configuration: ${{ parameters.configuration }} + referenceType: ${{ parameters.referenceType }} operatingSystem: Windows - build: all + build: MDS + abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} - template: ../steps/generate-nuget-package-step.yml@self parameters: - NugetPackageVersion: $(NugetPackageVersion) + packageVersion: ${{ parameters.mdsPackageVersion }} configuration: $(Configuration) nuspecPath: 'tools/specs/Microsoft.Data.SqlClient.nuspec' - OutputDirectory: $(packagePath) + outputDirectory: $(packagePath) generateSymbolsPackage: false - properties: 'AbstractionsPackageVersion=$(abstractionsPackageVersion)' - displayName: 'Generate NuGet package M.D.SqlClient' + referenceType: ${{ parameters.referenceType }} + properties: 'AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' + displayName: 'Create MDS NuGet Package' + + - template: ../steps/ci-project-build-step.yml@self + parameters: + platform: ${{ parameters.platform }} + configuration: ${{ parameters.configuration }} + referenceType: ${{ parameters.referenceType }} + operatingSystem: Windows + build: AKV + mdsPackageVersion: ${{parameters.mdsPackageVersion}} - template: ../steps/generate-nuget-package-step.yml@self parameters: - NugetPackageVersion: $(NugetPackageVersion) + packageVersion: $(akvPackageVersion) configuration: $(Configuration) nuspecPath: 'tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec' - OutputDirectory: $(packagePath) + outputDirectory: $(packagePath) generateSymbolsPackage: false - properties: 'MdsPackageVersion=$(mdsPackageVersion)' + referenceType: ${{ parameters.referenceType }} + properties: 'MdsPackageVersion=${{ parameters.mdsPackageVersion }}' installNuget: false - displayName: 'Generate NuGet package AKV Provider' + displayName: 'Create AKV NuGet Package' - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: Artifacts' + - task: PublishPipelineArtifact@1 + displayName: 'Publish Pipeline Artifact' inputs: - PathtoPublish: $(packagePath) - ArtifactName: ${{ parameters.artifactName }} + targetPath: $(packagePath) + artifactName: ${{ parameters.mdsArtifactName }} diff --git a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml index dbf5b10028..06bc31911b 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -4,12 +4,31 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: + - name: abstractionsArtifactName + type: string + + - name: abstractionsPackageVersion + type: string + + - name: configProperties + type: object + default: {} # - key: 'value' + + - name: configSqlFor + type: string # local, azure, or enclave + default: local + - name: debug type: boolean default: false - - name: poolName - type: string + - name: enableX64Test + type: boolean + default: true + + - name: enableX86Test + type: boolean + default: false - name: hostedPool type: boolean @@ -21,63 +40,51 @@ parameters: - name: jobDisplayName type: string - - name: usemanagedSNI - type: boolean - default: false - - - name: configProperties - type: object - default: {} # - key: 'value' - - - name: prebuildSteps - type: stepList - default: [] - - - name: artifactName + - name: mdsArtifactName type: string - default: Artifacts - - name: targetFramework + - name: mdsPackageVersion type: string - name: netcoreVersionTestUtils type: string + + - name: operatingSystem + type: string + default: '' - - name: enableX86Test - type: boolean - default: false - - - name: enableX64Test - type: boolean - default: true - - - name: testSet + - name: poolName type: string + + - name: prebuildSteps + type: stepList + default: [] - name: publishTestResults type: boolean default: false - - - name: configSqlFor - type: string # local, azure, or enclave - default: local - - - name: operatingSystem + + - name: referenceType type: string - default: '' - - - name: buildType - displayName: 'Build Type' - default: Project values: - Project - Package + - name: targetFramework + type: string + + - name: testSet + type: string + # The timeout, in minutes, for this job. - name: timeout type: string default: 90 + - name: usemanagedSNI + type: boolean + default: false + jobs: - job: ${{ format('{0}', coalesce(parameters.jobDisplayName, parameters.image, 'unknown_image')) }} @@ -98,6 +105,22 @@ jobs: value: '$(dotnetx86Path)' steps: + + # If we're testing in Package mode, download the Abstractions and MDS package + # artifacts and put them in the packages/ directory in the repo root. + - ${{ if eq(parameters.referenceType, 'Package') }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Abstractions Package Artifact + inputs: + artifactName: ${{ parameters.abstractionsArtifactName }} + targetPath: $(Build.SourcesDirectory)/packages + + - task: DownloadPipelineArtifact@2 + displayName: Download MDS Package Artifact + inputs: + artifactName: ${{ parameters.mdsArtifactName }} + targetPath: $(Build.SourcesDirectory)/packages + - ${{ if ne(parameters.prebuildSteps, '') }}: - ${{ parameters.prebuildSteps }} # extra steps to run before the build like downloading sni and the required configuration @@ -227,8 +250,10 @@ jobs: - template: ../steps/build-all-tests-step.yml@self # build tests parameters: targetFramework: ${{ parameters.targetFramework }} - referenceType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} ${{ if ne(parameters.operatingSystem, 'Windows') }}: OSGroup: Unix @@ -237,7 +262,7 @@ jobs: parameters: debug: ${{ parameters.debug }} targetFramework: ${{ parameters.targetFramework }} - referenceType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} operatingSystem: ${{ parameters.operatingSystem }} @@ -279,13 +304,13 @@ jobs: parameters: debug: ${{ parameters.debug }} targetFramework: ${{ parameters.targetFramework }} - referenceType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} msbuildArchitecture: x86 dotnetx86RootPath: $(dotnetx86RootPath) operatingSystem: ${{ parameters.operatingSystem }} - - ${{ if and(eq(parameters.publishTestResults, true), eq(parameters.buildType, 'Project')) }}: # publish test results if build type is project + - ${{ if and(eq(parameters.publishTestResults, true), eq(parameters.referenceType, 'Project')) }}: # publish test results if build type is project - template: ../steps/publish-test-results-step.yml@self parameters: debug: ${{ parameters.debug }} diff --git a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml index 14aea42411..caa677aa89 100644 --- a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml +++ b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml @@ -51,9 +51,7 @@ jobs: - template: ../steps/update-nuget-config-local-feed-step.yml parameters: - downloadedNugetPath: $(Pipeline.Workspace)\${{parameters.packageFolderName }} - ${{ if parameters.isPreview }}: - nugetPackageVersion: $(PreviewNugetPackageVersion) + packagePath: $(Pipeline.Workspace)\${{parameters.packageFolderName }} - template: ../steps/update-config-file-step.yml parameters: @@ -68,11 +66,11 @@ jobs: parameters: referenceType: Package ${{ if parameters.isPreview }}: - nugetPackageVersion: $(PreviewNugetPackageVersion) + mdsPackageVersion: $(previewMdsPackageVersion) - template: ../steps/build-and-run-tests-netcore-step.yml parameters: referenceType: Package cleanFirst: true ${{ if parameters.isPreview }}: - nugetPackageVersion: $(PreviewNugetPackageVersion) + mdsPackageVersion: $(previewMdsPackageVersion) diff --git a/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml index 009e6f2647..42000da697 100644 --- a/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml @@ -25,14 +25,6 @@ parameters: - pdb - both - - name: assembly_file_version_netfx - type: string - default: $(AssemblyFileVersion) - - - name: assembly_file_version_core - type: string - default: $(AssemblyFileVersion) - - name: isPreview type: boolean @@ -53,19 +45,14 @@ jobs: - name: pathToDownloadedNuget # path to the downloaded nuget files value: $(Pipeline.Workspace)\${{parameters.packageFolderName }} - - name: ProductVersion #MDS product version (MDS validation) - value: $(NugetPackageVersion) - - name: BuildType value: $[ stageDependencies.buildMDS.build_signed_package.outputs['GetBuildType.CDP_BUILD_TYPE_COPY'] ] - ${{ if parameters.isPreview }}: - name: extractedNugetPath - value: $(extractedNugetRootPath).$(PreviewNugetPackageVersion) - - name: NugetPackageVersion - value: $(PreviewNugetPackageVersion) - - name: ProductVersion - value: $(PreviewNugetPackageVersion) + value: $(extractedNugetRootPath).$(previewMdsPackageVersion) + - name: mdsPackageVersion + value: $(previewMdsPackageVersion) steps: - script: SET @@ -75,7 +62,7 @@ jobs: displayName: 'Use NuGet' - powershell: | - #Sets Variables for AssemblyFileVersion, AssemblyVersion and NugetPackageVersion + # Sets the pipeline ASSEMBLY_VERSION variable. [Xml] $versionprops = Get-Content -Path ".\tools\props\Versions.props" Write-Host $versionprops.Project.PropertyGroup[0].AssemblyFileVersion @@ -283,60 +270,47 @@ jobs: displayName: 'Verify all dlls status are Valid' - powershell: | - # This will check for ProductVersion and FileVersion. - # - # For NetFx we have a different FileVersion, but product versions are all - # the same. Some may have extra numbering at the end. We only check for - # the first parts. + # This will check each DLL's ProductVersion and FileVersion against + # expected values. + $failed = 0 foreach ( $pVersion in Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo ) { - if ($pVersion.ProductVersion -Like '$(ProductVersion)*') + if ($pVersion.ProductVersion -Like '$(mdsPackageVersion)*') { - Write-Host Valid Product Version:"$pVersion.ProductVersion" $pVersion.ProductVersion detected for $pVersion.FileName -ForegroundColor Green + Write-Host -ForegroundColor Green "Correct ProductVersion detected for $($pVersion.FileName): $($pVersion.ProductVersion)" } else { - Write-Host "Wrong ProductVersion detected. Expected: '$(ProductVersion)', but Detected: "$pVersion.ProductVersion"" - Exit -1 + Write-Host -ForegroundColor Red "Wrong ProductVersion detected for $($pVersion.FileName); expected: $(mdsPackageVersion); found: $($pVersion.ProductVersion)" + $failed = 1 } - if($pVersion.FileName -like '*lib\$(CurrentNetFxVersion)*'){ - - if($pVersion.FileVersion -eq '${{parameters.assembly_file_version_netfx }}') - { - Write-Host 'Correct File version Detected for net46,' $pVersion.FileVersion -ForegroundColor Green - } - else - { - Write-Host 'Wrong File version Detected for net46,' $pVersion.FileVersion -ForegroundColor Red - Exit -1 - } + if ($pVersion.FileVersion -eq '$(mdsAssemblyFileVersion)') + { + Write-Host -ForegroundColor Green "Correct FileVersion detected for $($pVersion.FileName): $($pVersion.FileVersion)" } else { - - if($pVersion.FileVersion -eq '${{parameters.assembly_file_version_core}}') - { - Write-Host 'Correct File version Detected for netcore,' $pVersion.FileVersion -ForegroundColor Green - } - else - { - Write-Host 'Wrong File version Detected for netcore and ref folder,' $pVersion.FileVersion -ForegroundColor Red - Exit -1 - } + Write-Host -ForegroundColor Red "Wrong FileVersion detected for $($pVersion.FileName); expected $(mdsAssemblyFileVersion); found: $($pVersion.FileVersion)" + $failed = 1 } } - Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo - displayName: 'Verify "File Version" matches provided pipeline variable "ASSEMBLY_FILE_VERSION" for DLLs' + if ($failed -ne 0) + { + Exit -1 + } + + Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object VersionInfo | Format-List + displayName: 'Verify "File Version" matches expected values for DLLs' - powershell: | # Change TestMicrosoftDataSqlClientVersion [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" $versionpropspath = "tools\props\Versions.props" - $versionprops.Project.PropertyGroup[$versionprops.Project.PropertyGroup.Count-1].TestMicrosoftDataSqlClientVersion ="$(NugetPackageVersion)" + $versionprops.Project.PropertyGroup[$versionprops.Project.PropertyGroup.Count-1].TestMicrosoftDataSqlClientVersion ="$(mdsPackageVersion)" Write-Host "Saving Test nuget version at $rootfolder\props ...." -ForegroundColor Green $versionprops.Save($versionpropspath) @@ -344,10 +318,16 @@ jobs: - powershell: | # Check assembly versions. + # + # GOTCHA: This expects the Versions.props file having XML elements in a + # certain order. If the order changes, this check will fail! + # + # TODO: This also isn't checking the versions of the actual assemblies in + # the package, so it isn't terribly useful. [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" - $AssemblyFileVersion = $versionprops.Project.PropertyGroup[0].AssemblyFileVersion - $AssemblyVersion = $versionprops.Project.PropertyGroup[0].AssemblyVersion + $AssemblyFileVersion = $versionprops.Project.PropertyGroup[2].AssemblyFileVersion + $AssemblyVersion = $versionprops.Project.PropertyGroup[2].AssemblyVersion if($AssemblyFileVersion -eq $AssemblyVersion) { diff --git a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml index e07685407f..46318f8e06 100644 --- a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml +++ b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml @@ -4,31 +4,43 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: + - name: abstractionsArtifactName + type: string + + - name: abstractionsPackageVersion + type: string + - name: debug type: boolean default: false - - name: testConfigurations + - name: dependsOn type: object + default: [] - - name: dependsOn + - name: mdsArtifactName + type: string + default: MDS.Artifact + + - name: mdsPackageVersion type: string - default: '' + + - name: postTestJobs + type: jobList + default: [] - - name: buildType - displayName: 'Build Type' + - name: prebuildSteps + type: stepList + default: [] + + - name: referenceType default: Project values: - Project - Package - - name: prebuildSteps - type: stepList - default: [] - - - name: postTestJobs - type: jobList - default: [] + - name: testConfigurations + type: object # The timeout, in minutes, for each test job. - name: testsTimeout @@ -39,10 +51,7 @@ stages: - ${{ each config in parameters.testConfigurations }}: - ${{ each image in config.value.images }}: - stage: ${{ image.key }} - ${{ if ne(parameters.dependsOn, '') }}: - dependsOn: ${{ parameters.dependsOn }} - ${{ else }}: - dependsOn: [] + dependsOn: ${{ parameters.dependsOn }} jobs: - ${{ each targetFramework in config.value.TargetFrameworks }}: - ${{ each platform in config.value.buildPlatforms }}: @@ -51,13 +60,17 @@ stages: - template: ../jobs/ci-run-tests-job.yml@self parameters: debug: ${{ parameters.debug }} - buildType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} timeout: ${{ parameters.testsTimeout }} poolName: ${{ config.value.pool }} hostedPool: ${{ eq(config.value.hostedPool, true) }} image: ${{ image.value }} jobDisplayName: ${{ format('{0}_{1}_{2}', replace(targetFramework, '.', '_'), platform, testSet) }} configProperties: ${{ config.value.configProperties }} + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} + abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} + mdsArtifactName: ${{ parameters.mdsArtifactName }} + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} targetFramework: ${{ targetFramework }} netcoreVersionTestUtils: ${{config.value.netcoreVersionTestUtils }} @@ -77,7 +90,7 @@ stages: - template: ../jobs/ci-run-tests-job.yml@self parameters: debug: ${{ parameters.debug }} - buildType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} timeout: ${{ parameters.testsTimeout }} poolName: ${{ config.value.pool }} hostedPool: ${{ eq(config.value.hostedPool, true) }} @@ -88,6 +101,10 @@ stages: jobDisplayName: ${{ format('{0}_{1}_{2}_{3}', replace(targetFramework, '.', '_'), platform, 'NativeSNI', testSet) }} configProperties: ${{ config.value.configProperties }} useManagedSNI: ${{ useManagedSNI }} + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} + abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} + mdsArtifactName: ${{ parameters.mdsArtifactName }} + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} targetFramework: ${{ targetFramework }} netcoreVersionTestUtils: ${{config.value.netcoreVersionTestUtils }} diff --git a/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml b/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml index e6a6c0443a..0cfbb3494b 100644 --- a/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml +++ b/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml @@ -4,23 +4,27 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - - name: AssemblyFileVersion + + - name: abstractionsAssemblyFileVersion + type: string + + - name: abstractionsPackageVersion type: string - default: $(AssemblyFileVersion) - - name: Configuration + - name: configuration type: string - default: '$(Configuration)' - - name: packageRefMdsVersion + - name: mdsAssemblyFileVersion type: string - default: '' - - name: product - default: MDS + - name: mdsPackageVersion + type: string + + - name: referenceType + type: string values: - - MDS - - MSS + - Package + - Project steps: - task: DownloadSecureFile@1 @@ -41,10 +45,9 @@ steps: packageType: runtime version: '8.x' -- ${{ if eq(parameters.product, 'MDS') }}: - - task: MSBuild@1 - displayName: 'BuildAllConfigurations using build.proj' - inputs: - solution: '**/build.proj' - configuration: '${{parameters.Configuration }}' - msbuildArguments: '-p:AssemblyFileVersion=${{parameters.AssemblyFileVersion }} -t:BuildAllConfigurations -p:GenerateNuget=false -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' +- task: MSBuild@1 + displayName: 'BuildAllConfigurations using build.proj' + inputs: + solution: '**/build.proj' + configuration: '${{ parameters.configuration }}' + msbuildArguments: '-t:BuildAllConfigurations -p:ReferenceType=${{ parameters.referenceType }} -p:GenerateNuget=false -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:AssemblyFileVersion=${{ parameters.mdsAssemblyFileVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:AbstractionsAssemblyFileVersion=${{ parameters.abstractionsAssemblyFileVersion }}' diff --git a/eng/pipelines/common/templates/steps/build-all-tests-step.yml b/eng/pipelines/common/templates/steps/build-all-tests-step.yml index 826be1df8b..43ecb6aafa 100644 --- a/eng/pipelines/common/templates/steps/build-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/build-all-tests-step.yml @@ -4,31 +4,33 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - - name: targetFramework + - name: abstractionsPackageVersion + type: string + + - name: configuration + type: string + default: '$(Configuration)' + + - name: mdsPackageVersion type: string - - name: nugetPackageVersion + - name: osGroup type: string - default: $(NugetPackageVersion) + default: '' - name: platform type: string default: $(Platform) - - - name: configuration - type: string - default: '$(Configuration)' - name: referenceType - default: Package + type: string values: - Project - Package - - - name: OSGroup - type: string - default: '' + - name: targetFramework + type: string + - name: testSet type: string @@ -40,26 +42,16 @@ steps: solution: build.proj platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' - msbuildArguments: '-t:BuildTestsNetFx -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' - -# - ${{else if contains(parameters.targetFramework, 'netstandard')}}: # .NET Standard -# - task: MSBuild@1 -# displayName: 'Build Tests NetStandard' -# inputs: -# solution: build.proj -# platform: '${{parameters.platform }}' -# configuration: '${{parameters.configuration }}' -# msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=NetStandard -p:TargetNetStandardVersion=${{parameters.targetNetStandardVersion }} -p:TF=${{parameters.targetFramework }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' -# condition: and(succeeded(), not(startsWith(variables['TF'], 'net4')), startsWith(variables['TargetNetStandardVersion'], 'netstandard')) + msbuildArguments: '-t:BuildTestsNetFx -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' -- ${{elseif eq(parameters.OSGroup, '')}}: # .NET on Windows +- ${{elseif eq(parameters.osGroup, '')}}: # .NET on Windows - task: MSBuild@1 displayName: 'Build Tests NetCore [Win]' inputs: solution: build.proj platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' - msbuildArguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' + msbuildArguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) - ${{ else }}: # .NET on Unix @@ -69,7 +61,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:OSGroup=${{parameters.OSGroup }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:OSGroup=${{parameters.osGroup }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' verbosityRestore: Detailed verbosityPack: Detailed condition: and(succeeded(), ne(variables['Agent.OS'], 'Windows_NT')) diff --git a/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml b/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml index c70fe776a7..43eea4449d 100644 --- a/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml +++ b/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml @@ -18,9 +18,9 @@ parameters: - Project - Package - - name: NugetPackageVersion + - name: mdsPackageVersion type: string - default: $(NugetPackageVersion) + default: $(mdsPackageVersion) - name: platform type: string @@ -55,14 +55,14 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }}' + msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' - task: MSBuild@1 displayName: 'MSBuild Build Tests for ${{parameters.TargetNetCoreVersion }}' inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} -p:Configuration=${{parameters.configuration }}' + msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:Configuration=${{parameters.configuration }}' # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -71,12 +71,12 @@ steps: inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.FunctionalTests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests"' - task: DotNetCoreCLI@2 displayName: 'Run Manual Tests for ${{parameters.TargetNetCoreVersion }}' inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests --collect "Code Coverage"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests --collect "Code Coverage"' retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} diff --git a/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml b/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml index 5d9f194c48..e7b35f653e 100644 --- a/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml +++ b/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml @@ -18,9 +18,9 @@ parameters: - Project - Package - - name: NugetPackageVersion + - name: mdsPackageVersion type: string - default: $(NugetPackageVersion) + default: $(mdsPackageVersion) - name: platform type: string @@ -55,13 +55,13 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }}' + msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' - task: MSBuild@1 displayName: 'MSBuild Build Tests for ${{parameters.TargetNetFxVersion }}' inputs: solution: build.proj - msbuildArguments: ' -t:BuildTestsNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:Configuration=${{parameters.configuration }} -p:Platform=${{parameters.platform }}' + msbuildArguments: ' -t:BuildTestsNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:Configuration=${{parameters.configuration }} -p:Platform=${{parameters.platform }}' # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -70,12 +70,12 @@ steps: inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.FunctionalTests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' - task: DotNetCoreCLI@2 displayName: 'Run Manual Tests for ${{parameters.TargetNetFxVersion }}' inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} diff --git a/eng/pipelines/common/templates/steps/ci-prebuild-step.yml b/eng/pipelines/common/templates/steps/ci-prebuild-step.yml index efe17856d8..5f9adface7 100644 --- a/eng/pipelines/common/templates/steps/ci-prebuild-step.yml +++ b/eng/pipelines/common/templates/steps/ci-prebuild-step.yml @@ -8,13 +8,8 @@ parameters: type: boolean default: false - - name: artifactName + - name: referenceType type: string - default: Artifacts - - - name: buildType - displayName: 'Build Type' - default: Project values: - Project - Package @@ -37,21 +32,8 @@ steps: Get-ChildItem env: | Sort-Object Name displayName: 'List Environment Variables [debug]' -- ${{if eq(parameters.buildType, 'Package')}}: - - task: DownloadPipelineArtifact@2 - displayName: 'Download NuGet Package' - inputs: - buildType: current - artifact: ${{parameters.artifactName }} - patterns: '**/*.nupkg' - targetPath: $(Pipeline.Workspace)/${{parameters.artifactName }} - +- ${{if eq(parameters.referenceType, 'Package')}}: - template: update-nuget-config-local-feed-step.yml@self parameters: - downloadedNugetPath: $(Pipeline.Workspace)\${{parameters.artifactName }} debug: ${{ parameters.debug }} - -- ${{ else }}: # project - - template: ci-project-build-step.yml@self - parameters: - build: allNoDocs + packagePath: $(Build.SourcesDirectory)/packages diff --git a/eng/pipelines/common/templates/steps/ci-project-build-step.yml b/eng/pipelines/common/templates/steps/ci-project-build-step.yml index e938c909fd..f0dbbf2080 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -12,6 +12,12 @@ parameters: type: string default: $(Configuration) + - name: referenceType + type: string + values: + - Package + - Project + - name: buildNumber type: string default: $(BuildNumber) @@ -34,38 +40,43 @@ parameters: - all - allNoDocs + # Used when MDS is built with ReferenceType = Package. + - name: abstractionsPackageVersion + type: string + default: '' + + # Used when AKV is built with ReferenceType = Package. + - name: mdsPackageVersion + type: string + default: '' + steps: - template: ./ensure-dotnet-version.yml@self parameters: packageType: 'sdk' version: '9.0' -- template: ./ensure-dotnet-version.yml@self - parameters: - packageType: 'runtime' - version: '8.0' - - ${{ if or(eq(parameters.operatingSystem, 'Windows'), eq(parameters.operatingSystem, 'deferedToRuntime')) }}: - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: - task: MSBuild@1 - displayName: 'Restore nugets [Win]' + displayName: 'Restore [Win]' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-t:restore' + msbuildArguments: '-t:restore -p:ReferenceType=${{ parameters.ReferenceType }} -p:AbstractionsPackageVersion=${{parameters.abstractionsPackageVersion}}' retryCountOnTaskFailure: 1 - ${{ if eq(parameters.build, 'allNoDocs') }}: - task: MSBuild@1 - displayName: 'Build Driver [Win]' + displayName: 'Build Driver (no docs) [Win]' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) inputs: solution: build.proj msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.configuration }}' - msbuildArguments: '-t:BuildAllConfigurations -p:GenerateDocumentationFile=false -p:GenerateNuGet=false -p:BuildNumber=${{ parameters.buildNumber }}' + msbuildArguments: '-t:BuildAllConfigurations -p:ReferenceType=${{ parameters.ReferenceType }} -p:GenerateDocumentationFile=false -p:GenerateNuGet=false -p:BuildNumber=${{ parameters.buildNumber }} -p:AbstractionsPackageVersion=${{parameters.abstractionsPackageVersion}}' clean: true - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all')) }}: @@ -77,7 +88,7 @@ steps: msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.configuration }}' - msbuildArguments: '-t:BuildAllConfigurations -p:GenerateNuGet=false -p:BuildNumber=${{ parameters.buildNumber }}' + msbuildArguments: '-t:BuildAllConfigurations -p:ReferenceType=${{ parameters.ReferenceType }} -p:GenerateNuGet=false -p:BuildNumber=${{ parameters.buildNumber }} -p:AbstractionsPackageVersion=${{parameters.abstractionsPackageVersion}}' clean: true - ${{ if or(eq(parameters.build, 'AKV'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: @@ -89,7 +100,7 @@ steps: msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.configuration }}' - msbuildArguments: '-t:BuildAKVNetFx -p:BuildNumber=${{ parameters.buildNumber }}' + msbuildArguments: '-t:BuildAKVNetFx -p:ReferenceType=${{ parameters.ReferenceType }} -p:BuildNumber=${{ parameters.buildNumber }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion}}' - task: MSBuild@1 displayName: 'Build AKV Provider NetCore All OS [Win]' @@ -99,17 +110,17 @@ steps: msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.configuration }}' - msbuildArguments: '-t:BuildAKVNetCoreAllOS -p:BuildNumber=${{ parameters.buildNumber }}' + msbuildArguments: '-t:BuildAKVNetCoreAllOS -p:ReferenceType=${{ parameters.ReferenceType }} -p:BuildNumber=${{ parameters.buildNumber }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion}}' - ${{ if or(eq(parameters.operatingSystem, 'Linux'), eq(parameters.operatingSystem, 'MacOS'), eq(parameters.operatingSystem, 'deferedToRuntime')) }}: - task: DotNetCoreCLI@2 - displayName: 'Build Driver [non-Win]' + displayName: 'Build Driver [${{ parameters.operatingSystem }}]' condition: and(succeeded(), ne(variables['Agent.OS'], 'Windows_NT')) inputs: command: custom projects: build.proj custom: msbuild - arguments: '-t:BuildAll -p:TestEnabled=true -p:GenerateDocumentationFile=false -p:configuration=${{ parameters.configuration }}' + arguments: '-t:BuildAll -p:ReferenceType=${{ parameters.ReferenceType }} -p:TestEnabled=true -p:GenerateDocumentationFile=false -p:configuration=${{ parameters.configuration }} -p:AbstractionsPackageVersion=${{parameters.abstractionsPackageVersion}}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 diff --git a/eng/pipelines/common/templates/steps/code-analyze-step.yml b/eng/pipelines/common/templates/steps/code-analyze-step.yml index 9807541a70..bad64f55d3 100644 --- a/eng/pipelines/common/templates/steps/code-analyze-step.yml +++ b/eng/pipelines/common/templates/steps/code-analyze-step.yml @@ -3,40 +3,30 @@ # 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: analyzeType - values: - - roslyn - - inspect - - all +# This template defines a step to run Roslyn Analyzers on the MDS project build. +# It uses the RoslynAnalyzers@3 task from the Secure Development Team's SDL +# extension: +# +# https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-mohanb/security-integration/guardian-wiki/sdl-azdo-extension/roslyn-analyzers-build-task +# +# GOTCHA: This step will clobber any existing build output. It should be run +# _before_ any build steps that perform versioning or signing. + +# @TODO: This can probably be made generic and pass in the command lines for msbuild +# BUT, they should be kept separate by now as we rebuild build.proj in parallel, we won't +# affect >1 project at a time. + +parameters: - name: sourceRoot type: string default: $(REPOROOT) - - name: packageRefMdsVersion - type: string - default: '' - - - name: product - default: MDS - values: - - MDS - - MSS - steps: -- ${{ if or(eq(parameters.analyzeType, 'roslyn'), eq(parameters.analyzeType, 'all')) }}: - - ${{ if eq(parameters.product, 'MDS') }}: - - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 - displayName: 'Guardian Dotnet Analyzers ' - inputs: - msBuildVersion: 17.0 - msBuildArchitecture: x64 - setupCommandlinePicker: vs2022 - msBuildCommandline: 'msbuild ${{parameters.sourceRoot}}\build.proj -p:configuration=Release -p:GenerateNuget=false -p:BuildTools=false -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' - -- ${{ if or(eq(parameters.analyzeType, 'inspect'), eq(parameters.analyzeType, 'all')) }}: - - task: securedevelopmentteam.vss-secure-development-tools.build-task-codeinspector.CodeInspector@2 - displayName: 'Run Code Inspector' + - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 + displayName: Roslyn Analyzers inputs: - LogLevel: Error + msBuildVersion: 17.0 + msBuildArchitecture: x64 + setupCommandlinePicker: vs2022 + msBuildCommandline: 'msbuild ${{parameters.sourceRoot}}\build.proj -p:configuration=Release -p:GenerateNuget=false -p:BuildTools=false -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' diff --git a/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml b/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml index 098286866c..23347ac364 100644 --- a/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml +++ b/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml @@ -83,6 +83,6 @@ steps: $software = '${{parameters.softwareFolder}}' $symbols = '${{parameters.symbolsFolder}}' - Get-ChildItem -recurse "$software\*.dll" - Get-ChildItem -recurse "$symbols\*.pdb" + Get-ChildItem -recurse "$software\*.dll" | ForEach-Object VersionInfo | Format-List + Get-ChildItem -recurse "$symbols\*.pdb" | ForEach-Object VersionInfo | Format-List displayName: 'List the prepared files' diff --git a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml index 8a8fd00f6f..80a085c502 100644 --- a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml +++ b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml @@ -6,17 +6,15 @@ parameters: - name: nuspecPath type: string - default: '$(nuspecPath)' - - name: NugetPackageVersion + - name: packageVersion type: string - default: '$(NugetPackageVersion)' - - name: OutputDirectory + - name: outputDirectory type: string default: '$(Build.SourcesDirectory)/packages' - - name: Configuration + - name: configuration type: string default: '$(Configuration)' @@ -26,12 +24,17 @@ parameters: - name: displayName type: string - default: 'NuGet pack with snupkg' - name: installNuget type: boolean default: true + - name: referenceType + type: string + values: + - Package + - Project + # Semi-colon separated properties to pass to nuget via the -properties # argument. - name: properties @@ -55,6 +58,6 @@ steps: inputs: command: custom ${{ if parameters.generateSymbolsPackage }}: - arguments: 'pack -Symbols -SymbolPackageFormat snupkg ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};${{parameters.properties}}"' + arguments: 'pack -Symbols -SymbolPackageFormat snupkg ${{parameters.nuspecPath}} -Version ${{parameters.packageVersion}} -OutputDirectory ${{parameters.outputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};ReferenceType=${{ parameters.referenceType }};${{parameters.properties}}"' ${{else }}: - arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};${{parameters.properties}}"' + arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.packageVersion}} -OutputDirectory ${{parameters.outputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.Configuration}};ReferenceType=${{ parameters.referenceType }};${{parameters.properties}}"' diff --git a/eng/pipelines/common/templates/steps/publish-symbols-step.yml b/eng/pipelines/common/templates/steps/publish-symbols-step.yml index 5f8d2e6a7d..8999a6cc1d 100644 --- a/eng/pipelines/common/templates/steps/publish-symbols-step.yml +++ b/eng/pipelines/common/templates/steps/publish-symbols-step.yml @@ -15,7 +15,7 @@ parameters: - name: symbolsVersion type: string - default: '$(NuGetPackageVersion)' + default: '$(mdsPackageVersion)' - name: symbolServer type: string diff --git a/eng/pipelines/common/templates/steps/run-all-tests-step.yml b/eng/pipelines/common/templates/steps/run-all-tests-step.yml index b9a870ada4..27da9e4e73 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -11,9 +11,9 @@ parameters: - name: targetFramework type: string - - name: nugetPackageVersion + - name: mdsPackageVersion type: string - default: $(NugetPackageVersion) + default: $(mdsPackageVersion) - name: platform type: string @@ -62,9 +62,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' + msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' ${{ else }}: # x86 - msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' + msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) retryCountOnTaskFailure: 1 @@ -76,9 +76,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' + msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' ${{ else }}: # x86 - msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' + msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) retryCountOnTaskFailure: 1 @@ -90,9 +90,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' + msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' ${{ else }}: # x86 - msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' + msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' condition: eq(variables['Agent.OS'], 'Windows_NT') retryCountOnTaskFailure: 2 @@ -104,7 +104,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 @@ -116,7 +116,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 @@ -128,7 +128,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 2 diff --git a/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml b/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml index 4eac341108..ac2c517aee 100644 --- a/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml +++ b/eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml @@ -8,76 +8,56 @@ parameters: type: boolean default: false - - name: downloadedNugetPath # path to the downloaded nuget files + # The path to add as a NuGet source. + - name: packagePath type: string - - name: nugetPackageVersion - type: string - default: $(NugetPackageVersion) - steps: -- powershell: | +- pwsh: | + # Resolve the path to an absolute path. + # + # Resolve-Path throws an error if the path doesn't exist yet, which is an + # expected scenario here, so we must dance around it. + $PackagePath = Resolve-Path "${{ parameters.packagePath }}" -ErrorAction SilentlyContinue -ErrorVariable resolveError + if (-Not ($PackagePath)) + { + $PackagePath = $resolveError[0].TargetObject + } + + # Ensure the package path exists, for example if we are going to generate + # packages in a later step. + if (-Not (Test-Path -Path "$PackagePath")) + { + New-Item -ItemType Directory -Path "$PackagePath" -Force + Write-Host "Created package path: $PackagePath" + } + + Write-Host "Adding package path: $PackagePath" + # Get a list of package sources available Get-PackageSource - #Current location + # Current location Get-Location # Register the local nuget folder to be used by nuget.config - Register-PackageSource -Name "Package Source" -Location ${{parameters.downloadedNugetPath }} -Force -ProviderName NuGet -Trusted + Register-PackageSource -Name "Pipeline Source" -Location "$PackagePath" -Force -ProviderName NuGet -Trusted # Get a list of package sources available after the change Get-PackageSource - #Set the NuGet.config file in the project to use extracted package + # Set the NuGet.config file in the project to use extracted package $rootFolder = Get-location [Xml] $nugetConfig = Get-Content -Path "NuGet.config" - $Value = Resolve-Path ${{parameters.downloadedNugetPath }} $newAdd = $nugetConfig.CreateElement("add") - $newAdd.SetAttribute("key","Package source") - $newAdd.SetAttribute("value", "$Value/" ) + $newAdd.SetAttribute("key","pipeline_source") + $newAdd.SetAttribute("value", "$PackagePath" ) $nugetConfig.configuration.packageSources.AppendChild($newAdd) $nugetConfig.Save("$rootFolder/NuGet.config") - displayName: 'Update NuGet config file to read from Nuget folder' + displayName: 'Add source to NuGet.config' - ${{ if parameters.debug }}: - - powershell: | + - pwsh: | # Display the content of the NuGet.config file Get-Content -Path "NuGet.config" displayName: 'Read NuGet.config [debug]' - -- task: DotNetCoreCLI@2 - displayName: 'Restore NuGets' - inputs: - command: 'custom' - custom: 'msbuild' - arguments: 'build.proj -t:restore' - feedsToUse: 'select' - -- powershell: | - $Doc = [xml](Get-Content "./Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj") - $parent_xpath = '/Project/ItemGroup/ProjectReference' - $node = $Doc.SelectSingleNode($parent_xpath) - $parentNode = $node.ParentNode - while($node -ne $null) { - $node.ParentNode.RemoveChild($node) - $node = $Doc.SelectSingleNode($parent_xpath) - } - - $parent_xpath = '/Project/ItemGroup/PackageReference[@Include="Microsoft.Data.SqlClient"]' - $node = $Doc.SelectSingleNode($parent_xpath) - - if($node -eq $null){ - $packagerefnode = $doc.createelement("packagereference") - $value = $doc.selectsinglenode('/project/itemgroup/projectreference') - $attrinclude = $doc.createattribute("include") - $attrinclude.value = "microsoft.data.sqlclient" - $packagerefnode.attributes.append($attrinclude) - $parentNode.AppendChild($packageRefNode) - } - - $currentFolder = Get-Location - $filePath = Join-Path $currentFolder "Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj" - $Doc.Save($filePath) - workingDirectory: 'src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider' - displayName: 'Update AKV Project Ref to Package Ref (.NET Framework/Core)' diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 353122828b..86987f63e2 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -58,9 +58,12 @@ parameters: type: object default: [net462, net8.0] -- name: buildType - displayName: 'Build Type' - default: Project +# The way we will reference sibling projects in the .csproj files: +# Project - use references. +# Package - use references to NuGet packages in the +# packages/ directory. +- name: referenceType + displayName: 'Reference Type' values: - Project - Package @@ -89,20 +92,48 @@ parameters: variables: - template: libraries/ci-build-variables.yml@self - - name: artifactName - value: Artifacts + - name: abstractionsArtifactName + value: Abstractions.Artifact + + - name: mdsArtifactName + value: MDS.Artifact - name: defaultHostedPoolName value: 'Azure Pipelines' stages: - - stage: build_nugets - displayName: 'Build NuGet Packages' + + # Build the Abstractions package, and publish it to the pipeline artifacts + # under the given artifact name. + - template: stages/build-abstractions-package-ci-stage.yml@self + parameters: + buildConfiguration: Release + abstractionsPackageVersion: $(abstractionsPackageVersion) + artifactName: $(abstractionsArtifactName) + ${{if eq(parameters.debug, 'true')}}: + verbosity: diagnostic + + # Build MDS and its NuGet packages. + - stage: build_mds_akv_packages_stage + displayName: 'Build MDS & AKV Packages' + + # When building MDS via packages, we must depend on the Abstractions + # package. + ${{ if eq(parameters.referenceType, 'Package') }}: + dependsOn: + - build_abstractions_package_stage + ${{ else }}: + dependsOn: [] + jobs: - template: common/templates/jobs/ci-build-nugets-job.yml@self parameters: + referenceType: ${{ parameters.referenceType }} configuration: ${{ parameters.buildConfiguration }} - artifactName: $(artifactName) + abstractionsPackageVersion: $(abstractionsPackageVersion) + abstractionsArtifactName: $(abstractionsArtifactName) + mdsPackageVersion: $(mdsPackageVersion) + mdsArtifactName: $(mdsArtifactName) ${{if ne(parameters.SNIVersion, '')}}: prebuildSteps: - template: common/templates/steps/override-sni-version.yml@self @@ -110,44 +141,48 @@ stages: SNIVersion: ${{parameters.SNIVersion}} SNIValidationFeed: ${{parameters.SNIValidationFeed}} + # Run the stress tests, if desired. - ${{ if eq(parameters.enableStressTests, true) }}: - template: stages/stress-tests-ci-stage.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} - dependsOn: [build_nugets] + dependsOn: [build_mds_akv_packages_stage] pipelineArtifactName: $(artifactName) - mdsPackageVersion: $(NugetPackageVersion) + mdsPackageVersion: $(mdsPackageVersion) ${{ if eq(parameters.debug, 'true') }}: verbosity: 'detailed' - + + # Run the MDS and AKV tests. - template: common/templates/stages/ci-run-tests-stage.yml@self parameters: debug: ${{ parameters.debug }} - buildType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} testsTimeout: ${{ parameters.testsTimeout }} - ${{ if eq(parameters.buildType, 'Package') }}: - dependsOn: build_nugets - - ${{if ne(parameters.SNIVersion, '')}}: - prebuildSteps: # steps to run prior to building and running tests on each job + abstractionsArtifactName: $(abstractionsArtifactName) + abstractionsPackageVersion: $(abstractionsPackageVersion) + mdsArtifactName: $(mdsArtifactName) + mdsPackageVersion: $(mdsPackageVersion) + + # When testing MDS via packages, we must depend on the Abstractions and + # MDS packages. + ${{ if eq(parameters.referenceType, 'Package') }}: + dependsOn: + - build_abstractions_package_stage + - build_mds_akv_packages_stage + + prebuildSteps: # steps to run prior to building and running tests on each job + - ${{if ne(parameters.SNIVersion, '')}}: - template: common/templates/steps/override-sni-version.yml@self parameters: SNIVersion: ${{parameters.SNIVersion}} SNIValidationFeed: ${{parameters.SNIValidationFeed}} - - template: common/templates/steps/ci-prebuild-step.yml@self - parameters: - debug: ${{ parameters.debug }} - artifactName: $(artifactName) - buildType: ${{ parameters.buildType }} - ${{else}}: - prebuildSteps: # steps to run prior to building and running tests on each job - - template: common/templates/steps/ci-prebuild-step.yml@self - parameters: - debug: ${{ parameters.debug }} - artifactName: $(artifactName) - buildType: ${{ parameters.buildType }} - ${{ if eq(parameters.buildType, 'Project') }}: # only run the code coverage job if the build type is project + - template: common/templates/steps/ci-prebuild-step.yml@self + parameters: + debug: ${{ parameters.debug }} + referenceType: ${{ parameters.referenceType }} + + ${{ if eq(parameters.referenceType, 'Project') }}: # only run the code coverage job if the build type is project postTestJobs: # jobs to run after the tests are done - template: common/templates/jobs/ci-code-coverage-job.yml@self parameters: @@ -354,9 +389,8 @@ stages: # ways to detect if they were present) won't run consistently across forks and non-forks. ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) - AADServicePrincipalId: $(AADServicePrincipalId) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADServicePrincipalSecret: $(AADServicePrincipalSecret) + AADServicePrincipalId: $(AADServicePrincipalId) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: false @@ -383,9 +417,8 @@ stages: AADAuthorityURL: $(AADAuthorityURL) ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR_eastus) - AADServicePrincipalId: $(AADServicePrincipalId) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADServicePrincipalSecret: $(AADServicePrincipalSecret) + AADServicePrincipalId: $(AADServicePrincipalId) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: false @@ -393,37 +426,6 @@ stages: LocalDbAppName: $(LocalDbAppName) LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: # only run enclave jobs if the password is available - windows_enclave_sql: - pool: ADO-CI-AE-1ES-Pool - images: - Win22_Enclave_Sql19: ADO-MMS22-SQL19 - TargetFrameworks: ${{parameters.targetFrameworks }} - netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} - buildPlatforms: ${{parameters.buildPlatforms }} - testSets: [AE] - useManagedSNI: ${{parameters.useManagedSNI }} - codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} - configSqlFor: enclave - operatingSystem: Windows - configProperties: - # config.json properties - TCPConnectionStringHGSVBS: $(SQL_TCP_CONN_STRING_HGSVBS) - TCPConnectionStringNoneVBS: $(SQL_TCP_CONN_STRING_NoneVBS) - TCPConnectionStringAASSGX: $(SQL_TCP_CONN_STRING_AASSGX) - EnclaveEnabled: true - AADAuthorityURL: $(AADAuthorityURL) - AADServicePrincipalId: $(AADServicePrincipalId) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: - AADServicePrincipalSecret: $(AADServicePrincipalSecret) - AzureKeyVaultUrl: $(AzureKeyVaultUrl) - AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) - SupportsIntegratedSecurity: $(SupportsIntegratedSecurity) - UserManagedIdentityClientId: $(UserManagedIdentityClientId) - AliasName: $(SQLAliasName) - LocalDbAppName: $(LocalDbAppName) - LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - # self hosted SQL Server on Linux linux_sql_19_22: pool: ${{parameters.defaultPoolName }} @@ -469,9 +471,8 @@ stages: AADAuthorityURL: $(AADAuthorityURL) ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) - AADServicePrincipalId: $(AADServicePrincipalId) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADServicePrincipalSecret: $(AADServicePrincipalSecret) + AADServicePrincipalId: $(AADServicePrincipalId) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: false @@ -479,7 +480,65 @@ stages: LocalDbAppName: $(LocalDbAppName) LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: # only run enclave jobs if the password is available + # Self hosted SQL Server on Mac + mac_sql_22: + pool: $(defaultHostedPoolName) + hostedPool: true + images: + MacOSLatest_Sql22: macos-latest + TargetFrameworks: ${{parameters.targetFrameworksLinux }} + netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} + buildPlatforms: [AnyCPU] + testSets: ${{parameters.testSets }} + useManagedSNI: [true] + codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} + configSqlFor: local + operatingSystem: Mac + configProperties: + # config.json properties + TCPConnectionString: $(SQL_TCP_CONN_STRING) + NPConnectionString: $(SQL_NP_CONN_STRING) + SupportsIntegratedSecurity: false + ManagedIdentitySupported: false + LocalDbAppName: $(LocalDbAppName) + LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) + + # Enclave tests + # + # Only run enclave jobs if the password is available, which it won't be + # for forked PRs. + # + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: + windows_enclave_sql: + pool: ADO-CI-AE-1ES-Pool + images: + Win22_Enclave_Sql19: ADO-MMS22-SQL19 + TargetFrameworks: ${{parameters.targetFrameworks }} + netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} + buildPlatforms: ${{parameters.buildPlatforms }} + testSets: [AE] + useManagedSNI: ${{parameters.useManagedSNI }} + codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} + configSqlFor: enclave + operatingSystem: Windows + configProperties: + # config.json properties + TCPConnectionStringHGSVBS: $(SQL_TCP_CONN_STRING_HGSVBS) + TCPConnectionStringNoneVBS: $(SQL_TCP_CONN_STRING_NoneVBS) + TCPConnectionStringAASSGX: $(SQL_TCP_CONN_STRING_AASSGX) + EnclaveEnabled: true + AADAuthorityURL: $(AADAuthorityURL) + AADServicePrincipalId: $(AADServicePrincipalId) + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: + AADServicePrincipalSecret: $(AADServicePrincipalSecret) + AzureKeyVaultUrl: $(AzureKeyVaultUrl) + AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) + SupportsIntegratedSecurity: $(SupportsIntegratedSecurity) + UserManagedIdentityClientId: $(UserManagedIdentityClientId) + AliasName: $(SQLAliasName) + LocalDbAppName: $(LocalDbAppName) + LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) + linux_enclave_sql: pool: ADO-CI-AE-1ES-Pool images: @@ -507,26 +566,3 @@ stages: UserManagedIdentityClientId: $(UserManagedIdentityClientId) LocalDbAppName: $(LocalDbAppName) LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - - # Self hosted SQL Server on Mac - mac_sql_22: - pool: $(defaultHostedPoolName) - hostedPool: true - images: - MacOSLatest_Sql22: macos-latest - TargetFrameworks: ${{parameters.targetFrameworksLinux }} - netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} - buildPlatforms: [AnyCPU] - testSets: ${{parameters.testSets }} - useManagedSNI: [true] - codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} - configSqlFor: local - operatingSystem: Mac - configProperties: - # config.json properties - TCPConnectionString: $(SQL_TCP_CONN_STRING) - NPConnectionString: $(SQL_NP_CONN_STRING) - SupportsIntegratedSecurity: false - ManagedIdentitySupported: false - LocalDbAppName: $(LocalDbAppName) - LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) diff --git a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml index 336bd97ab5..0e6ee71697 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml @@ -4,37 +4,78 @@ # See the LICENSE file in the project root for more information. # ################################################################################# +# Use the day of year and a revision number for the build name. This is what +# appears in the Azure DevOps UI. name: $(DayOfYear)$(Rev:rr) -trigger: - batch: true + +# Trigger PR validation runs for all pushes to PRs that target the specified +# branches. +# +# https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#pr-triggers +# +pr: branches: include: + # GitHub repo branch targets that will trigger PR validation builds. + - dev/* + - feat/* - main - - internal/main + paths: include: - - src\Microsoft.Data.SqlClient\netcore\ref - - src\Microsoft.Data.SqlClient\netfx\ref - - src\Microsoft.Data.SqlClient\ref + - .azuredevops + - .config + - src - eng - tools - - .config - - Nuget.config + - azurepipelines-coverage.yml + - build.proj + - NuGet.config -schedules: -- cron: '0 4 * * Fri' - displayName: Weekly Thursday 9:00 PM (UTC - 7) Build +# Trigger batched CI runs on pushes to the specified branches. +# +# https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#ci-triggers +# +trigger: + batch: true branches: include: + # GitHub repo branch targets that will trigger CI builds. + - feat/* + - main + + # ADO repo branch targets that will trigger CI builds. - internal/main - always: true -- cron: '0 0 * * Mon-Fri' - displayName: Daily build 5:00 PM (UTC - 7) Build - branches: + paths: include: - - main - always: true + - .azuredevops + - .config + - src + - eng + - tools + - azurepipelines-coverage.yml + - build.proj + - NuGet.config + +# Schedule periodic CI runs for the main branches. +schedules: + + # ADO internal/main + - cron: '0 4 * * Fri' + displayName: Weekly Thursday 9:00 PM (UTC - 7) Build + branches: + include: + - internal/main + always: true + + # GitHub main + - cron: '0 0 * * Mon-Fri' + displayName: Daily build 5:00 PM (UTC - 7) Build + branches: + include: + - main + always: true parameters: # parameters are shown up in ADO UI in a build queue time - name: 'debug' @@ -74,13 +115,6 @@ parameters: # parameters are shown up in ADO UI in a build queue time type: object default: [net462, net8.0] -- name: buildType - displayName: 'Build Type' - default: Package - values: - - Project - - Package - - name: buildConfiguration displayName: 'Build Configuration' default: Release @@ -109,7 +143,7 @@ extends: testSets: ${{ parameters.testSets }} useManagedSNI: ${{ parameters.useManagedSNI }} codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} - buildType: ${{ parameters.buildType }} + referenceType: Package buildConfiguration: ${{ parameters.buildConfiguration }} enableStressTests: ${{ parameters.enableStressTests }} testsTimeout: ${{ parameters.testsTimeout }} diff --git a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml index 38325d38ca..e63fefdf3d 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml @@ -4,29 +4,78 @@ # See the LICENSE file in the project root for more information. # ################################################################################# +# Use the day of year and a revision number for the build name. This is what +# appears in the Azure DevOps UI. name: $(DayOfYear)$(Rev:rr) + +# Trigger PR validation runs for all pushes to PRs that target the specified +# branches. +# +# https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#pr-triggers +# +pr: + branches: + include: + # GitHub repo branch targets that will trigger PR validation builds. + - dev/* + - feat/* + - main + + paths: + include: + - .azuredevops + - .config + - src + - eng + - tools + - azurepipelines-coverage.yml + - build.proj + - NuGet.config + +# Trigger batched CI runs on pushes to the specified branches. +# +# https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#ci-triggers +# trigger: batch: true branches: include: + # GitHub repo branch targets that will trigger CI builds. + - feat/* - main + + # ADO repo branch targets that will trigger CI builds. - internal/main + paths: include: + - .azuredevops + - .config - src - eng - tools - - .config + - azurepipelines-coverage.yml - build.proj - - Nuget.config + - NuGet.config +# Schedule periodic CI runs for the main branches. schedules: -- cron: '0 5 * * Thu' - displayName: Weekly Wednesday 10:00 PM (UTC - 7) Build - branches: - include: - - internal/main - always: true + + # ADO internal/main + - cron: '0 4 * * Fri' + displayName: Weekly Thursday 9:00 PM (UTC - 7) Build + branches: + include: + - internal/main + always: true + + # GitHub main + - cron: '0 0 * * Mon-Fri' + displayName: Daily build 5:00 PM (UTC - 7) Build + branches: + include: + - main + always: true parameters: # parameters are shown up in ADO UI in a build queue time - name: 'debug' @@ -66,13 +115,6 @@ parameters: # parameters are shown up in ADO UI in a build queue time type: object default: [net462, net8.0] -- name: buildType - displayName: 'Build Type' - default: Project - values: - - Project - - Package - - name: buildConfiguration displayName: 'Build Configuration' default: Release @@ -101,7 +143,7 @@ extends: testSets: ${{ parameters.testSets }} useManagedSNI: ${{ parameters.useManagedSNI }} codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} - buildType: ${{ parameters.buildType }} + referenceType: Project buildConfiguration: ${{ parameters.buildConfiguration }} enableStressTests: ${{ parameters.enableStressTests }} testsTimeout: ${{ parameters.testsTimeout }} diff --git a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml index e104b99193..3a81b58bfd 100644 --- a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml @@ -8,17 +8,20 @@ name: $(Year:YY)$(DayOfYear)$(Rev:.r) trigger: branches: include: + + # This pipeline is intended to only run against the ADO dotnet-sqlclient + # repo. - internal/main paths: include: + - .azuredevops + - .config - src - eng - tools - - .config + - azurepipelines-coverage.yml - build.proj - - Nuget.config - - '*.cmd' - - '*.sh' + - NuGet.config schedules: - cron: '30 4 * * Mon' @@ -107,14 +110,14 @@ extends: softwareFolder: $(softwareFolder) symbolsFolder: $(symbolsFolder) softwarename: Microsoft.Data.SqlClient - versionNumber: $(AssemblyFileVersion) + versionNumber: $(mdsAssemblyFileVersion) codeql: compiled: enabled: ${{ not(parameters['isPreview']) }} sbom: enabled: ${{ not(parameters['isPreview']) }} packageName: Microsoft.Data.SqlClient - packageVersion: $(NugetPackageVersion) + packageVersion: $(mdsPackageVersion) policheck: enabled: ${{ not(parameters['isPreview']) }} break: true # always break the build on policheck issues. You can disable it by setting to 'false' diff --git a/eng/pipelines/jobs/build-akv-official-job.yml b/eng/pipelines/jobs/build-akv-official-job.yml index a4374b773b..bad8758f6e 100644 --- a/eng/pipelines/jobs/build-akv-official-job.yml +++ b/eng/pipelines/jobs/build-akv-official-job.yml @@ -5,21 +5,21 @@ ################################################################################# parameters: + - name: akvAssemblyFileVersion + type: string + + - name: akvPackageVersion + type: string + - name: apiScanDllPath type: string - name: apiScanPdbPath type: string - - - name: assemblyFileVersion - type: string - name: buildConfiguration type: string - - name: nugetPackageVersion - type: string - - name: mdsPackageVersion type: string @@ -86,9 +86,18 @@ jobs: $jsonParams | ConvertFrom-Json | Format-List displayName: 'Output Job Parameters' + # Perform analysis before building, since this step will clobber build + # output + - template: ../steps/roslyn-analyzers-akv-step.yml@self + parameters: + akvPackageVersion: '${{ parameters.akvPackageVersion }}' + buildConfiguration: '${{ parameters.buildConfiguration }}' + mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + - template: ../steps/compound-build-akv-step.yml@self parameters: - assemblyFileVersion: '${{ parameters.assemblyFileVersion }}' + akvAssemblyFileVersion: '${{ parameters.akvAssemblyFileVersion }}' + akvPackageVersion: '${{ parameters.akvPackageVersion }}' buildConfiguration: '${{ parameters.buildConfiguration }}' mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' @@ -101,11 +110,6 @@ jobs: referenceType: Package targetFramework: '${{ targetFramework }}' - - template: ../steps/roslyn-analyzers-akv-step.yml@self - parameters: - buildConfiguration: '${{ parameters.buildConfiguration }}' - mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' - - template: ../steps/compound-esrp-code-signing-step.yml@self parameters: appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' @@ -120,10 +124,11 @@ jobs: parameters: buildConfiguration: '${{ parameters.buildConfiguration }}' generateSymbolsPackage: true # Always generate symbols, even if they are not published - packageVersion: '${{ parameters.nugetPackageVersion }}' + packageVersion: '${{ parameters.akvPackageVersion }}' nuspecPath: '$(REPO_ROOT)/tools/specs/add-ons/$(PACKAGE_NAME).nuspec' outputDirectory: '$(ARTIFACT_PATH)' - referenceType: 'Package' + referenceType: Package + properties: MdsPackageVersion=${{ parameters.mdsPackageVersion }} - template: ../steps/compound-esrp-code-signing-step.yml@self parameters: @@ -138,7 +143,7 @@ jobs: - ${{ if parameters.publishSymbols }}: - template: ../steps/compound-publish-symbols-step.yml@self parameters: - artifactName: 'akv_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.nugetPackageVersion }}_$(System.TimelineId)' + artifactName: 'akv_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.akvPackageVersion }}_$(System.TimelineId)' azureSubscription: '${{ parameters.symbolsAzureSubscription }}' publishProjectName: '${{ parameters.symbolsPublishProjectName }}' packageName: '$(PACKAGE_NAME)' @@ -151,4 +156,4 @@ jobs: Windows_NT/${{ parameters.buildConfiguration }}.AnyCPU/AzureKeyVaultProvider/**/$(PACKAGE_NAME).pdb AnyOS/${{ parameters.buildConfiguration }}.AnyCPU/AzureKeyVaultProvider/**/$(PACKAGE_NAME).pdb uploadAccount: '${{ parameters.symbolsUploadAccount }}' - version: '${{ parameters.nugetPackageVersion }}' + version: '${{ parameters.akvPackageVersion }}' diff --git a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml new file mode 100644 index 0000000000..13bf3618fb --- /dev/null +++ b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml @@ -0,0 +1,148 @@ +################################################################################ +# 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. +################################################################################ + +# This job packs the Abstractions package into NuGet and symbols packages and +# publishes them as a named pipeline artifact. +# +# This template defines a job named 'pack_abstractions_package_job' that can be +# depended on by downstream jobs. + +parameters: + + # The version to apply to the Abstractions NuGet package and its assemblies. + - name: abstractionsPackageVersion + type: string + + # The name to apply to the published pipeline artifact. + - name: artifactName + type: string + default: Abstractions.Artifact + + # The type of build to test (Release or Debug) + - name: buildConfiguration + type: string + values: + - Release + - Debug + + # The list of upstream jobs to depend on. + - name: dependsOn + type: object + default: [] + + # The verbosity level for the dotnet CLI commands. + - name: verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +jobs: + + - job: pack_abstractions_package_job + displayName: 'Pack Abstractions Package' + + dependsOn: ${{ parameters.dependsOn }} + + pool: + name: Azure Pipelines + vmImage: ubuntu-latest + + variables: + + # The Abstractions project file to use for all dotnet CLI commands. + - name: project + value: src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj + + # The directory where the NuGet packages will be staged before being + # published as pipeline artifacts. + - name: dotnetPackagesDir + value: $(Build.StagingDirectory)/dotnetPackages + + # dotnet CLI arguments common to all commands. + - name: commonArguments + value: >- + --verbosity ${{ parameters.verbosity }} + + # dotnet CLI arguments for build/test/pack commands + - name: buildArguments + value: >- + $(commonArguments) + --configuration ${{ parameters.buildConfiguration }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + + # Explicitly unset the $PLATFORM environment variable that is set by the + # 'ADO Build properties' Library in the ADO SqlClientDrivers public project. + # This is defined with a non-standard Platform of 'AnyCPU', and will fail + # the builds if left defined. The stress tests solution does not require + # any specific Platform, and so its solution file doesn't support any + # non-standard platforms. + # + # Note that Azure Pipelines will inject this variable as PLATFORM into the + # environment of all tasks in this job. + # + # See: + # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch + # + - name: Platform + value: '' + + # Do the same for $CONFIGURATION since we explicitly set it using our + # 'buildConfiguration' parameter, and we don't want the environment to + # override us. + - name: Configuration + value: '' + + steps: + + # Install the .NET 9.0 SDK. + - task: UseDotNet@2 + displayName: Install .NET 9.0 SDK + inputs: + packageType: sdk + version: 9.x + + # We use the 'custom' command because the DotNetCoreCLI@2 task doesn't + # support all of our argument combinations for the different build steps. + + # Restore the solution. + - task: DotNetCoreCLI@2 + displayName: Restore Solution + inputs: + command: custom + custom: restore + projects: $(project) + arguments: $(commonArguments) + + # Build the solution. + - task: DotNetCoreCLI@2 + displayName: Build Solution + inputs: + command: custom + custom: build + projects: $(project) + arguments: $(buildArguments) --no-restore + + # Create the NuGet packages. + - task: DotNetCoreCLI@2 + displayName: Create NuGet Package + inputs: + command: custom + custom: pack + projects: $(project) + arguments: $(buildArguments) --no-build -o $(dotnetPackagesDir) + + # Publish the NuGet packages as a named pipeline artifact. + - task: PublishPipelineArtifact@1 + displayName: Publish Pipeline Artifact + inputs: + targetPath: $(dotnetPackagesDir) + artifactName: ${{ parameters.artifactName }} + publishLocation: pipeline diff --git a/eng/pipelines/jobs/stress-tests-ci-job.yml b/eng/pipelines/jobs/stress-tests-ci-job.yml index 2e01470fe5..cc2913c367 100644 --- a/eng/pipelines/jobs/stress-tests-ci-job.yml +++ b/eng/pipelines/jobs/stress-tests-ci-job.yml @@ -4,7 +4,7 @@ # file in the project root for more information. ################################################################################ -# This stage builds and runs stress tests against an MDS NuGet package available +# This job builds and runs stress tests against an MDS NuGet package available # as a pipeline artifact. # # The stress tests are located here: diff --git a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml new file mode 100644 index 0000000000..a5e0f98978 --- /dev/null +++ b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml @@ -0,0 +1,174 @@ +################################################################################ +# 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. +################################################################################ + +# This job builds the Abstractions package and runs its tests for a set of .NET +# runtimes. +# +# This template defines a job named 'test_abstractions_package_job_' +# that can be depended on by downstream jobs. + +parameters: + + # The type of build to test (Release or Debug) + - name: buildConfiguration + type: string + values: + - Release + - Debug + + # The prefix to prepend to the job's display name: + # + # [] Run Stress Tests + # + - name: displayNamePrefix + type: string + + # The suffix to append to the job name. + - name: jobNameSuffix + type: string + + # The list of .NET Framework runtimes to test against. + - name: netFrameworkRuntimes + type: object + default: [] + + # The list of .NET runtimes to test against. + - name: netRuntimes + type: object + default: [] + + # The name of the Azure Pipelines pool to use. + - name: poolName + type: string + + # The verbosity level for the dotnet CLI commands. + - name: verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + + # The pool VM image to use. + - name: vmImage + type: string + +jobs: + + - job: test_abstractions_package_job_${{ parameters.jobNameSuffix }} + displayName: '[${{ parameters.displayNamePrefix }}] Test Abstractions Package' + pool: + name: ${{ parameters.poolName }} + + # Images provided by Azure Pipelines must be selected using 'vmImage'. + ${{ if eq(parameters.poolName, 'Azure Pipelines') }}: + vmImage: ${{ parameters.vmImage }} + # Images provided by 1ES must be selected using a demand. + ${{ else }}: + demands: + - imageOverride -equals ${{ parameters.vmImage }} + + variables: + + # The Abstractions test project file to use for all dotnet CLI commands. + - name: project + value: src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj + + # dotnet CLI arguments common to all commands. + - name: commonArguments + value: >- + --verbosity ${{ parameters.verbosity }} + + # dotnet CLI arguments for build/test/pack commands + - name: buildArguments + value: >- + $(commonArguments) + --configuration ${{ parameters.buildConfiguration }} + + # Explicitly unset the $PLATFORM environment variable that is set by the + # 'ADO Build properties' Library in the ADO SqlClientDrivers public project. + # This is defined with a non-standard Platform of 'AnyCPU', and will fail + # the builds if left defined. The stress tests solution does not require + # any specific Platform, and so its solution file doesn't support any + # non-standard platforms. + # + # Note that Azure Pipelines will inject this variable as PLATFORM into the + # environment of all tasks in this job. + # + # See: + # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch + # + - name: Platform + value: '' + + # Do the same for $CONFIGURATION since we explicitly set it using our + # 'buildConfiguration' parameter, and we don't want the environment to + # override us. + - name: Configuration + value: '' + + steps: + + # Install the .NET 9.0 SDK. + - task: UseDotNet@2 + displayName: Install .NET 9.0 SDK + inputs: + packageType: sdk + version: 9.x + + # Install the .NET 8.0 runtime. + - task: UseDotNet@2 + displayName: Install .NET 8.0 Runtime + inputs: + packageType: runtime + version: 8.x + + # The Windows agent images include a suitable .NET Framework runtime, so + # we don't have to install one explicitly. + + # We use the 'custom' command because the DotNetCoreCLI@2 task doesn't + # support all of our argument combinations for the different build steps. + + # Restore the solution. + - task: DotNetCoreCLI@2 + displayName: Restore Solution + inputs: + command: custom + custom: restore + projects: $(project) + arguments: $(commonArguments) + + # Build the solution. + - task: DotNetCoreCLI@2 + displayName: Build Solution + inputs: + command: custom + custom: build + projects: $(project) + arguments: $(buildArguments) --no-restore + + # Run the tests for each .NET runtime. + - ${{ each runtime in parameters.netRuntimes }}: + - task: DotNetCoreCLI@2 + displayName: Test [${{ runtime }}] + inputs: + command: custom + custom: test + projects: $(project) + arguments: $(buildArguments) --no-build -f ${{ runtime }} + + # Run the tests for each .NET Framework runtime. + - ${{ each runtime in parameters.netFrameworkRuntimes }}: + - task: DotNetCoreCLI@2 + displayName: Test [${{ runtime }}] + inputs: + command: custom + custom: test + projects: $(project) + arguments: $(buildArguments) --no-build -f ${{ runtime }} diff --git a/eng/pipelines/libraries/ci-build-variables.yml b/eng/pipelines/libraries/ci-build-variables.yml index d3ae6fcbc3..6b6ac2a825 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -6,7 +6,6 @@ variables: - group: 'ADO Build properties' - - group: 'ADO CI Packaging' - group: 'ADO Test Configuration Properties' - name: buildNumber @@ -15,21 +14,15 @@ variables: value: 'net8.0' - name: SQLTarget value: 'localhost' - - name: NugetPackageVersion - value: $(Major).$(Minor)$(Patch)-pull.1$(buildnumber) + - name: abstractionsPackageVersion + value: 1.0.0.$(buildNumber)-ci + - name: akvPackageVersion + value: 7.0.0.$(buildNumber)-ci + - name: mdsPackageVersion + value: 7.0.0.$(buildNumber)-ci - name: skipComponentGovernanceDetection value: true - name: runCodesignValidationInjection value: false - name: packagePath value: '$(Build.SourcesDirectory)/packages' - - # TODO(ADO-38703): Remove these when the other pipeline changes arrive. - - name: baseBuildNumber - value: $[ split(variables['Build.BuildNumber'], '.')[0] ] - - name: abstractionsPackageVersion - value: 1.0.0.$(baseBuildNumber) - - name: mdsPackageVersion - value: $(NugetPackageVersion) - - name: akvPackageVersion - value: $(NugetPackageVersion) diff --git a/eng/pipelines/libraries/common-variables.yml b/eng/pipelines/libraries/common-variables.yml index a986c714af..e03e239f3b 100644 --- a/eng/pipelines/libraries/common-variables.yml +++ b/eng/pipelines/libraries/common-variables.yml @@ -26,6 +26,39 @@ variables: - name: artifactDirectory value: '$(REPOROOT)/packages' + # C# assembly versions must be in the format: Major.Minor.Build.Revision, but + # $(Build.BuildNumber) has the format XXX.YY. Additionally, each version part + # must be a positive 16-bit integer less than 65535. Simply concatenating + # both parts of $(Build.BuildNumber) could produce values larger than 65534, + # so we must omit the second part entirely. Unfortunately, this may result + # in multiple subsequent pipline builds using the same C# assembly versions. + # The package versions will not be affected and will show the complete + # $(Build.BuildNumber) values. + - name: assemblyBuildNumber + value: $[ split(variables['Build.BuildNumber'], '.')[0] ] + + # ---------------------------------------------------------------------------- + # Abstractions Package Versions + # + # These are version values that will be used by the official build. They + # should be updated after each release to reflect the next release's versions. + + # The NuGet package version for GA releases (non-preview). + - name: abstractionsPackageVersion + value: '1.0.0' + + # The NuGet package version for preview releases. + - name: abstractionsPackagePreviewVersion + value: 1.0.0-preview1.$(Build.BuildNumber) + + # The AssemblyFileVersion for all assemblies in the Abstractions package. + # + - name: abstractionsAssemblyFileVersion + value: 1.0.0.$(assemblyBuildNumber) + + # ---------------------------------------------------------------------------- + # MDS Package Versions + # Update this after every release. This is used to generate the MDS NuGet package version. - name: Major value: '7' @@ -40,21 +73,11 @@ variables: - name: Revision value: '2' - - name: NugetPackageVersion + - name: mdsPackageVersion value: $(Major).$(Minor).$(Patch) - - name: PreviewNugetPackageVersion + - name: previewMdsPackageVersion value: $(Major).$(Minor).$(Patch)$(Preview)$(Revision).$(Build.BuildNumber) - - name: AssemblyFileVersion - value: '$(Major).$(Minor)$(Patch).$(Build.BuildNumber)' + - name: mdsAssemblyFileVersion + value: $(Major).$(Minor).$(Patch).$(assemblyBuildNumber) - name: nuspecPath value: '$(REPOROOT)/tools/specs/Microsoft.Data.SqlClient.nuspec' - - # TODO(ADO-38703): Remove these when the other pipeline changes arrive. - - name: baseBuildNumber - value: $[ split(variables['Build.BuildNumber'], '.')[0] ] - - name: abstractionsPackageVersion - value: 1.0.0.$(baseBuildNumber) - - name: mdsPackageVersion - value: $(NugetPackageVersion) - - name: akvPackageVersion - value: $(NugetPackageVersion) diff --git a/eng/pipelines/libraries/mds-validation-variables.yml b/eng/pipelines/libraries/mds-validation-variables.yml index d7723a059f..93dc0804ff 100644 --- a/eng/pipelines/libraries/mds-validation-variables.yml +++ b/eng/pipelines/libraries/mds-validation-variables.yml @@ -13,7 +13,7 @@ variables: - name: extractedNugetRootPath value: $(Build.SourcesDirectory)\$(TempFolderName)\Microsoft.Data.SqlClient - name: extractedNugetPath - value: $(extractedNugetRootPath).$(NugetPackageVersion) + value: $(extractedNugetRootPath).$(mdsPackageVersion) - name: expectedFolderNames value: lib,ref,runtimes - name: expectedDotnetVersions diff --git a/eng/pipelines/stages/build-abstractions-package-ci-stage.yml b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml new file mode 100644 index 0000000000..2a9268e737 --- /dev/null +++ b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml @@ -0,0 +1,119 @@ +################################################################################ +# 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. +################################################################################ + +# This stage builds the Abstractions package, runs tests, and publishes the +# resulting NuGet packages as pipeline artifacts. +# +# The NuGet packages have the following properties: +# +# Name: Microsoft.Data.SqlClient.Extensions.Abstractions +# Version: ${{ abstractionsPackageVersion }} (from parameter) +# +# The following NuGet packages are published: +# +# Microsoft.Data.SqlClient.Extensions.Abstractions..nupkg +# Microsoft.Data.SqlClient.Extensions.Abstractions..snupkg (symbols) +# +# The packages are published to pipeline artifacts with the name specified by +# the ${{ artifactName }} parameter. +# +# This template defines a stage named 'build_abstractions_package_stage' that +# can be depended on by downstream stages. + +parameters: + + # The version to apply to the NuGet package and DLLs. + - name: abstractionsPackageVersion + type: string + + # The name of the pipeline artifact to publish. + - name: artifactName + type: string + default: Abstractions.Artifact + + # The type of build to produce (Release or Debug) + - name: buildConfiguration + type: string + default: Release + values: + - Release + - Debug + + # The verbosity level for the dotnet CLI commands. + - name: verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +stages: + + - stage: build_abstractions_package_stage + displayName: Build Abstractions Package + + jobs: + + # ------------------------------------------------------------------------ + # Build and test on Linux. + + - template: ../jobs/test-abstractions-package-ci-job.yml@self + parameters: + jobNameSuffix: linux + displayNamePrefix: Linux + poolName: Azure Pipelines + vmImage: ubuntu-latest + buildConfiguration: ${{ parameters.buildConfiguration }} + netRuntimes: [net8.0, net9.0] + netFrameworkRuntimes: [] + verbosity: ${{ parameters.verbosity }} + + # ------------------------------------------------------------------------ + # Build and test on Windows + + - template: ../jobs/test-abstractions-package-ci-job.yml@self + parameters: + jobNameSuffix: windows + displayNamePrefix: Win + poolName: Azure Pipelines + vmImage: windows-latest + buildConfiguration: ${{ parameters.buildConfiguration }} + netRuntimes: [net8.0, net9.0] + netFrameworkRuntimes: [net462, net47, net471, net472, net48, net481] + verbosity: ${{ parameters.verbosity }} + + # ------------------------------------------------------------------------ + # Build and test on macOS. + + - template: ../jobs/test-abstractions-package-ci-job.yml + parameters: + jobNameSuffix: macos + displayNamePrefix: macOS + poolName: Azure Pipelines + vmImage: macos-latest + buildConfiguration: ${{ parameters.buildConfiguration }} + netRuntimes: [net8.0, net9.0] + netFrameworkRuntimes: [] + verbosity: ${{ parameters.verbosity }} + + # ------------------------------------------------------------------------ + # Create and publish the NuGet package. + + - template: ../jobs/pack-abstractions-package-ci-job.yml@self + parameters: + artifactName: ${{ parameters.artifactName }} + buildConfiguration: ${{ parameters.buildConfiguration }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + verbosity: ${{ parameters.verbosity }} + dependsOn: + # We depend on all of the test jobs to ensure the tests pass before + # producing the NuGet package. + - test_abstractions_package_job_linux + - test_abstractions_package_job_windows + - test_abstractions_package_job_macos diff --git a/eng/pipelines/steps/compound-build-akv-step.yml b/eng/pipelines/steps/compound-build-akv-step.yml index 906dcfaf72..75e6e00e3d 100644 --- a/eng/pipelines/steps/compound-build-akv-step.yml +++ b/eng/pipelines/steps/compound-build-akv-step.yml @@ -7,10 +7,13 @@ # @TODO: This can probably be made generic and pass in the command lines for msbuild # BUT, they should be kept separate by now as we rebuild build.proj in parallel, we won't # affect >1 project at a time. -# @TODO: NugetPackageVersion should not be used for MDS package version +# @TODO: mdsPackageVersion should not be used for MDS package version parameters: - - name: assemblyFileVersion + - name: akvAssemblyFileVersion + type: string + + - name: akvPackageVersion type: string - name: buildConfiguration @@ -32,12 +35,6 @@ steps: packageType: 'sdk' version: '9.x' - - task: UseDotNet@2 - displayName: 'Install .NET 8.x Runtime' - inputs: - packageType: 'runtime' - version: '8.x' - - task: MSBuild@1 displayName: 'Build.proj - BuildAkv' inputs: @@ -45,8 +42,9 @@ steps: configuration: '${{ parameters.buildConfiguration }}' msbuildArguments: >- -t:BuildAkv - -p:AssemblyFileVersion=${{ parameters.assemblyFileVersion }} - -p:NugetPackageVersion=${{ parameters.mdsPackageVersion }} + -p:AssemblyFileVersion=${{ parameters.akvAssemblyFileVersion }} + -p:AkvPackageVersion=${{ parameters.akvPackageVersion }} + -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:ReferenceType=Package -p:SigningKeyPath=$(Agent.TempDirectory)/netfxKeypair.snk diff --git a/eng/pipelines/steps/compound-nuget-pack-step.yml b/eng/pipelines/steps/compound-nuget-pack-step.yml index 888078ed30..d645a361c3 100644 --- a/eng/pipelines/steps/compound-nuget-pack-step.yml +++ b/eng/pipelines/steps/compound-nuget-pack-step.yml @@ -23,14 +23,36 @@ parameters: - name: referenceType type: string values: - - Package - - Project + - Package + - Project + + # Semi-colon separated properties to pass to nuget via the -properties + # argument. + - name: properties + type: string + default: '' steps: - - task: NuGetToolInstaller@1 - displayName: 'Install Latest Nuget' - inputs: - checkLatest: true + # This tool is failing on OneBranch pipelines, possibly due to new + # network isolation rules: + # + # ERR:Client network socket disconnected before secure TLS connection was established + # + # Our AKV Official build uses this 1ES image: + # + # Image: 1ES-OB-2022-D8-Netlock-V2_westus2_1_image + # + # An ICM for this issue exists: + # + # https://portal.microsofticm.com/imp/v5/incidents/details/690355343/summary + # + # Recommendation is to remove this step since NuGet is already present on + # the 1ES images. + # + # - task: NuGetToolInstaller@1 + # displayName: 'Install Latest Nuget' + # inputs: + # checkLatest: true - ${{ if parameters.generateSymbolsPackage }}: - task: NuGetCommand@2 @@ -44,7 +66,7 @@ steps: -SymbolPackageFormat snupkg -Version ${{ parameters.packageVersion }} -OutputDirectory ${{ parameters.outputDirectory }} - -Properties "COMMITID=$(Build.SourceVersion);Configuration=${{ parameters.buildConfiguration }};ReferenceType=${{ parameters.referenceType }}" + -Properties "COMMITID=$(Build.SourceVersion);Configuration=${{ parameters.buildConfiguration }};ReferenceType=${{ parameters.referenceType }};${{ parameters.properties }}" - ${{ else }}: - task: NuGetCommand@2 displayName: 'Generate NuGet Package' @@ -55,4 +77,4 @@ steps: ${{ parameters.nuspecPath }} -Version ${{ parameters.packageVersion }} -OutputDirectory ${{ parameters.outputDirectory }} - -Properties "COMMITID=$(Build.SourceVersion);Configuration=${{ parameters.buildConfiguration }};ReferenceType=${{ parameters.referenceType }}" + -Properties "COMMITID=$(Build.SourceVersion);Configuration=${{ parameters.buildConfiguration }};ReferenceType=${{ parameters.referenceType }};${{ parameters.properties }}" diff --git a/eng/pipelines/steps/roslyn-analyzers-akv-step.yml b/eng/pipelines/steps/roslyn-analyzers-akv-step.yml index 0e05177d5a..909241e4b3 100644 --- a/eng/pipelines/steps/roslyn-analyzers-akv-step.yml +++ b/eng/pipelines/steps/roslyn-analyzers-akv-step.yml @@ -4,11 +4,23 @@ # See the LICENSE file in the project root for more information. # ################################################################################# +# This template defines a step to run Roslyn Analyzers on the AKV project build. +# It uses the RoslynAnalyzers@3 task from the Secure Development Team's SDL +# extension: +# +# https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-mohanb/security-integration/guardian-wiki/sdl-azdo-extension/roslyn-analyzers-build-task +# +# GOTCHA: This step will clobber any existing build output. It should be run +# _before_ any build steps that perform versioning or signing. + # @TODO: This can probably be made generic and pass in the command lines for msbuild # BUT, they should be kept separate by now as we rebuild build.proj in parallel, we won't # affect >1 project at a time. parameters: + - name: akvPackageVersion + type: string + - name: buildConfiguration type: string @@ -25,7 +37,8 @@ steps: $(REPO_ROOT)/build.proj -t:BuildAkv -p:Configuration=${{ parameters.buildConfiguration }} - -p:NugetPackageVersion=${{ parameters.mdsPackageVersion }} + -p:AkvPackageVersion=${{ parameters.akvPackageVersion }} + -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:ReferenceType=Package msBuildVersion: 17.0 setupCommandLinePicker: vs2022 diff --git a/eng/pipelines/variables/akv-official-variables.yml b/eng/pipelines/variables/akv-official-variables.yml index aaf8de7c5e..591a562686 100644 --- a/eng/pipelines/variables/akv-official-variables.yml +++ b/eng/pipelines/variables/akv-official-variables.yml @@ -35,9 +35,7 @@ variables: value: '-preview1' # Compound Variables --------------------------------------------------- - - name: assemblyFileVersion + - name: akvAssemblyFileVersion value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}${{ variables.versionPatch }}.$(Build.BuildNumber)' - - name: nugetPackageVersion + - name: akvPackageVersion value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}.${{ variables.versionPatch }}${{ variables.versionPreview }}' - - diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index f4fa41bf45..4b658f409a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -55,12 +55,9 @@ - - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index d46e4c5f37..fff624a59a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -1091,12 +1091,9 @@ - - diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index e8dbcf7070..f73b8682a0 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -58,12 +58,9 @@ - - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 0886b0a973..3729377928 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -1068,12 +1068,9 @@ - - diff --git a/tools/props/Versions.props b/tools/props/Versions.props index 7a3ba3cc9a..d82a04cce2 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -31,10 +31,14 @@ specific to MDS, and then used in the MDS project. As-is, these names are used by the build tooling and may be unintentionally included in other (non-MDS) projects. + + For example, the AKV package uses the AssemblyVersion, FileVersion, and + Version as its own. It currently isn't possible to build/package both + MDS and AKV at the same time. --> + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - + + - - - - + + + + - - - - + + + + diff --git a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec index a313d0af12..e55fb4e7cb 100644 --- a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec +++ b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec @@ -36,12 +36,6 @@ Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyStoreProvider.SqlColumnEncrypti - - - - - - @@ -51,23 +45,28 @@ Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyStoreProvider.SqlColumnEncrypti + + - - - - - + + + + + - - - + + + - - - + + + - + + + + + + + + + + + + + + + diff --git a/build.proj b/build.proj index 2a41791844..d5cdcf73ef 100644 --- a/build.proj +++ b/build.proj @@ -28,10 +28,13 @@ $(TF) $(TF) true - Configuration=$(Configuration);AssemblyVersion=$(SqlServerAssemblyVersion);AssemblyFileVersion=$(SqlServerAssemblyFileVersion);Version=$(SqlServerPackageVersion); - Configuration=$(Configuration);AssemblyFileVersion=$(AssemblyFileVersion);TargetsWindows=$(TargetsWindows);TargetsUnix=$(TargetsUnix); + + Configuration=$(Configuration);ReferenceType=$(ReferenceType) + $(CommonProperties);AssemblyVersion=$(SqlServerAssemblyVersion);AssemblyFileVersion=$(SqlServerAssemblyFileVersion);Version=$(SqlServerPackageVersion); + $(CommonProperties);AssemblyFileVersion=$(AssemblyFileVersion);TargetsWindows=$(TargetsWindows);TargetsUnix=$(TargetsUnix); $(ProjectProperties);BuildForRelease=false;TargetNetCoreVersion=$(TargetNetCoreVersion);TargetNetFxVersion=$(TargetNetFxVersion) TestResults + + + + @@ -101,6 +107,8 @@ + $(CommonProperties) + - AbstractionsPackageVersion=$(AbstractionsPackageVersion) + $(AbstractionsProperties);AbstractionsPackageVersion=$(AbstractionsPackageVersion) @@ -133,7 +141,7 @@ Properties="$(AbstractionsProperties)" /> - + - + + + + $(CommonProperties) + + + + $(AzureProperties);AzurePackageVersion=$(AzurePackageVersion) + + + + + $(AzureProperties);AzureAssemblyFileVersion=$(AzureAssemblyFileVersion) + + + + + + + + + + + + + + + + + + + - + - + @@ -175,7 +239,7 @@ Name="RestoreNetFx" DependsOnTargets="RestoreSqlServerLib;RestoreAbstractions" Condition="'$(IsEnabledWindows)' == 'true'"> - + - - - dotnet build -c Release -p:ReferenceType=$(ReferenceType) - - + + + + + + - + - + @@ -238,29 +303,29 @@ Name="BuildUnitTestsNetCore" DependsOnTargets="RestoreTestsNetCore;BuildNetCore" Condition="$(ReferenceType.Contains('Project'))"> - + - + - + - + - + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml new file mode 100644 index 0000000000..f79488fd08 --- /dev/null +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml @@ -0,0 +1,20 @@ + + + + Manages which authentication provider is used for each authentication method. + + + The authentication method. + Gets an authentication provider by method. + The authentication provider or if not found. + + + The authentication method. + The authentication provider. + Sets an authentication provider by method. + + if the operation succeeded; otherwise, (for example, the existing provider disallows overriding). + + + + diff --git a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml index b90fe026a8..a77ee79146 100644 --- a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml @@ -49,7 +49,7 @@ jobs: configuration: $(Configuration) msbuildArguments: -t:BuildTools - # Perform analysis before building, since this step will clobber build output + # Perform analysis before building, since this step will clobber build output. - template: ../steps/code-analyze-step.yml@self # Update the root NuGet.config to use the packages/ directory as a source. diff --git a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml index 06bc31911b..ceab63eb61 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -10,6 +10,12 @@ parameters: - name: abstractionsPackageVersion type: string + - name: azureArtifactName + type: string + + - name: azurePackageVersion + type: string + - name: configProperties type: object default: {} # - key: 'value' @@ -121,6 +127,12 @@ jobs: artifactName: ${{ parameters.mdsArtifactName }} targetPath: $(Build.SourcesDirectory)/packages + - task: DownloadPipelineArtifact@2 + displayName: Download Azure Package Artifact + inputs: + artifactName: ${{ parameters.azureArtifactName }} + targetPath: $(Build.SourcesDirectory)/packages + - ${{ if ne(parameters.prebuildSteps, '') }}: - ${{ parameters.prebuildSteps }} # extra steps to run before the build like downloading sni and the required configuration @@ -253,6 +265,7 @@ jobs: referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + azurePackageVersion: ${{ parameters.azurePackageVersion }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} ${{ if ne(parameters.operatingSystem, 'Windows') }}: OSGroup: Unix diff --git a/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml index 42000da697..030199e72b 100644 --- a/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml @@ -305,17 +305,6 @@ jobs: Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object VersionInfo | Format-List displayName: 'Verify "File Version" matches expected values for DLLs' - - powershell: | - # Change TestMicrosoftDataSqlClientVersion - - [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" - $versionpropspath = "tools\props\Versions.props" - $versionprops.Project.PropertyGroup[$versionprops.Project.PropertyGroup.Count-1].TestMicrosoftDataSqlClientVersion ="$(mdsPackageVersion)" - Write-Host "Saving Test nuget version at $rootfolder\props ...." -ForegroundColor Green - $versionprops.Save($versionpropspath) - - displayName: 'Modify TestMicrosoftDataSqlClientVersion' - - powershell: | # Check assembly versions. # diff --git a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml index 46318f8e06..d9c7148c82 100644 --- a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml +++ b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml @@ -10,6 +10,12 @@ parameters: - name: abstractionsPackageVersion type: string + - name: azureArtifactName + type: string + + - name: azurePackageVersion + type: string + - name: debug type: boolean default: false @@ -68,7 +74,9 @@ stages: jobDisplayName: ${{ format('{0}_{1}_{2}', replace(targetFramework, '.', '_'), platform, testSet) }} configProperties: ${{ config.value.configProperties }} abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} - abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + azureArtifactName: ${{ parameters.azureArtifactName }} + azurePackageVersion: ${{ parameters.azurePackageVersion }} mdsArtifactName: ${{ parameters.mdsArtifactName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} @@ -102,7 +110,9 @@ stages: configProperties: ${{ config.value.configProperties }} useManagedSNI: ${{ useManagedSNI }} abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} - abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + azureArtifactName: ${{ parameters.azureArtifactName }} + azurePackageVersion: ${{ parameters.azurePackageVersion }} mdsArtifactName: ${{ parameters.mdsArtifactName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} diff --git a/eng/pipelines/common/templates/steps/build-all-tests-step.yml b/eng/pipelines/common/templates/steps/build-all-tests-step.yml index 43ecb6aafa..36aa703f28 100644 --- a/eng/pipelines/common/templates/steps/build-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/build-all-tests-step.yml @@ -7,6 +7,9 @@ parameters: - name: abstractionsPackageVersion type: string + - name: azurePackageVersion + type: string + - name: configuration type: string default: '$(Configuration)' @@ -42,7 +45,7 @@ steps: solution: build.proj platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' - msbuildArguments: '-t:BuildTestsNetFx -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' + msbuildArguments: '-t:BuildTestsNetFx -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:AzurePackageVersion=${{ parameters.azurePackageVersion }}' - ${{elseif eq(parameters.osGroup, '')}}: # .NET on Windows - task: MSBuild@1 @@ -51,7 +54,7 @@ steps: solution: build.proj platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' - msbuildArguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' + msbuildArguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:AzurePackageVersion=${{ parameters.azurePackageVersion }}' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) - ${{ else }}: # .NET on Unix @@ -61,7 +64,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:OSGroup=${{parameters.osGroup }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' + arguments: '-t:BuildTestsNetCore -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:OSGroup=${{parameters.osGroup }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:AzurePackageVersion=${{ parameters.azurePackageVersion }}' verbosityRestore: Detailed verbosityPack: Detailed condition: and(succeeded(), ne(variables['Agent.OS'], 'Windows_NT')) diff --git a/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml b/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml index 43eea4449d..c23e7d310a 100644 --- a/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml +++ b/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml @@ -55,14 +55,14 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' + msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }}' - task: MSBuild@1 displayName: 'MSBuild Build Tests for ${{parameters.TargetNetCoreVersion }}' inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:Configuration=${{parameters.configuration }}' + msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:Configuration=${{parameters.configuration }}' # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -71,12 +71,12 @@ steps: inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.FunctionalTests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests"' - task: DotNetCoreCLI@2 displayName: 'Run Manual Tests for ${{parameters.TargetNetCoreVersion }}' inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests --collect "Code Coverage"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests --collect "Code Coverage"' retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} diff --git a/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml b/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml index e7b35f653e..8d2a57872b 100644 --- a/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml +++ b/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml @@ -55,13 +55,13 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' + msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }}' - task: MSBuild@1 displayName: 'MSBuild Build Tests for ${{parameters.TargetNetFxVersion }}' inputs: solution: build.proj - msbuildArguments: ' -t:BuildTestsNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:Configuration=${{parameters.configuration }} -p:Platform=${{parameters.platform }}' + msbuildArguments: ' -t:BuildTestsNetFx -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:Configuration=${{parameters.configuration }} -p:Platform=${{parameters.platform }}' # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -70,12 +70,12 @@ steps: inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.FunctionalTests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' - task: DotNetCoreCLI@2 displayName: 'Run Manual Tests for ${{parameters.TargetNetFxVersion }}' inputs: command: test projects: 'src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj' - arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' + arguments: '-p:Platform=${{parameters.platform }} -p:TestTargetOS="${{parameters.TestTargetOS }}" -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:ReferenceType=${{parameters.referenceType }} -p:Configuration=${{parameters.configuration }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" --collect "Code Coverage"' retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} diff --git a/eng/pipelines/common/templates/steps/run-all-tests-step.yml b/eng/pipelines/common/templates/steps/run-all-tests-step.yml index 27da9e4e73..9b3289e300 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -62,9 +62,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' + msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }}' ${{ else }}: # x86 - msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' + msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) retryCountOnTaskFailure: 1 @@ -76,9 +76,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' + msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }}' ${{ else }}: # x86 - msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' + msbuildArguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) retryCountOnTaskFailure: 1 @@ -90,9 +90,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.configuration }}' ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: - msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' + msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }}' ${{ else }}: # x86 - msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' + msbuildArguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' condition: eq(variables['Agent.OS'], 'Windows_NT') retryCountOnTaskFailure: 2 @@ -104,7 +104,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 @@ -116,7 +116,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:RunFunctionalTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 @@ -128,7 +128,7 @@ steps: command: custom projects: build.proj custom: msbuild - arguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' + arguments: '-t:RunManualTests -p:TF=${{parameters.targetFramework }} -p:TestSet=${{parameters.testSet }} -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:platform=${{parameters.platform }} -p:Configuration=${{parameters.configuration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 2 diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 86987f63e2..9c13159a14 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -94,6 +94,9 @@ variables: - name: abstractionsArtifactName value: Abstractions.Artifact + + - name: azureArtifactName + value: Azure.Artifact - name: mdsArtifactName value: MDS.Artifact @@ -107,7 +110,7 @@ stages: # under the given artifact name. - template: stages/build-abstractions-package-ci-stage.yml@self parameters: - buildConfiguration: Release + buildConfiguration: ${{ parameters.buildConfiguration }} abstractionsPackageVersion: $(abstractionsPackageVersion) artifactName: $(abstractionsArtifactName) ${{if eq(parameters.debug, 'true')}}: @@ -141,6 +144,24 @@ stages: SNIVersion: ${{parameters.SNIVersion}} SNIValidationFeed: ${{parameters.SNIValidationFeed}} + # Build the Azure package, and publish it to the pipeline artifacts under the + # given artifact name. + - template: stages/build-azure-package-ci-stage.yml@self + parameters: + abstractionsArtifactName: $(abstractionsArtifactName) + abstractionsPackageVersion: $(abstractionsPackageVersion) + azureArtifactName: $(azureArtifactName) + azurePackageVersion: $(azurePackageVersion) + buildConfiguration: ${{ parameters.buildConfiguration }} + # When building via package references, we must depend on the Abstractions + # package. + ${{ if eq(parameters.referenceType, 'Package') }}: + dependsOn: + - build_abstractions_package_stage + referenceType: ${{ parameters.referenceType }} + ${{if eq(parameters.debug, 'true')}}: + verbosity: diagnostic + # Run the stress tests, if desired. - ${{ if eq(parameters.enableStressTests, true) }}: - template: stages/stress-tests-ci-stage.yml@self @@ -160,14 +181,17 @@ stages: testsTimeout: ${{ parameters.testsTimeout }} abstractionsArtifactName: $(abstractionsArtifactName) abstractionsPackageVersion: $(abstractionsPackageVersion) + azureArtifactName: $(azureArtifactName) + azurePackageVersion: $(azurePackageVersion) mdsArtifactName: $(mdsArtifactName) mdsPackageVersion: $(mdsPackageVersion) - # When testing MDS via packages, we must depend on the Abstractions and - # MDS packages. + # When testing MDS via packages, we must depend on the Abstractions, + # Azure, and MDS packages. ${{ if eq(parameters.referenceType, 'Package') }}: dependsOn: - build_abstractions_package_stage + - build_azure_package_stage - build_mds_akv_packages_stage prebuildSteps: # steps to run prior to building and running tests on each job diff --git a/eng/pipelines/jobs/build-akv-official-job.yml b/eng/pipelines/jobs/build-akv-official-job.yml index bad8758f6e..55ebff9f79 100644 --- a/eng/pipelines/jobs/build-akv-official-job.yml +++ b/eng/pipelines/jobs/build-akv-official-job.yml @@ -87,7 +87,7 @@ jobs: displayName: 'Output Job Parameters' # Perform analysis before building, since this step will clobber build - # output + # output. - template: ../steps/roslyn-analyzers-akv-step.yml@self parameters: akvPackageVersion: '${{ parameters.akvPackageVersion }}' diff --git a/eng/pipelines/jobs/pack-azure-package-ci-job.yml b/eng/pipelines/jobs/pack-azure-package-ci-job.yml new file mode 100644 index 0000000000..9cc33eadb1 --- /dev/null +++ b/eng/pipelines/jobs/pack-azure-package-ci-job.yml @@ -0,0 +1,188 @@ +################################################################################ +# 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. +################################################################################ + +# This job packs the Azure package into NuGet and symbols packages and publishes +# them as a named pipeline artifact. +# +# This template defines a job named 'pack_azure_package_job' that can be +# depended on by downstream jobs. + +parameters: + + # The name of the Abstractions pipeline artifact to download. + # + # This is used when the referenceType is 'Package'. + - name: abstractionsArtifactName + type: string + default: Abstractions.Artifact + + # The Abstractions package verion to depend on. + # + # This is used when the referenceType is 'Package'. + - name: abstractionsPackageVersion + type: string + + # The name of the pipeline artifact to publish. + - name: azureArtifactName + type: string + default: Azure.Artifact + + # The version to apply to the NuGet package and DLLs. + - name: azurePackageVersion + type: string + + # The type of build to test (Release or Debug) + - name: buildConfiguration + type: string + values: + - Release + - Debug + + # The list of upstream jobs to depend on. + - name: dependsOn + type: object + default: [] + + # The reference type to use: + # + # Project - dependent projects are referenced directly. + # Package - dependent projects are referenced via NuGet packages. + # + - name: referenceType + type: string + values: + - Package + - Project + + # The verbosity level for the dotnet CLI commands. + - name: verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +jobs: + + - job: pack_azure_package_job + displayName: 'Pack Azure Package' + + dependsOn: ${{ parameters.dependsOn }} + + pool: + name: Azure Pipelines + vmImage: ubuntu-latest + + variables: + + # The Azure project file to use for all dotnet CLI commands. + - name: project + value: src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj + + # The directory where the NuGet packages will be staged before being + # published as pipeline artifacts. + - name: dotnetPackagesDir + value: $(Build.StagingDirectory)/dotnetPackages + + # dotnet CLI arguments common to all commands. + - name: commonArguments + value: >- + --verbosity ${{ parameters.verbosity }} + -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + + # dotnet CLI arguments for build/test/pack commands + - name: buildArguments + value: >- + $(commonArguments) + --configuration ${{ parameters.buildConfiguration }} + -p:AzurePackageVersion=${{ parameters.azurePackageVersion }} + + # Explicitly unset the $PLATFORM environment variable that is set by the + # 'ADO Build properties' Library in the ADO SqlClientDrivers public + # project. This is defined with a non-standard Platform of 'AnyCPU', and + # will fail the builds if left defined. The stress tests solution does + # not require any specific Platform, and so its solution file doesn't + # support any non-standard platforms. + # + # Note that Azure Pipelines will inject this variable as PLATFORM into the + # environment of all tasks in this job. + # + # See: + # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch + # + - name: Platform + value: '' + + # Do the same for $CONFIGURATION since we explicitly set it using our + # 'buildConfiguration' parameter, and we don't want the environment to + # override us. + - name: Configuration + value: '' + + steps: + + # We have a few extra steps for Package reference builds. + - ${{ if eq(parameters.referenceType, 'Package') }}: + + # Download the Abstractions package artifacts into packages/. + - task: DownloadPipelineArtifact@2 + displayName: Download Abstractions Package Artifact + inputs: + artifactName: ${{ parameters.abstractionsArtifactName }} + targetPath: $(Build.SourcesDirectory)/packages + + # Use the local NuGet.config that references the packages/ directory. + - pwsh: cp $(Build.SourcesDirectory)/NuGet.config.local $(Build.SourcesDirectory)/NuGet.config + displayName: Use local NuGet.config + + # Install the .NET 9.0 SDK. + - task: UseDotNet@2 + displayName: Install .NET 9.0 SDK + inputs: + packageType: sdk + version: 9.x + + # We use the 'custom' command because the DotNetCoreCLI@2 task doesn't + # support all of our argument combinations for the different build steps. + + # Restore the solution. + - task: DotNetCoreCLI@2 + displayName: Restore Solution + inputs: + command: custom + custom: restore + projects: $(project) + arguments: $(commonArguments) + + # Build the solution. + - task: DotNetCoreCLI@2 + displayName: Build Solution + inputs: + command: custom + custom: build + projects: $(project) + arguments: $(buildArguments) --no-restore + + # Create the NuGet packages. + - task: DotNetCoreCLI@2 + displayName: Create NuGet Package + inputs: + command: custom + custom: pack + projects: $(project) + arguments: $(buildArguments) --no-build -o $(dotnetPackagesDir) + + # Publish the NuGet packages as a named pipeline artifact. + - task: PublishPipelineArtifact@1 + displayName: Publish Pipeline Artifact + inputs: + targetPath: $(dotnetPackagesDir) + artifactName: ${{ parameters.azureArtifactName }} + publishLocation: pipeline diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml new file mode 100644 index 0000000000..8e1b8fc6db --- /dev/null +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -0,0 +1,214 @@ +################################################################################ +# 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. +################################################################################ + +# This job builds the Azure package and runs its tests for a set of .NET +# runtimes. +# +# This template defines a job named 'test_azure_package_job_' that can +# be depended on by downstream jobs. + +parameters: + + # The name of the Abstractions pipeline artifact to download. + # + # This is used when the referenceType is 'Package'. + - name: abstractionsArtifactName + type: string + default: Abstractions.Artifact + + # The Abstractions package verion to depend on. + # + # This is used when the referenceType is 'Package'. + - name: abstractionsPackageVersion + type: string + + # The type of build to test (Release or Debug) + - name: buildConfiguration + type: string + values: + - Release + - Debug + + # The prefix to prepend to the job's display name: + # + # [] Run Stress Tests + # + - name: displayNamePrefix + type: string + + # The suffix to append to the job name. + - name: jobNameSuffix + type: string + + # The list of .NET Framework runtimes to test against. + - name: netFrameworkRuntimes + type: object + default: [] + + # The list of .NET runtimes to test against. + - name: netRuntimes + type: object + default: [] + + # The name of the Azure Pipelines pool to use. + - name: poolName + type: string + + # The reference type to use: + # + # Project - dependent projects are referenced directly. + # Package - dependent projects are referenced via NuGet packages. + # + - name: referenceType + type: string + values: + - Package + - Project + + # The verbosity level for the dotnet CLI commands. + - name: verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + + # The pool VM image to use. + - name: vmImage + type: string + +jobs: + + - job: test_azure_package_job_${{ parameters.jobNameSuffix }} + displayName: '[${{ parameters.displayNamePrefix }}] Test Azure Package' + pool: + name: ${{ parameters.poolName }} + + # Images provided by Azure Pipelines must be selected using 'vmImage'. + ${{ if eq(parameters.poolName, 'Azure Pipelines') }}: + vmImage: ${{ parameters.vmImage }} + # Images provided by 1ES must be selected using a demand. + ${{ else }}: + demands: + - imageOverride -equals ${{ parameters.vmImage }} + + variables: + + # The Azure test project file to use for all dotnet CLI commands. + - name: project + value: src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj + + # dotnet CLI arguments common to all commands. + - name: commonArguments + value: >- + --verbosity ${{ parameters.verbosity }} + -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + + # dotnet CLI arguments for build/test/pack commands + - name: buildArguments + value: >- + $(commonArguments) + --configuration ${{ parameters.buildConfiguration }} + + # Explicitly unset the $PLATFORM environment variable that is set by the + # 'ADO Build properties' Library in the ADO SqlClientDrivers public + # project. This is defined with a non-standard Platform of 'AnyCPU', and + # will fail the builds if left defined. The stress tests solution does + # not require any specific Platform, and so its solution file doesn't + # support any non-standard platforms. + # + # Note that Azure Pipelines will inject this variable as PLATFORM into the + # environment of all tasks in this job. + # + # See: + # https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch + # + - name: Platform + value: '' + + # Do the same for $CONFIGURATION since we explicitly set it using our + # 'buildConfiguration' parameter, and we don't want the environment to + # override us. + - name: Configuration + value: '' + + steps: + + # We have a few extra steps for Package reference builds. + - ${{ if eq(parameters.referenceType, 'Package') }}: + + # Download the Abstractions package artifacts into packages/. + - task: DownloadPipelineArtifact@2 + displayName: Download Abstractions Package Artifact + inputs: + artifactName: ${{ parameters.abstractionsArtifactName }} + targetPath: $(Build.SourcesDirectory)/packages + + # Use the local NuGet.config that references the packages/ directory. + - pwsh: cp $(Build.SourcesDirectory)/NuGet.config.local $(Build.SourcesDirectory)/NuGet.config + displayName: Use local NuGet.config + + # Install the .NET 9.0 SDK. + - task: UseDotNet@2 + displayName: Install .NET 9.0 SDK + inputs: + packageType: sdk + version: 9.x + + # Install the .NET 8.0 runtime. + - task: UseDotNet@2 + displayName: Install .NET 8.0 Runtime + inputs: + packageType: runtime + version: 8.x + + # The Windows agent images include a suitable .NET Framework runtime, so + # we don't have to install one explicitly. + + # We use the 'custom' command because the DotNetCoreCLI@2 task doesn't + # support all of our argument combinations for the different build steps. + + # Restore the solution. + - task: DotNetCoreCLI@2 + displayName: Restore Solution + inputs: + command: custom + custom: restore + projects: $(project) + arguments: $(commonArguments) + + # Build the solution. + - task: DotNetCoreCLI@2 + displayName: Build Solution + inputs: + command: custom + custom: build + projects: $(project) + arguments: $(buildArguments) --no-restore + + # Run the tests for each .NET runtime. + - ${{ each runtime in parameters.netRuntimes }}: + - task: DotNetCoreCLI@2 + displayName: Test [${{ runtime }}] + inputs: + command: custom + custom: test + projects: $(project) + arguments: $(buildArguments) --no-build -f ${{ runtime }} + + # Run the tests for each .NET Framework runtime. + - ${{ each runtime in parameters.netFrameworkRuntimes }}: + - task: DotNetCoreCLI@2 + displayName: Test [${{ runtime }}] + inputs: + command: custom + custom: test + projects: $(project) + arguments: $(buildArguments) --no-build -f ${{ runtime }} diff --git a/eng/pipelines/libraries/ci-build-variables.yml b/eng/pipelines/libraries/ci-build-variables.yml index 6b6ac2a825..79ec983027 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -18,6 +18,8 @@ variables: value: 1.0.0.$(buildNumber)-ci - name: akvPackageVersion value: 7.0.0.$(buildNumber)-ci + - name: azurePackageVersion + value: 1.0.0.$(buildNumber)-ci - name: mdsPackageVersion value: 7.0.0.$(buildNumber)-ci - name: skipComponentGovernanceDetection diff --git a/eng/pipelines/stages/build-azure-package-ci-stage.yml b/eng/pipelines/stages/build-azure-package-ci-stage.yml new file mode 100644 index 0000000000..c88a2f5db5 --- /dev/null +++ b/eng/pipelines/stages/build-azure-package-ci-stage.yml @@ -0,0 +1,162 @@ +################################################################################ +# 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. +################################################################################ + +# This stage builds the Azure package, runs tests, and publishes the resulting +# NuGet packages as pipeline artifacts. +# +# The NuGet packages have the following properties: +# +# Name: Microsoft.Data.SqlClient.Extensions.Azure +# Version: azurePackageVersion (from parameters) +# +# The following NuGet packages are published: +# +# Microsoft.Data.SqlClient.Extensions.Azure..nupkg +# Microsoft.Data.SqlClient.Extensions.Azure..snupkg (symbols) +# +# The packages are published to pipeline artifacts with the name specified by +# the azureArtifactName parameter. +# +# This template defines a stage named 'build_azure_package_stage' that +# can be depended on by downstream stages. + +parameters: + + # The name of the Abstractions pipeline artifact to download. + # + # This is used when the referenceType is 'Package'. + - name: abstractionsArtifactName + type: string + default: Abstractions.Artifact + + # The Abstractions package verion to depend on. + # + # This is used when the referenceType is 'Package'. + - name: abstractionsPackageVersion + type: string + + # The name of the pipeline artifact to publish. + - name: azureArtifactName + type: string + default: Azure.Artifact + + # The version to apply to the NuGet package and DLLs. + - name: azurePackageVersion + type: string + + # The type of build to produce (Release or Debug) + - name: buildConfiguration + type: string + default: Release + values: + - Release + - Debug + + # The stages we depend on, if any. + - name: dependsOn + type: object + default: [] + + # The reference type to use: + # + # Project - dependent projects are referenced directly. + # Package - dependent projects are referenced via NuGet packages. + # + - name: referenceType + type: string + values: + - Package + - Project + + # The dotnet CLI verbosity to use. + - name: verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + +stages: + + - stage: build_azure_package_stage + displayName: Build Azure Package + + dependsOn: ${{ parameters.dependsOn }} + + jobs: + + # ------------------------------------------------------------------------ + # Build and test on Linux. + + - template: ../jobs/test-azure-package-ci-job.yml@self + parameters: + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + buildConfiguration: ${{ parameters.buildConfiguration }} + displayNamePrefix: Linux + jobNameSuffix: linux + netFrameworkRuntimes: [] + netRuntimes: [net8.0, net9.0] + poolName: Azure Pipelines + referenceType: ${{ parameters.referenceType }} + verbosity: ${{ parameters.verbosity }} + vmImage: ubuntu-latest + + # ------------------------------------------------------------------------ + # Build and test on Windows + + - template: ../jobs/test-azure-package-ci-job.yml@self + parameters: + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + buildConfiguration: ${{ parameters.buildConfiguration }} + displayNamePrefix: Win + jobNameSuffix: windows + netFrameworkRuntimes: [net462, net47, net471, net472, net48, net481] + netRuntimes: [net8.0, net9.0] + poolName: Azure Pipelines + referenceType: ${{ parameters.referenceType }} + verbosity: ${{ parameters.verbosity }} + vmImage: windows-latest + + # ------------------------------------------------------------------------ + # Build and test on macOS. + + - template: ../jobs/test-azure-package-ci-job.yml + parameters: + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + buildConfiguration: ${{ parameters.buildConfiguration }} + displayNamePrefix: macOS + jobNameSuffix: macos + netFrameworkRuntimes: [] + netRuntimes: [net8.0, net9.0] + poolName: Azure Pipelines + referenceType: ${{ parameters.referenceType }} + verbosity: ${{ parameters.verbosity }} + vmImage: macos-latest + + # ------------------------------------------------------------------------ + # Create and publish the NuGet package. + + - template: ../jobs/pack-azure-package-ci-job.yml@self + parameters: + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + azureArtifactName: ${{ parameters.azureArtifactName }} + azurePackageVersion: ${{ parameters.abstractionsPackageVersion }} + buildConfiguration: ${{ parameters.buildConfiguration }} + dependsOn: + # We depend on all of the test jobs to ensure the tests pass before + # producing the NuGet package. + - test_azure_package_job_linux + - test_azure_package_job_windows + - test_azure_package_job_macos + referenceType: ${{ parameters.referenceType }} + verbosity: ${{ parameters.verbosity }} diff --git a/eng/pipelines/steps/compound-build-akv-step.yml b/eng/pipelines/steps/compound-build-akv-step.yml index 75e6e00e3d..393c51362f 100644 --- a/eng/pipelines/steps/compound-build-akv-step.yml +++ b/eng/pipelines/steps/compound-build-akv-step.yml @@ -18,7 +18,7 @@ parameters: - name: buildConfiguration type: string - + - name: mdsPackageVersion type: string diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 86f3529cbb..705daf092a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -12,12 +12,10 @@ Project diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index c01b28e57e..03ea8a9942 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,22 +5,33 @@ - - - + + + + + + + + - + - + @@ -30,7 +41,7 @@ - + @@ -52,7 +63,7 @@ - + @@ -67,12 +78,16 @@ + + + + + - @@ -109,16 +124,22 @@ - + - + + + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/Sample.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/Sample.xml deleted file mode 100644 index 8d5f5c44d5..0000000000 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/Sample.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - Sample class to demonstrate packaging and pipelines. - - - - Construct with a name. - The name. - - - Gets the name. - The name. - - - diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationMethod.xml similarity index 100% rename from doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml rename to src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationMethod.xml diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationParameters.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml similarity index 67% rename from doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationParameters.xml rename to src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml index 7c409cdc5c..759919e48f 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationParameters.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml @@ -6,23 +6,31 @@ - One of the enumeration values that specifies the authentication method. + + Construct with values for all properties. + + The authentication method. The server name. The database name. The resource URI. The authority URI. - The user login name/ID. - The user password. + The user login name/ID, or null if not applicable. + The user password, or null if not applicable. The connection ID. - The connection timeout value in seconds. - - Initializes a new instance of the class using the specified authentication method, server name, database name, resource URI, authority URI, user login name/ID, user password, connection ID and connection timeout value. - + The connection timeout, in seconds. Gets the authentication method. The authentication method. + + Gets the server name. + The server name. + + + Gets the database name. + The database name. + The resource URIs. The resource URI. @@ -33,27 +41,19 @@ Gets the user login name/ID. - The user login name/ID. + The user login name/ID, or null if not applicable. Gets the user password. - The user password. + The user password, or null if not applicable. Gets the connection ID. The connection ID. - - Gets the server name. - The server name. - - - Gets the database name. - The database name. - Gets the connection timeout value. - The connection timeout value to be passed to Cancellation Token Source. + The connection timeout, in seconds. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml similarity index 88% rename from doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml rename to src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml index 75cc752d76..66c7f1a5bc 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml @@ -89,48 +89,36 @@ - - - Called from constructors in derived classes to initialize the class. - - - - The authentication method. - Gets an authentication provider by method. - The authentication provider or if not found. - - - The authentication method. - The authentication provider. - Sets an authentication provider by method. - - if the operation succeeded; otherwise, (for example, the existing provider disallows overriding). - - - The authentication method. This method is called immediately before the provider is added to the SQL authentication provider registry. Avoid performing long-waiting tasks in this method, since it can block other threads from accessing the provider registry. + + This method must not throw. + The authentication method. - The authentication method. This method is called immediately before the provider is removed from the SQL authentication provider registry. For example, this method is called when a different provider with the same authentication method overrides this provider in the SQL authentication provider registry. Avoid performing long-waiting task in this method, since it can block other threads from accessing the provider registry. + + This method must not throw. + The authentication method. - The authentication method. Indicates whether the specified authentication method is supported. + This method must not throw. if the specified authentication method is supported; otherwise, . + The authentication method. - The Active Directory authentication parameters passed by the driver to authentication providers. Acquires a security token from the authority. + The Active Directory authentication parameters passed by the driver to authentication providers. + If any errors occur. Represents an asynchronous operation that returns the AD authentication token. diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProviderException.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProviderException.xml new file mode 100644 index 0000000000..dbdf2966f9 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProviderException.xml @@ -0,0 +1,77 @@ + + + + + This exception is thrown for any errors that occur during the + authentication process. + + + + + Protected construction for derived classes to supply a minimal set of + values. + + Method will be NotSpecified. + FailureCode will be "Unknown". + ShouldRetry will be false. + RetryPeriod will be 0. + + The error message. + The exception that caused this exception, if any. + + + + Protected construction for derived classes to supply values for all + public properties. + + + The authentication method that failed, or NotSpecified if not known. + + + The failure code, or "Unknown" if not known. + + + True if the action should be retried, false otherwise. + + + The period of time, in milliseconds, to wait before retrying the action. + Specify 0 if no retry period is suggested. Ignored if negative. Not + used when ShouldRetry is false, in which cases 0 is assumed. + + + The error message. + + + The exception that caused this exception, if any. + + + + + The authentication method that failed, or NotSpecified if not known. + + + + + The failure code, or "Unknown" if not known. + + + + + True if the action should be retried, false otherwise. + + + + + The period of time, in milliseconds, to wait before retrying the action. + 0 if no retry period is suggested. Never negative. Always 0 when + ShouldRetry is false. + + + + + A string that includes the base exception information along with all + property values. + + + + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationToken.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationToken.xml similarity index 63% rename from doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationToken.xml rename to src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationToken.xml index 52f79dd535..11ce14db28 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationToken.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationToken.xml @@ -4,22 +4,22 @@ Represents an authentication token. - The access token. - The token expiration time. - Initializes a new instance of the class. + Construct with values for all properties. - - The parameter is or empty. - + The token string. + The token expiration time. + + is null or empty. + - - Gets the token expiration time. - The token expiration time. - Gets the token string. The token string. + + Gets the token expiration time. + The token expiration time. + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj index 485a3de83a..802a80068e 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj @@ -23,17 +23,19 @@ Microsoft.Data.SqlClient.Extensions.Abstractions - Microsoft.Data.SqlClient.Extensions.Abstractions + $(AssemblyName) - $(_DefaultMajorVersion).0.0.0 + $(AbstractionsDefaultMajorVersion).0.0.0 $(AbstractionsAssemblyFileVersion) $(AbstractionsAssemblyFileVersion) $(AbstractionsPackageVersion) + + $(Artifacts)/doc/$(TargetFramework)/$(AssemblyName).xml @@ -42,7 +44,7 @@ - + - <_DefaultMajorVersion>1 + 1 <_OurPackageVersion Condition="'$(AbstractionsPackageVersion)' != ''">$(AbstractionsPackageVersion) - <_OurPackageVersion Condition="'$(AbstractionsPackageVersion)' == ''">$(_DefaultMajorVersion).0.0.$(BuildNumber)-dev + <_OurPackageVersion Condition="'$(AbstractionsPackageVersion)' == ''">$(AbstractionsDefaultMajorVersion).0.0.$(BuildNumber)-dev @@ -56,7 +56,7 @@ <_OurAssemblyFileVersion Condition="'$(AbstractionsAssemblyFileVersion)' == '' and '$(AbstractionsPackageVersion)' != ''">$(AbstractionsPackageVersion.Split('-')[0]) - <_OurAssemblyFileVersion Condition="'$(AbstractionsAssemblyFileVersion)' == '' and '$(AbstractionsPackageVersion)' == ''">$(_DefaultMajorVersion).0.0.$(BuildNumber) + <_OurAssemblyFileVersion Condition="'$(AbstractionsAssemblyFileVersion)' == '' and '$(AbstractionsPackageVersion)' == ''">$(AbstractionsDefaultMajorVersion).0.0.$(BuildNumber) + + + + netstandard2.0 + + + + + enable + enable + + + + + Microsoft.Data.SqlClient.Extensions.Azure + $(AssemblyName) + + + $(AzureDefaultMajorVersion).0.0.0 + + $(AzureAssemblyFileVersion) + $(AzureAssemblyFileVersion) + $(AzurePackageVersion) + + $(Artifacts)/doc/$(TargetFramework)/$(AssemblyName).xml + + + + + <_Parameter1>true + + + + + + + + + $(AssemblyName) + $(AbstractionsPackageVersion) + $(PackagesDir) + true + snupkg + + Microsoft Corporation + Microsoft Corporation + Microsoft.Data.SqlClient Extensions Azure + https://github.com/dotnet/SqlClient + MIT + dotnet.png + + + + + + + + + + + + + + + + + + + + + + false + + + $(AssemblyName) + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props new file mode 100644 index 0000000000..8c9d16969e --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AzureVersions.props @@ -0,0 +1,69 @@ + + + + + + + + + + + + + 1 + + + <_OurPackageVersion Condition="'$(AzurePackageVersion)' != ''">$(AzurePackageVersion) + <_OurPackageVersion Condition="'$(AzurePackageVersion)' == ''">$(AzureDefaultMajorVersion).0.0.$(BuildNumber)-dev + + + + <_OurAssemblyFileVersion Condition="'$(AzureAssemblyFileVersion)' != ''">$(AzureAssemblyFileVersion) + + <_OurAssemblyFileVersion Condition="'$(AzureAssemblyFileVersion)' == '' and '$(AzurePackageVersion)' != ''">$(AzurePackageVersion.Split('-')[0]) + + <_OurAssemblyFileVersion Condition="'$(AzureAssemblyFileVersion)' == '' and '$(AzurePackageVersion)' == ''">$(AzureDefaultMajorVersion).0.0.$(BuildNumber) + + + $(_OurPackageVersion) + $(_OurAssemblyFileVersion) + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj new file mode 100644 index 0000000000..f391b7393c --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj @@ -0,0 +1,26 @@ + + + + net462;net47;net471;net472;net48;net481;net8.0;net9.0 + enable + enable + false + true + Microsoft.Data.SqlClient.Extensions.Azure.Test + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index d7a2f70689..d5efe9e4b9 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -327,6 +327,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{59667E4C-0 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abstractions.Test", "Microsoft.Data.SqlClient.Extensions\Abstractions\test\Abstractions.Test.csproj", "{04ACBF75-CFF2-41AB-B652-776BC0533490}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Azure", "Azure", "{A20114E1-82D8-903A-C389-726EB4FD943F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0D2F834B-6D91-18D0-3F09-672D448751BD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "Microsoft.Data.SqlClient.Extensions\Azure\src\Azure.csproj", "{20C16035-7293-45AC-8217-9B86A389E571}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5AF52CDD-DF78-3712-7516-5B49F94F9491}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Test", "Microsoft.Data.SqlClient.Extensions\Azure\test\Azure.Test.csproj", "{A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -679,6 +689,30 @@ Global {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x64.Build.0 = Release|Any CPU {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x86.ActiveCfg = Release|Any CPU {04ACBF75-CFF2-41AB-B652-776BC0533490}.Release|x86.Build.0 = Release|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Debug|x64.ActiveCfg = Debug|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Debug|x64.Build.0 = Debug|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Debug|x86.ActiveCfg = Debug|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Debug|x86.Build.0 = Debug|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Release|Any CPU.Build.0 = Release|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Release|x64.ActiveCfg = Release|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Release|x64.Build.0 = Release|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Release|x86.ActiveCfg = Release|Any CPU + {20C16035-7293-45AC-8217-9B86A389E571}.Release|x86.Build.0 = Release|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Debug|x64.ActiveCfg = Debug|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Debug|x64.Build.0 = Debug|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Debug|x86.ActiveCfg = Debug|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Debug|x86.Build.0 = Debug|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Release|Any CPU.Build.0 = Release|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Release|x64.ActiveCfg = Release|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Release|x64.Build.0 = Release|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Release|x86.ActiveCfg = Release|Any CPU + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -740,6 +774,11 @@ Global {B21E7C94-D805-427E-928A-8DE8EA2F08CC} = {210228A5-979A-DE06-EE1F-B35C65E1583C} {59667E4C-0BD2-9F48-FB50-9E55DD8B1011} = {556B486E-F9B0-7EA9-6A25-DA560C312761} {04ACBF75-CFF2-41AB-B652-776BC0533490} = {59667E4C-0BD2-9F48-FB50-9E55DD8B1011} + {A20114E1-82D8-903A-C389-726EB4FD943F} = {19F1F1E5-3013-7660-661A-2A15F7D606C1} + {0D2F834B-6D91-18D0-3F09-672D448751BD} = {A20114E1-82D8-903A-C389-726EB4FD943F} + {20C16035-7293-45AC-8217-9B86A389E571} = {0D2F834B-6D91-18D0-3F09-672D448751BD} + {5AF52CDD-DF78-3712-7516-5B49F94F9491} = {A20114E1-82D8-903A-C389-726EB4FD943F} + {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC} = {5AF52CDD-DF78-3712-7516-5B49F94F9491} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {01D48116-37A2-4D33-B9EC-94793C702431} diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj index 51af5632e3..b3e100b36c 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj @@ -33,15 +33,22 @@ - - - - + + + + + + + + + + + - + diff --git a/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props b/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props index dfeb60b38c..d5bd53f012 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props +++ b/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props @@ -8,7 +8,6 @@ $(OS) true true - Project true $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFramework)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) diff --git a/src/Microsoft.Data.SqlClient/add-ons/Directory.Packages.props b/src/Microsoft.Data.SqlClient/add-ons/Directory.Packages.props deleted file mode 100644 index 4a24bb25ae..0000000000 --- a/src/Microsoft.Data.SqlClient/add-ons/Directory.Packages.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index 0a04d047f8..0e5b501d41 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -142,30 +142,6 @@ public SqlVector(System.ReadOnlyMemory memory) { } } namespace Microsoft.Data.SqlClient { - /// - public sealed partial class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider - { - /// - public ActiveDirectoryAuthenticationProvider() { } - /// - public ActiveDirectoryAuthenticationProvider(string applicationClientId) { } - /// - public static void ClearUserTokenCache() { } - /// - public ActiveDirectoryAuthenticationProvider(System.Func deviceCodeFlowCallbackMethod, string applicationClientId = null) { } - /// - public override System.Threading.Tasks.Task AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; } - /// - public void SetDeviceCodeFlowCallback(System.Func deviceCodeFlowCallbackMethod) { } - /// - public void SetAcquireAuthorizationCodeAsyncCallback(System.Func> acquireAuthorizationCodeAsyncCallback) { } - /// - public override bool IsSupported(SqlAuthenticationMethod authentication) { throw null; } - /// - public override void BeforeLoad(SqlAuthenticationMethod authentication) { } - /// - public override void BeforeUnload(SqlAuthenticationMethod authentication) { } - } /// public enum ApplicationIntent { @@ -204,85 +180,14 @@ protected SqlAuthenticationInitializer() { } /// public abstract void Initialize(); } - /// - public enum SqlAuthenticationMethod + /// + public sealed partial class SqlAuthenticationProviderManager { - /// - NotSpecified = 0, - /// - SqlPassword = 1, - /// - [System.Obsolete("ActiveDirectoryPassword is deprecated, use a more secure authentication method. See https://aka.ms/SqlClientEntraIDAuthentication for more details.")] - ActiveDirectoryPassword = 2, - /// - ActiveDirectoryIntegrated = 3, - /// - ActiveDirectoryInteractive = 4, - /// - ActiveDirectoryServicePrincipal = 5, - /// - ActiveDirectoryDeviceCodeFlow = 6, - /// - ActiveDirectoryManagedIdentity = 7, - /// - ActiveDirectoryMSI = 8, - /// - ActiveDirectoryDefault = 9, - /// - ActiveDirectoryWorkloadIdentity = 10 - } - /// - public class SqlAuthenticationParameters - { - /// - protected SqlAuthenticationParameters(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod, string serverName, string databaseName, string resource, string authority, string userId, string password, System.Guid connectionId, int connectionTimeout) { } - /// - public Microsoft.Data.SqlClient.SqlAuthenticationMethod AuthenticationMethod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string Authority { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public System.Guid ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string DatabaseName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string Password { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string ServerName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string UserId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public int ConnectionTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - } - /// - public abstract partial class SqlAuthenticationProvider - { - /// - protected SqlAuthenticationProvider() { } - /// - public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters); - /// - public virtual void BeforeLoad(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } - /// - public virtual void BeforeUnload(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } - /// + /// public static Microsoft.Data.SqlClient.SqlAuthenticationProvider GetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { throw null; } - /// - public abstract bool IsSupported(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod); - /// + /// public static bool SetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod, Microsoft.Data.SqlClient.SqlAuthenticationProvider provider) { throw null; } } - /// - public partial class SqlAuthenticationToken - { - /// - public SqlAuthenticationToken(string accessToken, System.DateTimeOffset expiresOn) { } - /// - public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public System.DateTimeOffset ExpiresOn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - } /// public sealed partial class SqlBulkCopy : System.IDisposable { diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index 4b658f409a..7632098f89 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -32,8 +32,6 @@ - - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index fff624a59a..64580fa70e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -177,9 +177,6 @@ Microsoft\Data\SqlClient\AAsyncCallContext.cs - - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationProvider.cs - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationTimeoutRetryHelper.cs @@ -489,18 +486,12 @@ Microsoft\Data\SqlClient\SqlAppContextSwitchManager.netcore.cs - - Microsoft\Data\SqlClient\SqlAuthenticationParameters.cs - - - Microsoft\Data\SqlClient\SqlAuthenticationProvider.cs + + Microsoft\Data\SqlClient\SqlAuthenticationParametersBuilder.cs Microsoft\Data\SqlClient\SqlAuthenticationProviderManager.cs - - Microsoft\Data\SqlClient\SqlAuthenticationToken.cs - Microsoft\Data\SqlClient\SqlBatch.cs @@ -1068,8 +1059,6 @@ - - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 06736ab693..983f36693c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -18,7 +18,6 @@ using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; -using Microsoft.Identity.Client; namespace Microsoft.Data.SqlClient { @@ -106,8 +105,6 @@ public void AssertUnrecoverableStateCountIsCorrect() internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposable { - // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 - internal const int MsalHttpRetryStatusCode = 429; // Connection re-route limit internal const int MaxNumberOfRedirectRoute = 10; @@ -134,7 +131,8 @@ internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposa internal bool _federatedAuthenticationInfoReceived; // The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken. - SqlFedAuthToken _fedAuthToken = null; + private SqlFedAuthToken _fedAuthToken = null; + internal byte[] _accessTokenInBytes; internal readonly Func> _accessTokenCallback; internal readonly SspiContextProvider _sspiContextProvider; @@ -232,17 +230,6 @@ internal bool IsDNSCachingBeforeRedirectSupported private DbConnectionPoolAuthenticationContextKey _dbConnectionPoolAuthenticationContextKey; #if DEBUG - // This is a test hook to enable testing of the retry paths for MSAL get access token. - // Sample code to enable: - // - // Type type = typeof(SqlConnection).Assembly.GetType("Microsoft.Data.SqlClient.SQLInternalConnectionTds"); - // System.Reflection.FieldInfo field = type.GetField("_forceMsalRetry", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - // if (field != null) { - // field.SetValue(null, true); - // } - // - internal static bool _forceMsalRetry = false; - // This is a test hook to simulate a token expiring within the next 45 minutes. private static bool _forceExpiryLocked = false; @@ -763,7 +750,7 @@ protected override bool UnbindOnTransactionCompletion /// /// Validates if federated authentication is used, Access Token used by this connection is active for the value of 'accessTokenExpirationBufferTime'. /// - internal override bool IsAccessTokenExpired => _federatedAuthenticationInfoRequested && DateTime.FromFileTimeUtc(_fedAuthToken.expirationFileTime) < DateTime.UtcNow.AddSeconds(accessTokenExpirationBufferTime); + internal override bool IsAccessTokenExpired => _federatedAuthenticationInfoRequested && DateTime.FromFileTimeUtc(_fedAuthToken.ExpirationFileTime) < DateTime.UtcNow.AddSeconds(accessTokenExpirationBufferTime); //////////////////////////////////////////////////////////////////////////////////////// // GENERAL METHODS @@ -2338,7 +2325,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) Debug.Assert(_dbConnectionPool.AuthenticationContexts != null); // Construct the dbAuthenticationContextKey with information from FedAuthInfo and store for later use, when inserting in to the token cache. - _dbConnectionPoolAuthenticationContextKey = new DbConnectionPoolAuthenticationContextKey(fedAuthInfo.stsurl, fedAuthInfo.spn); + _dbConnectionPoolAuthenticationContextKey = new DbConnectionPoolAuthenticationContextKey(fedAuthInfo.StsUrl, fedAuthInfo.Spn); // Try to retrieve the authentication context from the pool, if one does exist for this key. if (_dbConnectionPool.AuthenticationContexts.TryGetValue(_dbConnectionPoolAuthenticationContextKey, out dbConnectionPoolAuthenticationContext)) @@ -2427,15 +2414,14 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) Debug.Assert(_fedAuthToken == null, "_fedAuthToken should be null in this case."); Debug.Assert(_newDbConnectionPoolAuthenticationContext == null, "_newDbConnectionPoolAuthenticationContext should be null."); - _fedAuthToken = new SqlFedAuthToken(); - // If the code flow is here, then we are re-using the context from the cache for this connection attempt and not // generating a new access token on this thread. - _fedAuthToken.accessToken = dbConnectionPoolAuthenticationContext.AccessToken; - _fedAuthToken.expirationFileTime = dbConnectionPoolAuthenticationContext.ExpirationTime.ToFileTime(); + _fedAuthToken = new( + dbConnectionPoolAuthenticationContext.AccessToken, + dbConnectionPoolAuthenticationContext.ExpirationTime.ToFileTime()); } - Debug.Assert(_fedAuthToken != null && _fedAuthToken.accessToken != null, "_fedAuthToken and _fedAuthToken.accessToken cannot be null."); + Debug.Assert(_fedAuthToken != null && _fedAuthToken.AccessToken != null, "_fedAuthToken and _fedAuthToken.AccessToken cannot be null."); _parser.SendFedAuthToken(_fedAuthToken); } @@ -2495,6 +2481,8 @@ internal bool TryGetFedAuthTokenLocked(SqlFedAuthInfo fedAuthInfo, DbConnectionP return authenticationContextLocked; } + #nullable enable + /// /// Get the Federated Authentication Token. /// @@ -2502,37 +2490,32 @@ internal bool TryGetFedAuthTokenLocked(SqlFedAuthInfo fedAuthInfo, DbConnectionP /// SqlFedAuthToken internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { - Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null."); - - // No:of milliseconds to sleep for the inital back off. - int sleepInterval = 100; - - // No:of attempts, for tracing purposes, if we underwent retries. - int numberOfAttempts = 0; + // Number of milliseconds to sleep for the initial back off, if a + // retry period is not specified by the provider. + const int defaultRetryPeriod = 100; - // Object that will be returned to the caller, containing all required data about the token. - _fedAuthToken = new SqlFedAuthToken(); + // Number of retry attempts we are willing to perform. + const int maxRetryAttempts = 1; // Username to use in error messages. - string username = null; + string? username = null; - SqlAuthenticationProvider authProvider = SqlAuthenticationProvider.GetProvider(ConnectionOptions.Authentication); + SqlAuthenticationProvider? authProvider = SqlAuthenticationProviderManager.GetProvider(ConnectionOptions.Authentication); if (authProvider == null && _accessTokenCallback == null) { throw SQL.CannotFindAuthProvider(ConnectionOptions.Authentication.ToString()); } - // retry getting access token once if MsalException.error_code is unknown_error. - // extra logic to deal with HTTP 429 (Retry after). - while (numberOfAttempts <= 1) + // We will perform retries if the provider indicates an error that + // is retryable. + for (int retryAttempt = 0; retryAttempt <= maxRetryAttempts; ++retryAttempt) { - numberOfAttempts++; try { - var authParamsBuilder = new SqlAuthenticationParameters.Builder( + var authParamsBuilder = new SqlAuthenticationParametersBuilder( authenticationMethod: ConnectionOptions.Authentication, - resource: fedAuthInfo.spn, - authority: fedAuthInfo.stsurl, + resource: fedAuthInfo.Spn, + authority: fedAuthInfo.StsUrl, serverName: ConnectionOptions.DataSource, databaseName: ConnectionOptions.InitialCatalog) .WithConnectionId(_clientConnectionId) @@ -2560,7 +2543,8 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { // We use Task.Run here in all places to execute task synchronously in the same context. // Fixes block-over-async deadlock possibilities https://github.com/dotnet/SqlClient/issues/1209 - _fedAuthToken = Task.Run(async () => await authProvider.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult().ToSqlFedAuthToken(); + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); + _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } break; @@ -2577,7 +2561,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) else { authParamsBuilder.WithUserId(ConnectionOptions.UserID); - _fedAuthToken = Task.Run(async () => await authProvider.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult().ToSqlFedAuthToken(); + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } break; @@ -2595,13 +2579,13 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { username = _credential.UserId; authParamsBuilder.WithUserId(username).WithPassword(_credential.Password); - _fedAuthToken = Task.Run(async () => await authProvider.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult().ToSqlFedAuthToken(); + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); } else { username = ConnectionOptions.UserID; authParamsBuilder.WithUserId(username).WithPassword(ConnectionOptions.Password); - _fedAuthToken = Task.Run(async () => await authProvider.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult().ToSqlFedAuthToken(); + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); } _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } @@ -2635,105 +2619,80 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { cts.CancelAfter((int)_timeout.MillisecondsRemaining); } - _fedAuthToken = Task.Run(async () => await _accessTokenCallback(parameters, cts.Token)).GetAwaiter().GetResult().ToSqlFedAuthToken(); + _fedAuthToken = new(Task.Run(async () => await _accessTokenCallback(parameters, cts.Token)).GetAwaiter().GetResult()); _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } break; } - Debug.Assert(_fedAuthToken.accessToken != null, "AccessToken should not be null."); -#if DEBUG - if (_forceMsalRetry) - { - // 3399614468 is 0xCAA20004L just for testing. - throw new MsalServiceException(MsalError.UnknownError, "Force retry in GetFedAuthToken"); - } -#endif + Debug.Assert(_fedAuthToken.AccessToken != null, "AccessToken should not be null."); + // Break out of the retry loop in successful case. break; } - // Deal with Msal service exceptions first, retry if 429 received. - catch (MsalServiceException serviceException) + catch (SqlAuthenticationProviderException ex) { - if (serviceException.StatusCode == MsalHttpRetryStatusCode) + // Is the error fatal or retryable? + if (!ex.ShouldRetry) { - RetryConditionHeaderValue retryAfter = serviceException.Headers.RetryAfter; - if (retryAfter.Delta.HasValue) - { - sleepInterval = retryAfter.Delta.Value.Milliseconds; - } - else if (retryAfter.Date.HasValue) - { - sleepInterval = Convert.ToInt32(retryAfter.Date.Value.Offset.TotalMilliseconds); - } - - // if there's enough time to retry before timeout, then retry, otherwise break out the retry loop. - if (sleepInterval < _timeout.MillisecondsRemaining) - { - Thread.Sleep(sleepInterval); - } - else - { - SqlClientEventSource.Log.TryTraceEvent(" Timeout: {0}", serviceException.ErrorCode); - throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), serviceException.ErrorCode, serviceException); - } + // It's fatal, so translate into a SqlException. + throw ADP.CreateSqlException(ex, ConnectionOptions, this, username); } - else + + // We should retry. + + // Could we retry if we wanted to? + if (_timeout.IsExpired || _timeout.MillisecondsRemaining <= 0) { - SqlClientEventSource.Log.TryTraceEvent(" {0}", serviceException.ErrorCode); - throw ADP.CreateSqlException(serviceException, ConnectionOptions, this, username); + // No, so we throw. + SqlClientEventSource.Log.TryTraceEvent(" Retry Attempt: {0}, Timeout: {1}", retryAttempt, ex.FailureCode); + throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), ex.FailureCode, ex); } - } - // Deal with normal MsalExceptions. - catch (MsalException msalException) - { - if (MsalError.UnknownError != msalException.ErrorCode || _timeout.IsExpired || _timeout.MillisecondsRemaining <= sleepInterval) - { - SqlClientEventSource.Log.TryTraceEvent(" {0}", msalException.ErrorCode); - throw ADP.CreateSqlException(msalException, ConnectionOptions, this, username); - } + // We use a doubling backoff if the provider didn't provide + // a retry period. + int retryPeriod = + ex.RetryPeriod > 0 + ? ex.RetryPeriod + : defaultRetryPeriod * (2 ^ retryAttempt); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, sleeping {1}[Milliseconds]", ObjectID, sleepInterval); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, remaining {1}[Milliseconds]", ObjectID, _timeout.MillisecondsRemaining); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Retry Attempt: {1}, sleeping {2}[Milliseconds]", ObjectID, retryAttempt, retryPeriod); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Retry Attempt: {1}, remaining {2}[Milliseconds]", ObjectID, retryAttempt, _timeout.MillisecondsRemaining); - Thread.Sleep(sleepInterval); - sleepInterval *= 2; - } - // All other exceptions from MSAL/Azure Identity APIs - catch (Exception e) - { - throw SqlException.CreateException( - new() - { - new( - 0, - (byte)0x00, - (byte)TdsEnums.FATAL_ERROR_CLASS, - ConnectionOptions.DataSource, - e.Message, - ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, - 0) - }, - "", - this, - e); + // Sleep for the desired period. + Thread.Sleep(retryPeriod); + + // Fall through to retry... } } - Debug.Assert(_fedAuthToken != null, "fedAuthToken should not be null."); - Debug.Assert(_fedAuthToken.accessToken != null && _fedAuthToken.accessToken.Length > 0, "fedAuthToken.accessToken should not be null or empty."); + // Nullable context has exposed that _fedAuthToken may be null here, + // which the existing code didn't handle. + if (_fedAuthToken is null) + { + // We failed to acquire a token, so use a default one. + // + // TODO: The old code actually allowed the AccessToken byte + // array to be null, and then had Debug.Assert()s to verify it + // wasn't null. We never test in debug, so those were never + // firing, and we were happily using a _fedAuthToken with a null + // AccessToken. Now that SqlFedAuthToken doesn't allow a null + // AccessToken, we just create an empty one instead. + _fedAuthToken = new SqlFedAuthToken([], 0); + } // Store the newly generated token in _newDbConnectionPoolAuthenticationContext, only if using pooling. if (_dbConnectionPool != null) { - DateTime expirationTime = DateTime.FromFileTimeUtc(_fedAuthToken.expirationFileTime); - _newDbConnectionPoolAuthenticationContext = new DbConnectionPoolAuthenticationContext(_fedAuthToken.accessToken, expirationTime); + DateTime expirationTime = DateTime.FromFileTimeUtc(_fedAuthToken.ExpirationFileTime); + _newDbConnectionPoolAuthenticationContext = new DbConnectionPoolAuthenticationContext(_fedAuthToken.AccessToken, expirationTime); } SqlClientEventSource.Log.TryTraceEvent(" {0}, Finished generating federated authentication token.", ObjectID); return _fedAuthToken; } + #nullable disable + internal void OnFeatureExtAck(int featureId, byte[] data) { if (RoutingInfo != null && featureId != TdsEnums.FEATUREEXT_SQLDNSCACHING) diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index e662fc0560..a929c01bd4 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -57,32 +57,6 @@ public sealed class SqlDataSourceEnumerator : System.Data.Common.DbDataSourceEnu namespace Microsoft.Data.SqlClient { - /// - public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider - { - /// - public ActiveDirectoryAuthenticationProvider() { } - /// - public ActiveDirectoryAuthenticationProvider(string applicationClientId) { } - /// - public static void ClearUserTokenCache() { } - /// - public ActiveDirectoryAuthenticationProvider(System.Func deviceCodeFlowCallbackMethod, string applicationClientId = null) { } - /// - public override System.Threading.Tasks.Task AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; } - /// - public void SetDeviceCodeFlowCallback(System.Func deviceCodeFlowCallbackMethod) { } - /// - public void SetAcquireAuthorizationCodeAsyncCallback(System.Func> acquireAuthorizationCodeAsyncCallback) { } - /// - public void SetIWin32WindowFunc(System.Func iWin32WindowFunc) { } - /// - public override bool IsSupported(SqlAuthenticationMethod authentication) { throw null; } - /// - public override void BeforeLoad(SqlAuthenticationMethod authentication) { } - /// - public override void BeforeUnload(SqlAuthenticationMethod authentication) { } - } /// public enum ApplicationIntent { @@ -122,84 +96,13 @@ protected SqlAuthenticationInitializer() { } /// public abstract void Initialize(); } - /// - public enum SqlAuthenticationMethod + /// + public sealed partial class SqlAuthenticationProviderManager { - /// - NotSpecified = 0, - /// - SqlPassword = 1, - /// - [System.ObsoleteAttribute("ActiveDirectoryPassword is deprecated, use a more secure authentication method. See https://aka.ms/SqlClientEntraIDAuthentication for more details.")] - ActiveDirectoryPassword = 2, - /// - ActiveDirectoryIntegrated = 3, - /// - ActiveDirectoryInteractive = 4, - /// - ActiveDirectoryServicePrincipal = 5, - /// - ActiveDirectoryDeviceCodeFlow = 6, - /// - ActiveDirectoryManagedIdentity = 7, - /// - ActiveDirectoryMSI = 8, - /// - ActiveDirectoryDefault = 9, - /// - ActiveDirectoryWorkloadIdentity = 10 - } - /// - public class SqlAuthenticationParameters - { - /// - protected SqlAuthenticationParameters(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod, string serverName, string databaseName, string resource, string authority, string userId, string password, System.Guid connectionId, int connectionTimeout) { } - /// - public Microsoft.Data.SqlClient.SqlAuthenticationMethod AuthenticationMethod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string Authority { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public System.Guid ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string DatabaseName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string Password { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string ServerName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string UserId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public int ConnectionTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - } - /// - public abstract partial class SqlAuthenticationProvider - { - /// - protected SqlAuthenticationProvider() { } - /// - public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters); - /// - public virtual void BeforeLoad(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } - /// - public virtual void BeforeUnload(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } - /// + /// public static Microsoft.Data.SqlClient.SqlAuthenticationProvider GetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { throw null; } - /// - public abstract bool IsSupported(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod); - /// - public static bool SetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod, Microsoft.Data.SqlClient.SqlAuthenticationProvider provider) { throw null; } - } - /// - public partial class SqlAuthenticationToken - { - /// - public SqlAuthenticationToken(string accessToken, System.DateTimeOffset expiresOn) { } - /// - public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public System.DateTimeOffset ExpiresOn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + /// + public static bool SetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod, Microsoft.Data.SqlClient.SqlAuthenticationProvider provider) { throw null; } } /// public sealed partial class SqlBulkCopy : System.IDisposable diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index f73b8682a0..b0bb14a9b7 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -32,8 +32,6 @@ - - All diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 3729377928..78b39dbe6b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -459,9 +459,6 @@ Microsoft\Data\SqlClient\AssemblyRef.cs - - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationProvider.cs - Microsoft\Data\SqlClient\AlwaysEncryptedEnclaveProviderUtils.cs @@ -660,11 +657,8 @@ Microsoft\Data\SqlClient\SqlAeadAes256CbcHmac256Factory.cs - - Microsoft\Data\SqlClient\SqlAuthenticationParameters.cs - - - Microsoft\Data\SqlClient\SqlAuthenticationProvider.cs + + Microsoft\Data\SqlClient\SqlAuthenticationParametersBuilder.cs Microsoft\Data\SqlClient\SqlAuthenticationProviderManager.cs @@ -675,9 +669,6 @@ Microsoft\Data\SqlClient\SqlBulkCopy.cs - - Microsoft\Data\SqlClient\SqlAuthenticationToken.cs - Microsoft\Data\SqlClient\SqlBatchCommand.cs @@ -1041,8 +1032,6 @@ - - All diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 9f4a612951..bc6e9c13de 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -19,7 +19,6 @@ using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; -using Microsoft.Identity.Client; namespace Microsoft.Data.SqlClient { @@ -107,8 +106,6 @@ public void AssertUnrecoverableStateCountIsCorrect() internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposable { - // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 - internal const int MsalHttpRetryStatusCode = 429; // Connection re-route limit internal const int MaxNumberOfRedirectRoute = 10; @@ -233,17 +230,6 @@ internal bool IsDNSCachingBeforeRedirectSupported private DbConnectionPoolAuthenticationContextKey _dbConnectionPoolAuthenticationContextKey; #if DEBUG - // This is a test hook to enable testing of the retry paths for MSAL get access token. - // Sample code to enable: - // - // Type type = typeof(SqlConnection).Assembly.GetType("Microsoft.Data.SqlClient.SQLInternalConnectionTds"); - // System.Reflection.FieldInfo field = type.GetField("_forceMsalRetry", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - // if (field != null) { - // field.SetValue(null, true); - // } - // - internal static bool _forceMsalRetry = false; - // This is a test hook to simulate a token expiring within the next 45 minutes. private static bool _forceExpiryLocked = false; @@ -773,7 +759,7 @@ protected override bool UnbindOnTransactionCompletion /// /// Validates if federated authentication is used, Access Token used by this connection is active for the value of 'accessTokenExpirationBufferTime'. /// - internal override bool IsAccessTokenExpired => _federatedAuthenticationInfoRequested && DateTime.FromFileTimeUtc(_fedAuthToken.expirationFileTime) < DateTime.UtcNow.AddSeconds(accessTokenExpirationBufferTime); + internal override bool IsAccessTokenExpired => _federatedAuthenticationInfoRequested && DateTime.FromFileTimeUtc(_fedAuthToken.ExpirationFileTime) < DateTime.UtcNow.AddSeconds(accessTokenExpirationBufferTime); //////////////////////////////////////////////////////////////////////////////////////// // GENERAL METHODS @@ -2394,7 +2380,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) Debug.Assert(_dbConnectionPool.AuthenticationContexts != null); // Construct the dbAuthenticationContextKey with information from FedAuthInfo and store for later use, when inserting in to the token cache. - _dbConnectionPoolAuthenticationContextKey = new DbConnectionPoolAuthenticationContextKey(fedAuthInfo.stsurl, fedAuthInfo.spn); + _dbConnectionPoolAuthenticationContextKey = new DbConnectionPoolAuthenticationContextKey(fedAuthInfo.StsUrl, fedAuthInfo.Spn); // Try to retrieve the authentication context from the pool, if one does exist for this key. if (_dbConnectionPool.AuthenticationContexts.TryGetValue(_dbConnectionPoolAuthenticationContextKey, out dbConnectionPoolAuthenticationContext)) @@ -2483,15 +2469,14 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) Debug.Assert(_fedAuthToken == null, "_fedAuthToken should be null in this case."); Debug.Assert(_newDbConnectionPoolAuthenticationContext == null, "_newDbConnectionPoolAuthenticationContext should be null."); - _fedAuthToken = new SqlFedAuthToken(); - // If the code flow is here, then we are re-using the context from the cache for this connection attempt and not // generating a new access token on this thread. - _fedAuthToken.accessToken = dbConnectionPoolAuthenticationContext.AccessToken; - _fedAuthToken.expirationFileTime = dbConnectionPoolAuthenticationContext.ExpirationTime.ToFileTime(); + _fedAuthToken = new( + dbConnectionPoolAuthenticationContext.AccessToken, + dbConnectionPoolAuthenticationContext.ExpirationTime.ToFileTime()); } - Debug.Assert(_fedAuthToken != null && _fedAuthToken.accessToken != null, "_fedAuthToken and _fedAuthToken.accessToken cannot be null."); + Debug.Assert(_fedAuthToken != null && _fedAuthToken.AccessToken != null, "_fedAuthToken and _fedAuthToken.AccessToken cannot be null."); _parser.SendFedAuthToken(_fedAuthToken); } @@ -2551,6 +2536,8 @@ internal bool TryGetFedAuthTokenLocked(SqlFedAuthInfo fedAuthInfo, DbConnectionP return authenticationContextLocked; } + #nullable enable + /// /// Get the Federated Authentication Token. /// @@ -2558,37 +2545,32 @@ internal bool TryGetFedAuthTokenLocked(SqlFedAuthInfo fedAuthInfo, DbConnectionP /// SqlFedAuthToken internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { - Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null."); - - // No:of milliseconds to sleep for the inital back off. - int sleepInterval = 100; - - // No:of attempts, for tracing purposes, if we underwent retries. - int numberOfAttempts = 0; + // Number of milliseconds to sleep for the initial back off, if a + // retry period is not specified by the provider. + const int defaultRetryPeriod = 100; - // Object that will be returned to the caller, containing all required data about the token. - _fedAuthToken = new SqlFedAuthToken(); + // Number of retry attempts we are willing to perform. + const int maxRetryAttempts = 1; // Username to use in error messages. - string username = null; + string? username = null; - SqlAuthenticationProvider authProvider = SqlAuthenticationProvider.GetProvider(ConnectionOptions.Authentication); + SqlAuthenticationProvider? authProvider = SqlAuthenticationProviderManager.GetProvider(ConnectionOptions.Authentication); if (authProvider == null && _accessTokenCallback == null) { throw SQL.CannotFindAuthProvider(ConnectionOptions.Authentication.ToString()); } - // retry getting access token once if MsalException.error_code is unknown_error. - // extra logic to deal with HTTP 429 (Retry after). - while (numberOfAttempts <= 1 && sleepInterval <= _timeout.MillisecondsRemaining) + // We will perform retries if the provider indicates an error that + // is retryable. + for (int retryAttempt = 0; retryAttempt < maxRetryAttempts; ++retryAttempt) { - numberOfAttempts++; try { - var authParamsBuilder = new SqlAuthenticationParameters.Builder( + var authParamsBuilder = new SqlAuthenticationParametersBuilder( authenticationMethod: ConnectionOptions.Authentication, - resource: fedAuthInfo.spn, - authority: fedAuthInfo.stsurl, + resource: fedAuthInfo.Spn, + authority: fedAuthInfo.StsUrl, serverName: ConnectionOptions.DataSource, databaseName: ConnectionOptions.InitialCatalog) .WithConnectionId(_clientConnectionId) @@ -2605,7 +2587,10 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { // We use Task.Run here in all places to execute task synchronously in the same context. // Fixes block-over-async deadlock possibilities https://github.com/dotnet/SqlClient/issues/1209 - _fedAuthToken = Task.Run(async () => await authProvider.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult().ToSqlFedAuthToken(); + // + // TODO: Remove this async stuff and the '!' + // when we have a sync API on the auth provider. + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } break; @@ -2622,7 +2607,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) else { authParamsBuilder.WithUserId(ConnectionOptions.UserID); - _fedAuthToken = Task.Run(async () => await authProvider.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult().ToSqlFedAuthToken(); + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } break; @@ -2640,13 +2625,13 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { username = _credential.UserId; authParamsBuilder.WithUserId(username).WithPassword(_credential.Password); - _fedAuthToken = Task.Run(async () => await authProvider.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult().ToSqlFedAuthToken(); + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); } else { username = ConnectionOptions.UserID; authParamsBuilder.WithUserId(username).WithPassword(ConnectionOptions.Password); - _fedAuthToken = Task.Run(async () => await authProvider.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult().ToSqlFedAuthToken(); + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); } _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } @@ -2680,105 +2665,80 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { cts.CancelAfter((int)_timeout.MillisecondsRemaining); } - _fedAuthToken = Task.Run(async () => await _accessTokenCallback(parameters, cts.Token)).GetAwaiter().GetResult().ToSqlFedAuthToken(); + _fedAuthToken = new(Task.Run(async () => await _accessTokenCallback(parameters, cts.Token)).GetAwaiter().GetResult()); _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } break; } - Debug.Assert(_fedAuthToken.accessToken != null, "AccessToken should not be null."); -#if DEBUG - if (_forceMsalRetry) - { - // 3399614468 is 0xCAA20004L just for testing. - throw new MsalServiceException(MsalError.UnknownError, "Force retry in GetFedAuthToken"); - } -#endif + Debug.Assert(_fedAuthToken.AccessToken != null, "AccessToken should not be null."); + // Break out of the retry loop in successful case. break; } - // Deal with Msal service exceptions first, retry if 429 received. - catch (MsalServiceException serviceException) + catch (SqlAuthenticationProviderException ex) { - if (serviceException.StatusCode == MsalHttpRetryStatusCode) + // Is the error fatal or retryable? + if (!ex.ShouldRetry) { - RetryConditionHeaderValue retryAfter = serviceException.Headers.RetryAfter; - if (retryAfter.Delta.HasValue) - { - sleepInterval = retryAfter.Delta.Value.Milliseconds; - } - else if (retryAfter.Date.HasValue) - { - sleepInterval = Convert.ToInt32(retryAfter.Date.Value.Offset.TotalMilliseconds); - } - - // if there's enough time to retry before timeout, then retry, otherwise break out the retry loop. - if (sleepInterval < _timeout.MillisecondsRemaining) - { - Thread.Sleep(sleepInterval); - } - else - { - SqlClientEventSource.Log.TryTraceEvent(" Timeout: {0}", serviceException.ErrorCode); - throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), serviceException.ErrorCode, serviceException); - } + // It's fatal, so translate into a SqlException. + throw ADP.CreateSqlException(ex, ConnectionOptions, this, username); } - else + + // We should retry. + + // Could we retry if we wanted to? + if (_timeout.IsExpired || _timeout.MillisecondsRemaining <= 0) { - SqlClientEventSource.Log.TryTraceEvent(" {0}", serviceException.ErrorCode); - throw ADP.CreateSqlException(serviceException, ConnectionOptions, this, username); + // No, so we throw. + SqlClientEventSource.Log.TryTraceEvent(" Retry Attempt: {0}, Timeout: {1}", retryAttempt, ex.FailureCode); + throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), ex.FailureCode, ex); } - } - // Deal with normal MsalExceptions. - catch (MsalException msalException) - { - if (MsalError.UnknownError != msalException.ErrorCode || _timeout.IsExpired || _timeout.MillisecondsRemaining <= sleepInterval) - { - SqlClientEventSource.Log.TryTraceEvent(" {0}", msalException.ErrorCode); - throw ADP.CreateSqlException(msalException, ConnectionOptions, this, username); - } + // We use a doubling backoff if the provider didn't provide + // a retry period. + int retryPeriod = + ex.RetryPeriod > 0 + ? ex.RetryPeriod + : defaultRetryPeriod * (2 ^ retryAttempt); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, sleeping {1}[Milliseconds]", ObjectID, sleepInterval); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, remaining {1}[Milliseconds]", ObjectID, _timeout.MillisecondsRemaining); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Retry Attempt: {1}, sleeping {2}[Milliseconds]", ObjectID, retryAttempt, retryPeriod); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Retry Attempt: {1}, remaining {2}[Milliseconds]", ObjectID, retryAttempt, _timeout.MillisecondsRemaining); - Thread.Sleep(sleepInterval); - sleepInterval *= 2; - } - // All other exceptions from MSAL/Azure Identity APIs - catch (Exception e) - { - throw SqlException.CreateException( - new() - { - new( - 0, - (byte)0x00, - (byte)TdsEnums.FATAL_ERROR_CLASS, - ConnectionOptions.DataSource, - e.Message, - ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, - 0) - }, - "", - this, - e); + // Sleep for the desired period. + Thread.Sleep(retryPeriod); + + // Fall through to retry... } } - Debug.Assert(_fedAuthToken != null, "fedAuthToken should not be null."); - Debug.Assert(_fedAuthToken.accessToken != null && _fedAuthToken.accessToken.Length > 0, "fedAuthToken.accessToken should not be null or empty."); + // Nullable context has exposed that _fedAuthToken may be null here, + // which the existing code didn't handle. + if (_fedAuthToken is null) + { + // We failed to acquire a token, so use a default one. + // + // TODO: The old code actually allowed the AccessToken byte + // array to be null, and then had Debug.Assert()s to verify it + // wasn't null. We never test in debug, so those were never + // firing, and we were happily using a _fedAuthToken with a null + // AccessToken. Now that SqlFedAuthToken doesn't allow a null + // AccessToken, we just create an empty one instead. + _fedAuthToken = new SqlFedAuthToken([], 0); + } // Store the newly generated token in _newDbConnectionPoolAuthenticationContext, only if using pooling. if (_dbConnectionPool != null) { - DateTime expirationTime = DateTime.FromFileTimeUtc(_fedAuthToken.expirationFileTime); - _newDbConnectionPoolAuthenticationContext = new DbConnectionPoolAuthenticationContext(_fedAuthToken.accessToken, expirationTime); + DateTime expirationTime = DateTime.FromFileTimeUtc(_fedAuthToken.ExpirationFileTime); + _newDbConnectionPoolAuthenticationContext = new DbConnectionPoolAuthenticationContext(_fedAuthToken.AccessToken, expirationTime); } SqlClientEventSource.Log.TryTraceEvent(" {0}, Finished generating federated authentication token.", ObjectID); return _fedAuthToken; } + #nullable disable + internal void OnFeatureExtAck(int featureId, byte[] data) { if (RoutingInfo != null && featureId != TdsEnums.FEATUREEXT_SQLDNSCACHING) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index df4ca44714..9aaef4560f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -8,8 +8,6 @@ - - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index b37f97ac17..961c50e9ed 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -21,7 +21,6 @@ using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.SqlClient; using IsolationLevel = System.Data.IsolationLevel; -using Microsoft.Identity.Client; using Microsoft.SqlServer.Server; using System.Security.Authentication; using System.Collections.Generic; @@ -465,7 +464,7 @@ internal static ArgumentException InvalidArgumentLength(string argumentName, int internal static ArgumentException MustBeReadOnly(string argumentName) => Argument(StringsHelper.GetString(Strings.ADP_MustBeReadOnly, argumentName)); - internal static Exception CreateSqlException(MsalException msalException, SqlConnectionString connectionOptions, SqlInternalConnectionTds sender, string username) + internal static Exception CreateSqlException(SqlAuthenticationProviderException authException, SqlConnectionString connectionOptions, SqlInternalConnectionTds sender, string username) { // Error[0] SqlErrorCollection sqlErs = new(); @@ -473,20 +472,20 @@ internal static Exception CreateSqlException(MsalException msalException, SqlCon sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, connectionOptions.DataSource, StringsHelper.GetString(Strings.SQL_MSALFailure, username, connectionOptions.Authentication.ToString("G")), - ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); + authException.Method.ToString(), 0)); // Error[1] - string errorMessage1 = StringsHelper.GetString(Strings.SQL_MSALInnerException, msalException.ErrorCode); + string errorMessage1 = StringsHelper.GetString(Strings.SQL_MSALInnerException, authException.FailureCode); sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, - connectionOptions.DataSource, errorMessage1, - ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); + connectionOptions.DataSource, errorMessage1, + authException.Method.ToString(), 0)); // Error[2] - if (!string.IsNullOrEmpty(msalException.Message)) + if (!string.IsNullOrEmpty(authException.Message)) { sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, - connectionOptions.DataSource, msalException.Message, - ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); + connectionOptions.DataSource, authException.Message, + authException.Method.ToString(), 0)); } return SqlException.CreateException(sqlErs, "", sender, innerException: null, batchCommand: null); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs deleted file mode 100644 index 0393de9beb..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ /dev/null @@ -1,743 +0,0 @@ -// 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. - -using System; -using System.Collections.Concurrent; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Azure.Core; -using Azure.Identity; -using Microsoft.Data.Common.ConnectionString; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Identity.Client; -using Microsoft.Identity.Client.Extensibility; - -namespace Microsoft.Data.SqlClient -{ - /// - public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider - { - /// - /// This is a static cache instance meant to hold instances of "PublicClientApplication" mapping to information available in PublicClientAppKey. - /// The purpose of this cache is to allow re-use of Access Tokens fetched for a user interactively or with any other mode - /// to avoid interactive authentication request every-time, within application scope making use of MSAL's userTokenCache. - /// - private static readonly ConcurrentDictionary s_pcaMap = new(); - private static readonly ConcurrentDictionary s_tokenCredentialMap = new(); - private static SemaphoreSlim s_pcaMapModifierSemaphore = new(1, 1); - private static SemaphoreSlim s_tokenCredentialMapModifierSemaphore = new(1, 1); - private static readonly MemoryCache s_accountPwCache = new MemoryCache(new MemoryCacheOptions()); - private static readonly int s_accountPwCacheTtlInHours = 2; - private static readonly string s_nativeClientRedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"; - private static readonly string s_defaultScopeSuffix = "/.default"; - private readonly string _type = typeof(ActiveDirectoryAuthenticationProvider).Name; - private readonly SqlClientLogger _logger = new(); - private Func _deviceCodeFlowCallback; - private ICustomWebUi _customWebUI = null; - private readonly string _applicationClientId = ActiveDirectoryAuthentication.AdoClientId; - - /// - public ActiveDirectoryAuthenticationProvider() - : this(DefaultDeviceFlowCallback) - { - } - - /// - public ActiveDirectoryAuthenticationProvider(string applicationClientId) - : this(DefaultDeviceFlowCallback, applicationClientId) - { - } - - /// - public ActiveDirectoryAuthenticationProvider(Func deviceCodeFlowCallbackMethod, string applicationClientId = null) - { - if (applicationClientId != null) - { - _applicationClientId = applicationClientId; - } - SetDeviceCodeFlowCallback(deviceCodeFlowCallbackMethod); - } - - /// - public static void ClearUserTokenCache() - { - if (!s_pcaMap.IsEmpty) - { - s_pcaMap.Clear(); - } - - if (!s_tokenCredentialMap.IsEmpty) - { - s_tokenCredentialMap.Clear(); - } - } - - /// - public void SetDeviceCodeFlowCallback(Func deviceCodeFlowCallbackMethod) => _deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; - - /// - public void SetAcquireAuthorizationCodeAsyncCallback(Func> acquireAuthorizationCodeAsyncCallback) => _customWebUI = new CustomWebUi(acquireAuthorizationCodeAsyncCallback); - - /// - public override bool IsSupported(SqlAuthenticationMethod authentication) - { - return authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated - #pragma warning disable 0618 // Type or member is obsolete - || authentication == SqlAuthenticationMethod.ActiveDirectoryPassword - #pragma warning restore 0618 // Type or member is obsolete - || authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive - || authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal - || authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow - || authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity - || authentication == SqlAuthenticationMethod.ActiveDirectoryMSI - || authentication == SqlAuthenticationMethod.ActiveDirectoryDefault - || authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; - } - - /// - public override void BeforeLoad(SqlAuthenticationMethod authentication) - { - _logger.LogInfo(_type, "BeforeLoad", $"being loaded into SqlAuthProviders for {authentication}."); - } - - /// - public override void BeforeUnload(SqlAuthenticationMethod authentication) - { - _logger.LogInfo(_type, "BeforeUnload", $"being unloaded from SqlAuthProviders for {authentication}."); - } - -#if NETFRAMEWORK - private Func _iWin32WindowFunc = null; - - /// - public void SetIWin32WindowFunc(Func iWin32WindowFunc) => this._iWin32WindowFunc = iWin32WindowFunc; -#endif - - /// - - public override async Task AcquireTokenAsync(SqlAuthenticationParameters parameters) - { - using CancellationTokenSource cts = new(); - - // Use Connection timeout value to cancel token acquire request after certain period of time. - int timeout = parameters.ConnectionTimeout * 1000; // Convert to milliseconds - if (timeout > 0) // if ConnectionTimeout is 0 or the millis overflows an int, no need to set CancelAfter - { - cts.CancelAfter(timeout); - } - - string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix, StringComparison.Ordinal) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; - string[] scopes = new string[] { scope }; - TokenRequestContext tokenRequestContext = new(scopes); - - /* We split audience from Authority URL here. Audience can be one of the following: - * The Azure AD authority audience enumeration - * The tenant ID, which can be: - * - A GUID (the ID of your Azure AD instance), for single-tenant applications - * - A domain name associated with your Azure AD instance (also for single-tenant applications) - * One of these placeholders as a tenant ID in place of the Azure AD authority audience enumeration: - * - `organizations` for a multitenant application - * - `consumers` to sign in users only with their personal accounts - * - `common` to sign in users with their work and school accounts or their personal Microsoft accounts - * - * MSAL will throw a meaningful exception if you specify both the Azure AD authority audience and the tenant ID. - * If you don't specify an audience, your app will target Azure AD and personal Microsoft accounts as an audience. (That is, it will behave as though `common` were specified.) - * More information: https://docs.microsoft.com/azure/active-directory/develop/msal-client-application-configuration - **/ - - int separatorIndex = parameters.Authority.LastIndexOf('/'); - string authority = parameters.Authority.Remove(separatorIndex + 1); - string audience = parameters.Authority.Substring(separatorIndex + 1); - string clientId = string.IsNullOrWhiteSpace(parameters.UserId) ? null : parameters.UserId; - - if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDefault) - { - // Cache DefaultAzureCredenial based on scope, authority, audience, and clientId - TokenCredentialKey tokenCredentialKey = new(typeof(DefaultAzureCredential), authority, scope, audience, clientId); - AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, string.Empty, tokenRequestContext, cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Default auth mode. Expiry Time: {0}", accessToken.ExpiresOn); - return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); - } - - TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(authority) }; - - if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryMSI) - { - // Cache ManagedIdentityCredential based on scope, authority, and clientId - TokenCredentialKey tokenCredentialKey = new(typeof(ManagedIdentityCredential), authority, scope, string.Empty, clientId); - AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, string.Empty, tokenRequestContext, cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Managed Identity auth mode. Expiry Time: {0}", accessToken.ExpiresOn); - return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); - } - - if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal) - { - // Cache ClientSecretCredential based on scope, authority, audience, and clientId - TokenCredentialKey tokenCredentialKey = new(typeof(ClientSecretCredential), authority, scope, audience, clientId); - AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, parameters.Password, tokenRequestContext, cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Service Principal auth mode. Expiry Time: {0}", accessToken.ExpiresOn); - return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); - } - - if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity) - { - // Cache WorkloadIdentityCredential based on authority and clientId - TokenCredentialKey tokenCredentialKey = new(typeof(WorkloadIdentityCredential), authority, string.Empty, string.Empty, clientId); - // If either tenant id, client id, or the token file path are not specified when fetching the token, - // a CredentialUnavailableException will be thrown instead - AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, string.Empty, tokenRequestContext, cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Workload Identity auth mode. Expiry Time: {0}", accessToken.ExpiresOn); - return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); - } - - /* - * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows - * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend - * that you use https://login.microsoftonline.com/common/oauth2/nativeclient. - * - * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris - */ - string redirectUri = s_nativeClientRedirectUri; - -#if NET - if (parameters.AuthenticationMethod != SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) - { - redirectUri = "http://localhost"; - } -#endif - PublicClientAppKey pcaKey = new(parameters.Authority, redirectUri, _applicationClientId -#if NETFRAMEWORK - , _iWin32WindowFunc -#endif - ); - - AuthenticationResult result = null; - IPublicClientApplication app = await GetPublicClientAppInstanceAsync(pcaKey, cts.Token).ConfigureAwait(false); - - if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated) - { - result = await TryAcquireTokenSilent(app, parameters, scopes, cts).ConfigureAwait(false); - - if (result == null) - { - if (!string.IsNullOrEmpty(parameters.UserId)) - { - // The AcquireTokenByIntegratedWindowsAuth method is marked as obsolete in MSAL.NET - // but it is still a supported way to acquire tokens for Active Directory Integrated authentication. -#pragma warning disable CS0618 // Type or member is obsolete - result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) -#pragma warning restore CS0618 // Type or member is obsolete - .WithCorrelationId(parameters.ConnectionId) - .WithUsername(parameters.UserId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); - } - else - { -#pragma warning disable CS0618 // Type or member is obsolete - result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) -#pragma warning restore CS0618 // Type or member is obsolete - .WithCorrelationId(parameters.ConnectionId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); - } - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result?.ExpiresOn); - } - } - #pragma warning disable 0618 // Type or member is obsolete - else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword) - #pragma warning restore 0618 // Type or member is obsolete - { - string pwCacheKey = GetAccountPwCacheKey(parameters); - object previousPw = s_accountPwCache.Get(pwCacheKey); - byte[] currPwHash = GetHash(parameters.Password); - - if (previousPw != null && - previousPw is byte[] previousPwBytes && - // Only get the cached token if the current password hash matches the previously used password hash - AreEqual(currPwHash, previousPwBytes)) - { - result = await TryAcquireTokenSilent(app, parameters, scopes, cts).ConfigureAwait(false); - } - - if (result == null) - { -#pragma warning disable CS0618 // Type or member is obsolete - result = await app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, parameters.Password) -#pragma warning restore CS0618 // Type or member is obsolete - .WithCorrelationId(parameters.ConnectionId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); - - // We cache the password hash to ensure future connection requests include a validated password - // when we check for a cached MSAL account. Otherwise, a connection request with the same username - // against the same tenant could succeed with an invalid password when we re-use the cached token. - using (ICacheEntry entry = s_accountPwCache.CreateEntry(pwCacheKey)) - { - entry.Value = GetHash(parameters.Password); - entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(s_accountPwCacheTtlInHours); - } - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result?.ExpiresOn); - } - } - else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive || - parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) - { - try - { - result = await TryAcquireTokenSilent(app, parameters, scopes, cts).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); - } - catch (MsalUiRequiredException) - { - // An 'MsalUiRequiredException' is thrown in the case where an interaction is required with the end user of the application, - // for instance, if no refresh token was in the cache, or the user needs to consent, or re-sign-in (for instance if the password expired), - // or the user needs to perform two factor authentication. - result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts, _customWebUI, _deviceCodeFlowCallback).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); - } - - if (result == null) - { - // If no existing 'account' is found, we request user to sign in interactively. - result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts, _customWebUI, _deviceCodeFlowCallback).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); - } - } - else - { - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod); - throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod); - } - - return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); - } - - private static async Task TryAcquireTokenSilent(IPublicClientApplication app, SqlAuthenticationParameters parameters, - string[] scopes, CancellationTokenSource cts) - { - AuthenticationResult result = null; - - // Fetch available accounts from 'app' instance - System.Collections.Generic.IEnumerator accounts = (await app.GetAccountsAsync().ConfigureAwait(false)).GetEnumerator(); - - IAccount account = default; - if (accounts.MoveNext()) - { - if (!string.IsNullOrEmpty(parameters.UserId)) - { - do - { - IAccount currentVal = accounts.Current; - if (string.Compare(parameters.UserId, currentVal.Username, StringComparison.InvariantCultureIgnoreCase) == 0) - { - account = currentVal; - break; - } - } - while (accounts.MoveNext()); - } - else - { - account = accounts.Current; - } - } - - if (account != null) - { - // If 'account' is available in 'app', we use the same to acquire token silently. - // Read More on API docs: https://docs.microsoft.com/dotnet/api/microsoft.identity.client.clientapplicationbase.acquiretokensilent - result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync(cancellationToken: cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); - } - - return result; - } - - private static async Task AcquireTokenInteractiveDeviceFlowAsync(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId, - SqlAuthenticationMethod authenticationMethod, CancellationTokenSource cts, ICustomWebUi customWebUI, Func deviceCodeFlowCallback) - { - try - { - if (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) - { - CancellationTokenSource ctsInteractive = new(); -#if NET - /* - * On .NET Core, MSAL will start the system browser as a separate process. MSAL does not have control over this browser, - * but once the user finishes authentication, the web page is redirected in such a way that MSAL can intercept the Uri. - * MSAL cannot detect if the user navigates away or simply closes the browser. Apps using this technique are encouraged - * to define a timeout (via CancellationToken). We recommend a timeout of at least a few minutes, to take into account - * cases where the user is prompted to change password or perform 2FA. - * - * https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/System-Browser-on-.Net-Core#system-browser-experience - */ - ctsInteractive.CancelAfter(180000); -#endif - if (customWebUI != null) - { - return await app.AcquireTokenInteractive(scopes) - .WithCorrelationId(connectionId) - .WithCustomWebUi(customWebUI) - .WithLoginHint(userId) - .ExecuteAsync(ctsInteractive.Token) - .ConfigureAwait(false); - } - else - { - /* - * We will use the MSAL Embedded or System web browser which changes by Default in MSAL according to this table: - * - * Framework Embedded System Default - * ------------------------------------------- - * .NET Classic Yes Yes^ Embedded - * .NET Core No Yes^ System - * .NET Standard No No NONE - * UWP Yes No Embedded - * Xamarin.Android Yes Yes System - * Xamarin.iOS Yes Yes System - * Xamarin.Mac Yes No Embedded - * - * ^ Requires "http://localhost" redirect URI - * - * https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-uses-web-browser#at-a-glance - */ - return await app.AcquireTokenInteractive(scopes) - .WithCorrelationId(connectionId) - .WithLoginHint(userId) - .ExecuteAsync(ctsInteractive.Token) - .ConfigureAwait(false); - } - } - else - { - AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes, - deviceCodeResult => deviceCodeFlowCallback(deviceCodeResult)) - .WithCorrelationId(connectionId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); - return result; - } - } - catch (OperationCanceledException) - { - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenInteractiveDeviceFlowAsync | Operation timed out while acquiring access token."); - throw (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) ? - SQL.ActiveDirectoryInteractiveTimeout() : - SQL.ActiveDirectoryDeviceFlowTimeout(); - } - } - - private static Task DefaultDeviceFlowCallback(DeviceCodeResult result) - { - // This will print the message on the console which tells the user where to go sign-in using - // a separate browser and the code to enter once they sign in. - // The AcquireTokenWithDeviceCode() method will poll the server after firing this - // device code callback to look for the successful login of the user via that browser. - // This background polling (whose interval and timeout data is also provided as fields in the - // deviceCodeCallback class) will occur until: - // * The user has successfully logged in via browser and entered the proper code - // * The timeout specified by the server for the lifetime of this code (typically ~15 minutes) has been reached - // * The developing application calls the Cancel() method on a CancellationToken sent into the method. - // If this occurs, an OperationCanceledException will be thrown (see catch below for more details). - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenInteractiveDeviceFlowAsync | Callback triggered with Device Code Result: {0}", result.Message); - Console.WriteLine(result.Message); - return Task.FromResult(0); - } - - private class CustomWebUi : ICustomWebUi - { - private readonly Func> _acquireAuthorizationCodeAsyncCallback; - - internal CustomWebUi(Func> acquireAuthorizationCodeAsyncCallback) => _acquireAuthorizationCodeAsyncCallback = acquireAuthorizationCodeAsyncCallback; - - public Task AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken cancellationToken) - => _acquireAuthorizationCodeAsyncCallback.Invoke(authorizationUri, redirectUri, cancellationToken); - } - - private async Task GetPublicClientAppInstanceAsync(PublicClientAppKey publicClientAppKey, CancellationToken cancellationToken) - { - if (!s_pcaMap.TryGetValue(publicClientAppKey, out IPublicClientApplication clientApplicationInstance)) - { - await s_pcaMapModifierSemaphore.WaitAsync(cancellationToken); - try - { - // Double-check in case another thread added it while we waited for the semaphore - if (!s_pcaMap.TryGetValue(publicClientAppKey, out clientApplicationInstance)) - { - clientApplicationInstance = CreateClientAppInstance(publicClientAppKey); - s_pcaMap.TryAdd(publicClientAppKey, clientApplicationInstance); - } - } - finally - { - s_pcaMapModifierSemaphore.Release(); - } - } - - return clientApplicationInstance; - } - - private static async Task GetTokenAsync(TokenCredentialKey tokenCredentialKey, string secret, - TokenRequestContext tokenRequestContext, CancellationToken cancellationToken) - { - if (!s_tokenCredentialMap.TryGetValue(tokenCredentialKey, out TokenCredentialData tokenCredentialInstance)) - { - await s_tokenCredentialMapModifierSemaphore.WaitAsync(cancellationToken); - try - { - // Double-check in case another thread added it while we waited for the semaphore - if (!s_tokenCredentialMap.TryGetValue(tokenCredentialKey, out tokenCredentialInstance)) - { - tokenCredentialInstance = CreateTokenCredentialInstance(tokenCredentialKey, secret); - s_tokenCredentialMap.TryAdd(tokenCredentialKey, tokenCredentialInstance); - } - } - finally - { - s_tokenCredentialMapModifierSemaphore.Release(); - } - } - - if (!AreEqual(tokenCredentialInstance._secretHash, GetHash(secret))) - { - // If the secret hash has changed, we need to remove the old token credential instance and create a new one. - await s_tokenCredentialMapModifierSemaphore.WaitAsync(cancellationToken); - try - { - s_tokenCredentialMap.TryRemove(tokenCredentialKey, out _); - tokenCredentialInstance = CreateTokenCredentialInstance(tokenCredentialKey, secret); - s_tokenCredentialMap.TryAdd(tokenCredentialKey, tokenCredentialInstance); - } - finally - { - s_tokenCredentialMapModifierSemaphore.Release(); - } - } - - return await tokenCredentialInstance._tokenCredential.GetTokenAsync(tokenRequestContext, cancellationToken); - } - - private static string GetAccountPwCacheKey(SqlAuthenticationParameters parameters) - { - return parameters.Authority + "+" + parameters.UserId; - } - - private static byte[] GetHash(string input) - { - byte[] unhashedBytes = Encoding.Unicode.GetBytes(input); - SHA256 sha256 = SHA256.Create(); - byte[] hashedBytes = sha256.ComputeHash(unhashedBytes); - return hashedBytes; - } - - private static bool AreEqual(byte[] a1, byte[] a2) - { - if (ReferenceEquals(a1, a2)) - { - return true; - } - else if (a1 is null || a2 is null) - { - return false; - } - else if (a1.Length != a2.Length) - { - return false; - } - - return a1.AsSpan().SequenceEqual(a2.AsSpan()); - } - - private IPublicClientApplication CreateClientAppInstance(PublicClientAppKey publicClientAppKey) - { - PublicClientApplicationBuilder builder = PublicClientApplicationBuilder - .CreateWithApplicationOptions(new PublicClientApplicationOptions - { - ClientId = publicClientAppKey._applicationClientId, - ClientName = DbConnectionStringDefaults.ApplicationName, - ClientVersion = Common.ADP.GetAssemblyVersion().ToString(), - RedirectUri = publicClientAppKey._redirectUri, - }) - .WithAuthority(publicClientAppKey._authority); - - #if NETFRAMEWORK - if (_iWin32WindowFunc is not null) - { - builder.WithParentActivityOrWindow(_iWin32WindowFunc); - } - #endif - - return builder.Build(); - } - - private static TokenCredentialData CreateTokenCredentialInstance(TokenCredentialKey tokenCredentialKey, string secret) - { - if (tokenCredentialKey._tokenCredentialType == typeof(DefaultAzureCredential)) - { - DefaultAzureCredentialOptions defaultAzureCredentialOptions = new() - { - AuthorityHost = new Uri(tokenCredentialKey._authority), - TenantId = tokenCredentialKey._audience, - ExcludeInteractiveBrowserCredential = true // Force disabled, even though it's disabled by default to respect driver specifications. - }; - - // Optionally set clientId when available - if (tokenCredentialKey._clientId is not null) - { - defaultAzureCredentialOptions.ManagedIdentityClientId = tokenCredentialKey._clientId; -#pragma warning disable CS0618 // Type or member is obsolete - defaultAzureCredentialOptions.SharedTokenCacheUsername = tokenCredentialKey._clientId; -#pragma warning restore CS0618 // Type or member is obsolete - defaultAzureCredentialOptions.WorkloadIdentityClientId = tokenCredentialKey._clientId; - } - - // SqlClient is a library and provides support to acquire access - // token using 'DefaultAzureCredential' on user demand when they - // specify 'Authentication = Active Directory Default' in - // connection string. - // - // Default Azure Credential is instantiated by the calling - // application when using "Active Directory Default" - // authentication code to connect to Azure SQL instance. - // SqlClient is a library, doesn't instantiate the credential - // without running application instructions. - // - // Note that CodeQL suppression support can only detect - // suppression comments that appear immediately above the - // flagged statement, or appended to the end of the statement. - // Multi-line justifications are not supported. - // - // https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/codeql/codeql-semmle#guidance-on-suppressions - // - // CodeQL [SM05137] See above for justification. - DefaultAzureCredential cred = new(defaultAzureCredentialOptions); - - return new TokenCredentialData(cred, GetHash(secret)); - } - - TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) }; - - if (tokenCredentialKey._tokenCredentialType == typeof(ManagedIdentityCredential)) - { - return new TokenCredentialData(new ManagedIdentityCredential(tokenCredentialKey._clientId, tokenCredentialOptions), GetHash(secret)); - } - else if (tokenCredentialKey._tokenCredentialType == typeof(ClientSecretCredential)) - { - return new TokenCredentialData(new ClientSecretCredential(tokenCredentialKey._audience, tokenCredentialKey._clientId, secret, tokenCredentialOptions), GetHash(secret)); - } - else if (tokenCredentialKey._tokenCredentialType == typeof(WorkloadIdentityCredential)) - { - // The WorkloadIdentityCredentialOptions object initialization populates its instance members - // from the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE, - // and AZURE_ADDITIONALLY_ALLOWED_TENANTS. AZURE_CLIENT_ID may be overridden by the User Id. - WorkloadIdentityCredentialOptions options = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) }; - - if (tokenCredentialKey._clientId is not null) - { - options.ClientId = tokenCredentialKey._clientId; - } - - return new TokenCredentialData(new WorkloadIdentityCredential(options), GetHash(secret)); - } - - // This should never be reached, but if it is, throw an exception that will be noticed during development - throw new ArgumentException(nameof(ActiveDirectoryAuthenticationProvider)); - } - - internal class PublicClientAppKey - { - public readonly string _authority; - public readonly string _redirectUri; - public readonly string _applicationClientId; -#if NETFRAMEWORK - public readonly Func _iWin32WindowFunc; -#endif - - public PublicClientAppKey(string authority, string redirectUri, string applicationClientId -#if NETFRAMEWORK - , Func iWin32WindowFunc -#endif - ) - { - _authority = authority; - _redirectUri = redirectUri; - _applicationClientId = applicationClientId; -#if NETFRAMEWORK - _iWin32WindowFunc = iWin32WindowFunc; -#endif - } - - public override bool Equals(object obj) - { - if (obj != null && obj is PublicClientAppKey pcaKey) - { - return (string.CompareOrdinal(_authority, pcaKey._authority) == 0 - && string.CompareOrdinal(_redirectUri, pcaKey._redirectUri) == 0 - && string.CompareOrdinal(_applicationClientId, pcaKey._applicationClientId) == 0 -#if NETFRAMEWORK - && pcaKey._iWin32WindowFunc == _iWin32WindowFunc -#endif - ); - } - return false; - } - - public override int GetHashCode() => Tuple.Create(_authority, _redirectUri, _applicationClientId -#if NETFRAMEWORK - , _iWin32WindowFunc -#endif - ).GetHashCode(); - } - - internal class TokenCredentialData - { - public TokenCredential _tokenCredential; - public byte[] _secretHash; - - public TokenCredentialData(TokenCredential tokenCredential, byte[] secretHash) - { - _tokenCredential = tokenCredential; - _secretHash = secretHash; - } - } - - internal class TokenCredentialKey - { - public readonly Type _tokenCredentialType; - public readonly string _authority; - public readonly string _scope; - public readonly string _audience; - public readonly string _clientId; - - public TokenCredentialKey(Type tokenCredentialType, string authority, string scope, string audience, string clientId) - { - _tokenCredentialType = tokenCredentialType; - _authority = authority; - _scope = scope; - _audience = audience; - _clientId = clientId; - } - - public override bool Equals(object obj) - { - if (obj != null && obj is TokenCredentialKey tcKey) - { - return string.CompareOrdinal(nameof(_tokenCredentialType), nameof(tcKey._tokenCredentialType)) == 0 - && string.CompareOrdinal(_authority, tcKey._authority) == 0 - && string.CompareOrdinal(_scope, tcKey._scope) == 0 - && string.CompareOrdinal(_audience, tcKey._audience) == 0 - && string.CompareOrdinal(_clientId, tcKey._clientId) == 0 - ; - } - return false; - } - - public override int GetHashCode() => Tuple.Create(_tokenCredentialType, _authority, _scope, _audience, _clientId).GetHashCode(); - } - - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationTimeoutRetryHelper.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationTimeoutRetryHelper.cs index e972102260..721e31ca6f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationTimeoutRetryHelper.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationTimeoutRetryHelper.cs @@ -132,7 +132,8 @@ private static string GetTokenHash(SqlFedAuthToken token) } // Here we mimic how ADAL calculates hash for token. They use UTF8 instead of Unicode. - var originalTokenString = SqlAuthenticationToken.AccessTokenStringFromBytes(token.accessToken); + var originalTokenString = Encoding.Unicode.GetString(token.AccessToken); + var bytesInUtf8 = Encoding.UTF8.GetBytes(originalTokenString); using (var sha256 = SHA256.Create()) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParameters.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParameters.cs deleted file mode 100644 index 9c74b937b8..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParameters.cs +++ /dev/null @@ -1,162 +0,0 @@ -// 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. - -using System; -using System.Runtime.InteropServices; -using System.Security; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - - /// - public class SqlAuthenticationParameters - { - /// - public SqlAuthenticationMethod AuthenticationMethod { get; } - - /// - public string Resource { get; } - - /// - public string Authority { get; } - - /// - public string UserId { get; } - - /// - public string Password { get; } - - /// - public Guid ConnectionId { get; } - - /// - public string ServerName { get; } - - /// - public string DatabaseName { get; } - - /// - public int ConnectionTimeout { get; } = ADP.DefaultConnectionTimeout; - - /// - protected SqlAuthenticationParameters( - SqlAuthenticationMethod authenticationMethod, - string serverName, - string databaseName, - string resource, - string authority, - string userId, - string password, - Guid connectionId, - int connectionTimeout) - { - AuthenticationMethod = authenticationMethod; - ServerName = serverName; - DatabaseName = databaseName; - Resource = resource; - Authority = authority; - UserId = userId; - Password = password; - ConnectionId = connectionId; - ConnectionTimeout = connectionTimeout; - } - - /// - /// AD authentication parameter builder. - /// - internal class Builder - { - private readonly SqlAuthenticationMethod _authenticationMethod; - private readonly string _serverName; - private readonly string _databaseName; - private readonly string _resource; - private readonly string _authority; - private string _userId; - private string _password; - private Guid _connectionId = Guid.NewGuid(); - private int _connectionTimeout = ADP.DefaultConnectionTimeout; - - /// - /// Implicitly converts to . - /// - public static implicit operator SqlAuthenticationParameters(Builder builder) - { - return new SqlAuthenticationParameters( - authenticationMethod: builder._authenticationMethod, - serverName: builder._serverName, - databaseName: builder._databaseName, - resource: builder._resource, - authority: builder._authority, - userId: builder._userId, - password: builder._password, - connectionId: builder._connectionId, - connectionTimeout: builder._connectionTimeout); - } - - /// - /// Set user id. - /// - public Builder WithUserId(string userId) - { - _userId = userId; - return this; - } - - /// - /// Set password. - /// - public Builder WithPassword(string password) - { - _password = password; - return this; - } - - /// - /// Set password. - /// - public Builder WithPassword(SecureString password) - { - IntPtr valuePtr = IntPtr.Zero; - try - { - valuePtr = Marshal.SecureStringToGlobalAllocUnicode(password); - _password = Marshal.PtrToStringUni(valuePtr); - } - finally - { - Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); - } - return this; - } - - /// - /// Set a specific connection id instead of using a random one. - /// - public Builder WithConnectionId(Guid connectionId) - { - _connectionId = connectionId; - return this; - } - - /// - /// Set connection timeout. - /// - public Builder WithConnectionTimeout(int timeout) - { - _connectionTimeout = timeout; - return this; - } - - internal Builder(SqlAuthenticationMethod authenticationMethod, string resource, string authority, string serverName, string databaseName) - { - _authenticationMethod = authenticationMethod; - _serverName = serverName; - _databaseName = databaseName; - _resource = resource; - _authority = authority; - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs new file mode 100644 index 0000000000..5fdd7f020e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs @@ -0,0 +1,104 @@ +// 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. + +using System; +using System.Runtime.InteropServices; +using System.Security; +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient +{ + internal sealed class SqlAuthenticationParametersBuilder + { + private readonly SqlAuthenticationMethod _authenticationMethod; + private readonly string _serverName; + private readonly string _databaseName; + private readonly string _resource; + private readonly string _authority; + private string _userId; + private string _password; + private Guid _connectionId = Guid.NewGuid(); + private int _connectionTimeout = ADP.DefaultConnectionTimeout; + + /// + /// Implicitly converts to . + /// + public static implicit operator SqlAuthenticationParameters(SqlAuthenticationParametersBuilder builder) + { + return new SqlAuthenticationParameters( + authenticationMethod: builder._authenticationMethod, + serverName: builder._serverName, + databaseName: builder._databaseName, + resource: builder._resource, + authority: builder._authority, + userId: builder._userId, + password: builder._password, + connectionId: builder._connectionId, + connectionTimeout: builder._connectionTimeout); + } + + /// + /// Set user id. + /// + public SqlAuthenticationParametersBuilder WithUserId(string userId) + { + _userId = userId; + return this; + } + + /// + /// Set password. + /// + public SqlAuthenticationParametersBuilder WithPassword(string password) + { + _password = password; + return this; + } + + /// + /// Set password. + /// + public SqlAuthenticationParametersBuilder WithPassword(SecureString password) + { + IntPtr valuePtr = IntPtr.Zero; + try + { + valuePtr = Marshal.SecureStringToGlobalAllocUnicode(password); + _password = Marshal.PtrToStringUni(valuePtr); + } + finally + { + Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); + } + return this; + } + + /// + /// Set a specific connection id instead of using a random one. + /// + public SqlAuthenticationParametersBuilder WithConnectionId(Guid connectionId) + { + _connectionId = connectionId; + return this; + } + + /// + /// Set connection timeout. + /// + public SqlAuthenticationParametersBuilder WithConnectionTimeout(int timeout) + { + _connectionTimeout = timeout; + return this; + } + + internal SqlAuthenticationParametersBuilder(SqlAuthenticationMethod authenticationMethod, string resource, string authority, string serverName, string databaseName) + { + _authenticationMethod = authenticationMethod; + _serverName = serverName; + _databaseName = databaseName; + _resource = resource; + _authority = authority; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs deleted file mode 100644 index 25e1cf006e..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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. - -using System.Threading.Tasks; - -namespace Microsoft.Data.SqlClient -{ - - /// - public abstract class SqlAuthenticationProvider - { - /// - public static SqlAuthenticationProvider GetProvider(SqlAuthenticationMethod authenticationMethod) - { - return SqlAuthenticationProviderManager.Instance.GetProvider(authenticationMethod); - } - - /// - public static bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider) - { - return SqlAuthenticationProviderManager.Instance.SetProvider(authenticationMethod, provider); - } - - /// - public virtual void BeforeLoad(SqlAuthenticationMethod authenticationMethod) { } - - /// - public virtual void BeforeUnload(SqlAuthenticationMethod authenticationMethod) { } - - /// - public abstract bool IsSupported(SqlAuthenticationMethod authenticationMethod); - - /// - public abstract Task AcquireTokenAsync(SqlAuthenticationParameters parameters); - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index 447ea0e9c5..ab3614a751 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -6,13 +6,15 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; +using System.IO; +using System.Reflection; + +#nullable enable namespace Microsoft.Data.SqlClient { - /// - /// Authentication provider manager. - /// - internal sealed class SqlAuthenticationProviderManager + /// + public sealed class SqlAuthenticationProviderManager { [Obsolete("ActiveDirectoryPassword is deprecated, use a more secure authentication method. See https://aka.ms/SqlClientEntraIDAuthentication for more details.")] private const string ActiveDirectoryPassword = "active directory password"; @@ -27,7 +29,7 @@ internal sealed class SqlAuthenticationProviderManager static SqlAuthenticationProviderManager() { - SqlAuthenticationProviderConfigurationSection configurationSection = null; + SqlAuthenticationProviderConfigurationSection? configurationSection = null; try { @@ -46,49 +48,116 @@ static SqlAuthenticationProviderManager() } Instance = new SqlAuthenticationProviderManager(configurationSection); - SetDefaultAuthProviders(Instance); - } - /// - /// Sets default supported Active Directory Authentication providers by the driver - /// on the SqlAuthenticationProviderManager instance. - /// - private static void SetDefaultAuthProviders(SqlAuthenticationProviderManager instance) - { - if (instance != null) + // If our Azure extensions package is present, use its + // authentication provider as our default. + const string assemblyName = "Microsoft.Data.SqlClient.Extensions.Azure"; + + try { - var activeDirectoryAuthProvider = new ActiveDirectoryAuthenticationProvider(instance._applicationClientId); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, activeDirectoryAuthProvider); + // Try to load our Azure extension. + var assembly = Assembly.Load(assemblyName); + + // TODO: Verify the assembly is signed by us? + + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension assembly={assemblyName} found; " + + "attempting to set as default provider for all Active " + + "Directory authentication methods"); + + // Look for the authentication provider class. + const string className = "Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider"; + var type = assembly.GetType(className); + + if (type is null) + { + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension does not contain class={className}; " + + "no default Active Directory provider installed"); + + return; + } + + // Try to instantiate it. + var instance = Activator.CreateInstance( + type, + [Instance._applicationClientId]) + as SqlAuthenticationProvider; + + if (instance is null) + { + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Failed to instantiate Azure extension class={className}; " + + "no default Active Directory provider installed"); + + return; + } + + // We successfully instantiated the provider, so set it as the + // default for all Active Directory authentication methods. + // + // Note that SetProvider() will refuse to clobber an application + // specified provider, so these defaults will only be applied + // for methods that do not already have a provider. + SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, instance); #pragma warning disable 0618 // Type or member is obsolete - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, activeDirectoryAuthProvider); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, instance); #pragma warning restore 0618 // Type or member is obsolete - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, activeDirectoryAuthProvider); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, instance); + + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension class={className} installed as " + + "provider for all Active Directory authentication methods"); + } + // All of these exceptions mean we couldn't find or instantiate the + // Azure extension's authentication provider, in which case we + // simply have no default and the app must provide one if they + // attempt to use Active Directory authentication. + catch (Exception ex) + when (ex is ArgumentNullException || + ex is ArgumentException || + ex is BadImageFormatException || + ex is FileLoadException || + ex is FileNotFoundException || + ex is MemberAccessException || + ex is MethodAccessException || + ex is MissingMethodException || + ex is NotSupportedException || + ex is TargetInvocationException || + ex is TypeLoadException) + { + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension assembly={assemblyName} not found or " + + "not usable; no default provider installed; " + + $"{ex.GetType().Name}: {ex.Message}"); } + // Any other exceptions are fatal. } - public static readonly SqlAuthenticationProviderManager Instance; + private static readonly SqlAuthenticationProviderManager Instance; - private readonly SqlAuthenticationInitializer _initializer; - private readonly IReadOnlyCollection _authenticationsWithAppSpecifiedProvider; - private readonly ConcurrentDictionary _providers; + private readonly HashSet _authenticationsWithAppSpecifiedProvider = new(); + private readonly ConcurrentDictionary _providers = new(); private readonly SqlClientLogger _sqlAuthLogger = new SqlClientLogger(); - private readonly string _applicationClientId = ActiveDirectoryAuthentication.AdoClientId; + private readonly string? _applicationClientId = null; /// /// Constructor. /// - public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSection configSection = null) + private SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSection? configSection) { var methodName = "Ctor"; - _providers = new ConcurrentDictionary(); - var authenticationsWithAppSpecifiedProvider = new HashSet(); - _authenticationsWithAppSpecifiedProvider = authenticationsWithAppSpecifiedProvider; if (configSection == null) { @@ -112,8 +181,14 @@ public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSe try { var initializerType = Type.GetType(configSection.InitializerType, true); - _initializer = (SqlAuthenticationInitializer)Activator.CreateInstance(initializerType); - _initializer.Initialize(); + if (initializerType is not null) + { + var initializer = (SqlAuthenticationInitializer?)Activator.CreateInstance(initializerType); + if (initializer is not null) + { + initializer.Initialize(); + } + } } catch (Exception e) { @@ -132,23 +207,31 @@ public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSe foreach (ProviderSettings providerSettings in configSection.Providers) { SqlAuthenticationMethod authentication = AuthenticationEnumFromString(providerSettings.Name); - SqlAuthenticationProvider provider; + SqlAuthenticationProvider? provider; try { var providerType = Type.GetType(providerSettings.Type, true); - provider = (SqlAuthenticationProvider)Activator.CreateInstance(providerType); + if (providerType is null) + { + continue; + } + provider = (SqlAuthenticationProvider?)Activator.CreateInstance(providerType); } catch (Exception e) { throw SQL.CannotCreateAuthProvider(authentication.ToString(), providerSettings.Type, e); } + if (provider is null) + { + continue; + } if (!provider.IsSupported(authentication)) { throw SQL.UnsupportedAuthenticationByProvider(authentication.ToString(), providerSettings.Type); } _providers[authentication] = provider; - authenticationsWithAppSpecifiedProvider.Add(authentication); + _authenticationsWithAppSpecifiedProvider.Add(authentication); _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, string.Format("Added user-defined auth provider: {0} for authentication {1}.", providerSettings?.Type, authentication)); } } @@ -158,54 +241,50 @@ public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSe } } - /// - /// Get an authentication provider by method. - /// - /// Authentication method. - /// Authentication provider or null if not found. - public SqlAuthenticationProvider GetProvider(SqlAuthenticationMethod authenticationMethod) + /// + public static SqlAuthenticationProvider? GetProvider(SqlAuthenticationMethod authenticationMethod) { - SqlAuthenticationProvider value; - return _providers.TryGetValue(authenticationMethod, out value) ? value : null; + SqlAuthenticationProvider? value; + return Instance._providers.TryGetValue(authenticationMethod, out value) ? value : null; } - /// - /// Set an authentication provider by method. - /// - /// Authentication method. - /// Authentication provider. - /// True if succeeded, false otherwise, e.g., the existing provider disallows overriding. - public bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider) + /// + public static bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider) { if (!provider.IsSupported(authenticationMethod)) { throw SQL.UnsupportedAuthenticationByProvider(authenticationMethod.ToString(), provider.GetType().Name); } var methodName = "SetProvider"; - if (_authenticationsWithAppSpecifiedProvider.Count > 0) + if (Instance._authenticationsWithAppSpecifiedProvider.Count > 0) { - foreach (SqlAuthenticationMethod candidateMethod in _authenticationsWithAppSpecifiedProvider) + foreach (SqlAuthenticationMethod candidateMethod in Instance._authenticationsWithAppSpecifiedProvider) { if (candidateMethod == authenticationMethod) { - _sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(_providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); - return false; // return here to avoid replacing user-defined provider + Instance._sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(Instance._providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); + + // The app has already specified a Provider for this + // authentication method, so we won't override it. + return false; } } } - _providers.AddOrUpdate(authenticationMethod, provider, (key, oldProvider) => - { - if (oldProvider != null) - { - oldProvider.BeforeUnload(authenticationMethod); - } - if (provider != null) + Instance._providers.AddOrUpdate( + authenticationMethod, + provider, + (SqlAuthenticationMethod key, SqlAuthenticationProvider oldProvider) => { + if (oldProvider != null) + { + oldProvider.BeforeUnload(authenticationMethod); + } + provider.BeforeLoad(authenticationMethod); - } - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, $"Added auth provider {GetProviderType(provider)}, overriding existed provider {GetProviderType(oldProvider)} for authentication {authenticationMethod}."); - return provider; - }); + + Instance._sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, $"Added auth provider {GetProviderType(provider)}, overriding existed provider {GetProviderType(oldProvider)} for authentication {authenticationMethod}."); + return provider; + }); return true; } @@ -216,7 +295,7 @@ public bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthent /// /// /// - private static T FetchConfigurationSection(string name) + private static T? FetchConfigurationSection(string name) where T : class { Type t = typeof(T); @@ -265,14 +344,13 @@ private static SqlAuthenticationMethod AuthenticationEnumFromString(string authe } } - private static string GetProviderType(SqlAuthenticationProvider provider) + private static string GetProviderType(SqlAuthenticationProvider? provider) { - if (provider == null) + if (provider is null) { return "null"; } - - return provider.GetType().FullName; + return provider.GetType().FullName ?? "unknown"; } } @@ -293,13 +371,13 @@ internal class SqlAuthenticationProviderConfigurationSection : ConfigurationSect /// User-defined initializer. /// [ConfigurationProperty("initializerType")] - public string InitializerType => this["initializerType"] as string; + public string InitializerType => this["initializerType"] as string ?? string.Empty; /// /// Application Client Id /// [ConfigurationProperty("applicationClientId", IsRequired = false)] - public string ApplicationClientId => this["applicationClientId"] as string; + public string ApplicationClientId => this["applicationClientId"] as string ?? string.Empty; } /// diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationToken.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationToken.cs deleted file mode 100644 index c405cac2cb..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationToken.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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. - -using System; -using System.Text; - -namespace Microsoft.Data.SqlClient -{ - /// - public class SqlAuthenticationToken - { - /// - public DateTimeOffset ExpiresOn { get; } - - /// - public string AccessToken { get; } - - /// - public SqlAuthenticationToken(string accessToken, DateTimeOffset expiresOn) - { - if (string.IsNullOrEmpty(accessToken)) - { - throw SQL.ParameterCannotBeEmpty("AccessToken"); - } - - AccessToken = accessToken; - ExpiresOn = expiresOn; - } - - /// - /// Constructor. - /// - internal SqlAuthenticationToken(byte[] accessToken, DateTimeOffset expiresOn) - : this(AccessTokenStringFromBytes(accessToken), expiresOn) { } - - /// - /// Convert to driver's internal token class. - /// - internal SqlFedAuthToken ToSqlFedAuthToken() - { - var tokenBytes = AccessTokenBytesFromString(AccessToken); - return new SqlFedAuthToken - { - accessToken = tokenBytes, - dataLen = (uint)tokenBytes.Length, - expirationFileTime = ExpiresOn.ToFileTime() - }; - } - - /// - /// Convert token bytes to string. - /// - internal static string AccessTokenStringFromBytes(byte[] bytes) - { - return Encoding.Unicode.GetString(bytes); - } - - /// - /// Convert token string to bytes. - /// - internal static byte[] AccessTokenBytesFromString(string token) - { - return Encoding.Unicode.GetBytes(token); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlErrorCollection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlErrorCollection.cs index 4684747627..fe20d0d2e5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlErrorCollection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlErrorCollection.cs @@ -45,6 +45,11 @@ internal SqlErrorCollection() { } /// public IEnumerator GetEnumerator() => _errors.GetEnumerator(); - internal void Add(SqlError error) => _errors.Add(error); + // Append the error to our list, and return ourselves for chaining. + internal SqlErrorCollection Add(SqlError error) + { + _errors.Add(error); + return this; + } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs index 80c810e748..dcaa8b55dd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs @@ -205,8 +205,6 @@ internal static SqlException CreateException(SqlErrorCollection errorCollection, internal static SqlException CreateException(SqlErrorCollection errorCollection, string serverVersion, Guid conId, Exception innerException = null, SqlBatchCommand batchCommand = null) { - Debug.Assert(errorCollection != null && errorCollection.Count > 0, "no errorCollection?"); - StringBuilder message = new(); for (int i = 0; i < errorCollection.Count; i++) { @@ -217,7 +215,11 @@ internal static SqlException CreateException(SqlErrorCollection errorCollection, message.Append(errorCollection[i].Message); } - if (innerException == null && errorCollection[0].Win32ErrorCode != 0 && errorCollection[0].Win32ErrorCode != -1) + if (innerException is null && + errorCollection is not null && + errorCollection.Count > 0 && + errorCollection[0].Win32ErrorCode != 0 && + errorCollection[0].Win32ErrorCode != -1) { innerException = new Win32Exception(errorCollection[0].Win32ErrorCode); } @@ -230,7 +232,10 @@ internal static SqlException CreateException(SqlErrorCollection errorCollection, exception.Data.Add("HelpLink.ProdVer", serverVersion); } exception.Data.Add("HelpLink.EvtSrc", "MSSQLServer"); - exception.Data.Add("HelpLink.EvtID", errorCollection[0].Number.ToString(CultureInfo.InvariantCulture)); + if (errorCollection is not null && errorCollection.Count > 0) + { + exception.Data.Add("HelpLink.EvtID", errorCollection[0].Number.ToString(CultureInfo.InvariantCulture)); + } exception.Data.Add("HelpLink.BaseHelpUrl", "https://go.microsoft.com/fwlink"); exception.Data.Add("HelpLink.LinkId", "20476"); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs index e13a77bd73..9b0361a008 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -1382,17 +1382,6 @@ internal static Exception UnsupportedFeatureAndToken(SqlInternalConnectionTds in return exc; } - internal static Exception Azure_ManagedIdentityException(string msg) - { - SqlErrorCollection errors = new SqlErrorCollection - { - new SqlError(0, (byte)0x00, TdsEnums.FATAL_ERROR_CLASS, null, msg, "", 0) - }; - SqlException exc = SqlException.CreateException(errors, null); - exc._doNotReconnect = true; // disable open retry logic on this error - return exc; - } - #region Always Encrypted Errors #region Always Encrypted - Certificate Store Provider Errors diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs index 53aaf8295b..1a77fbf19a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -1133,44 +1133,6 @@ public enum SqlCommandColumnEncryptionSetting /// Disabled, } - - /// - public enum SqlAuthenticationMethod - { - /// - NotSpecified = 0, - - /// - SqlPassword, - - /// - [Obsolete("ActiveDirectoryPassword is deprecated, use a more secure authentication method. See https://aka.ms/SqlClientEntraIDAuthentication for more details.")] - ActiveDirectoryPassword, - - /// - ActiveDirectoryIntegrated, - - /// - ActiveDirectoryInteractive, - - /// - ActiveDirectoryServicePrincipal, - - /// - ActiveDirectoryDeviceCodeFlow, - - /// - ActiveDirectoryManagedIdentity, - - /// - ActiveDirectoryMSI, - - /// - ActiveDirectoryDefault, - - /// - ActiveDirectoryWorkloadIdentity - } // This enum indicates the state of TransparentNetworkIPResolution // The first attempt when TNIR is on should be sequential. If the first attempt fails next attempts should be parallel. internal enum TransparentNetworkResolutionState @@ -1180,12 +1142,6 @@ internal enum TransparentNetworkResolutionState ParallelMode }; - internal class ActiveDirectoryAuthentication - { - internal const string AdoClientId = "2fd908ad-0664-4344-b9be-cd3e8b574c38"; - internal const string MSALGetAccessTokenFunctionName = "AcquireToken"; - } - // Fields in the first resultset of "sp_describe_parameter_encryption". // We expect the server to return the fields in the resultset in the same order as mentioned below. // If the server changes the below order, then transparent parameter encryption will break. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs index d88bf218f6..deb44f44b8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -4297,7 +4297,8 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out private TdsOperationStatus TryProcessFedAuthInfo(TdsParserStateObject stateObj, int tokenLen, out SqlFedAuthInfo sqlFedAuthInfo) { sqlFedAuthInfo = null; - SqlFedAuthInfo tempFedAuthInfo = new SqlFedAuthInfo(); + string spn = null; + string stsUrl = null; // Skip reading token length, since it has already been read in caller SqlClientEventSource.Log.TryAdvancedTraceEvent(" FEDAUTHINFO token stream length = {0}", tokenLen); @@ -4390,15 +4391,15 @@ private TdsOperationStatus TryProcessFedAuthInfo(TdsParserStateObject stateObj, } SqlClientEventSource.Log.TryAdvancedTraceEvent(" FedAuthInfoData: {0}", data); - // store data in tempFedAuthInfo + // Store data in temporaries. switch ((TdsEnums.FedAuthInfoId)id) { case TdsEnums.FedAuthInfoId.Spn: - tempFedAuthInfo.spn = data; + spn = data; break; case TdsEnums.FedAuthInfoId.Stsurl: - tempFedAuthInfo.stsurl = data; + stsUrl = data; break; default: @@ -4413,15 +4414,16 @@ private TdsOperationStatus TryProcessFedAuthInfo(TdsParserStateObject stateObj, throw SQL.ParsingErrorLength(ParsingErrorState.FedAuthInfoLengthTooShortForData, tokenLen); } - SqlClientEventSource.Log.TryTraceEvent(" Processed FEDAUTHINFO token stream: {0}", tempFedAuthInfo); - if (string.IsNullOrWhiteSpace(tempFedAuthInfo.stsurl) || string.IsNullOrWhiteSpace(tempFedAuthInfo.spn)) + if (string.IsNullOrWhiteSpace(spn) || string.IsNullOrWhiteSpace(stsUrl)) { // We should be receiving both stsurl and spn SqlClientEventSource.Log.TryTraceEvent(" FEDAUTHINFO token stream does not contain both STSURL and SPN."); throw SQL.ParsingError(ParsingErrorState.FedAuthInfoDoesNotContainStsurlAndSpn); } - sqlFedAuthInfo = tempFedAuthInfo; + sqlFedAuthInfo = new(spn, stsUrl); + SqlClientEventSource.Log.TryTraceEvent(" Processed FEDAUTHINFO token stream: {0}", sqlFedAuthInfo); + return TdsOperationStatus.Done; } @@ -9539,11 +9541,11 @@ private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures, internal void SendFedAuthToken(SqlFedAuthToken fedAuthToken) { Debug.Assert(fedAuthToken != null, "fedAuthToken cannot be null"); - Debug.Assert(fedAuthToken.accessToken != null, "fedAuthToken.accessToken cannot be null"); + Debug.Assert(fedAuthToken.AccessToken != null, "fedAuthToken.AccessToken cannot be null"); SqlClientEventSource.Log.TryTraceEvent(" Sending federated authentication token"); _physicalStateObj._outputMessageType = TdsEnums.MT_FEDAUTH; - byte[] accessToken = fedAuthToken.accessToken; + byte[] accessToken = fedAuthToken.AccessToken; // Send total length (length of token plus 4 bytes for the token length field) // If we were sending a nonce, this would include that length as well diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs index 8f0bb915c0..16386e1d9d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs @@ -13,6 +13,7 @@ using System.Security; using System.Security.Authentication; using System.Text; +using System.Text.Encodings; using Microsoft.Data.Common; using Microsoft.Data.Common.ConnectionString; @@ -128,23 +129,53 @@ internal sealed class SqlLoginAck internal uint tdsVersion; } + #nullable enable + internal sealed class SqlFedAuthInfo { - internal string spn; - internal string stsurl; + internal string Spn { get; } + internal string StsUrl { get; } + + internal SqlFedAuthInfo(string spn, string stsurl) + { + Spn = spn; + StsUrl = stsurl; + } + public override string ToString() { - return $"STSURL: {stsurl}, SPN: {spn}"; + return $"SPN: {Spn}, STSURL: {StsUrl}"; } } internal sealed class SqlFedAuthToken { - internal uint dataLen; - internal byte[] accessToken; - internal long expirationFileTime; + internal byte[] AccessToken { get; } + internal uint DataLen { get; } + internal long ExpirationFileTime { get; } + + internal SqlFedAuthToken( + byte[] accessToken, + long expirationFileTime) + { + AccessToken = accessToken; + DataLen = (uint)AccessToken.Length; + ExpirationFileTime = expirationFileTime; + } + + /// + /// Convert from a SqlAuthenticationToken. + /// + internal SqlFedAuthToken(SqlAuthenticationToken token) + { + AccessToken = Encoding.Unicode.GetBytes(token.AccessToken); + DataLen = (uint)AccessToken.Length; + ExpirationFileTime = token.ExpiresOn.ToFileTime(); + } } + #nullable disable + internal sealed class _SqlMetaData : SqlMetaDataPriv { [Flags] diff --git a/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj b/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj index 1b35e8f80d..f420579f9e 100644 --- a/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj +++ b/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj @@ -10,9 +10,15 @@ - - - - + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/Directory.Build.props index b470f899e1..a23f3f03a4 100644 --- a/src/Microsoft.Data.SqlClient/tests/Directory.Build.props +++ b/src/Microsoft.Data.SqlClient/tests/Directory.Build.props @@ -11,7 +11,6 @@ true Debug;Release; AnyCPU;x86;x64 - Project diff --git a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props index db17cb1e48..f03010f23a 100644 --- a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props +++ b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props @@ -14,9 +14,4 @@ - - - - - diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs index f354e6f806..208ddacd92 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs @@ -61,7 +61,7 @@ private void InvalidCombinationCheck(SqlCredential credential) [Fact] public async Task IsDummySqlAuthenticationProviderSetByDefault() { - var provider = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive); + var provider = SqlAuthenticationProviderManager.GetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive); Assert.NotNull(provider); Assert.Equal(typeof(DummySqlAuthenticationProvider), provider.GetType()); @@ -75,24 +75,24 @@ public async Task IsDummySqlAuthenticationProviderSetByDefault() public void CustomActiveDirectoryProviderTest() { SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(static (result) => Task.CompletedTask); - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); - Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + SqlAuthenticationProviderManager.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); + Assert.Same(authProvider, SqlAuthenticationProviderManager.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); } [Fact] public void CustomActiveDirectoryProviderTest_AppClientId() { SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(Guid.NewGuid().ToString()); - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); - Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + SqlAuthenticationProviderManager.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); + Assert.Same(authProvider, SqlAuthenticationProviderManager.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); } [Fact] public void CustomActiveDirectoryProviderTest_AppClientId_DeviceFlowCallback() { SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(static (result) => Task.CompletedTask, Guid.NewGuid().ToString()); - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); - Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + SqlAuthenticationProviderManager.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); + Assert.Same(authProvider, SqlAuthenticationProviderManager.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 7f6d8abd2c..7c54891ff4 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -111,6 +111,8 @@ + + Common @@ -128,12 +130,31 @@ TDS + + + + + + + + - - - - + + + + + + + + PreserveNewest diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs index 8168c26f8e..503b5468b9 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs @@ -22,7 +22,7 @@ public class SqlAuthenticationProviderTest [InlineData(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity)] public void DefaultAuthenticationProviders(SqlAuthenticationMethod method) { - Assert.IsType(SqlAuthenticationProvider.GetProvider(method)); + Assert.IsType(SqlAuthenticationProviderManager.GetProvider(method)); } #if NETFRAMEWORK @@ -33,7 +33,7 @@ public void DefaultAuthenticationProviders(SqlAuthenticationMethod method) [InlineData(SqlAuthenticationMethod.ActiveDirectoryInteractive)] public void DefaultAuthenticationProviders_Interactive(SqlAuthenticationMethod method) { - Assert.IsType(SqlAuthenticationProvider.GetProvider(method)); + Assert.IsType(SqlAuthenticationProviderManager.GetProvider(method)); } #endif diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index fbaf55db72..3b46cce657 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -303,6 +303,8 @@ + + Common @@ -323,11 +325,32 @@ - - - + + + + + + + + + + + + + + + + + @@ -346,7 +369,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs index 608c34c977..1cb7ac6cbf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -8,7 +8,6 @@ using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Azure.Identity; using Microsoft.Identity.Client; using Xunit; @@ -263,7 +262,7 @@ public static void TestADPasswordAuthentication() public static void TestCustomProviderAuthentication() { #pragma warning disable 0618 // Type or member is obsolete - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new CustomSqlAuthenticationProvider(DataTestUtility.ApplicationClientId)); + SqlAuthenticationProviderManager.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new CustomSqlAuthenticationProvider(DataTestUtility.ApplicationClientId)); #pragma warning restore 0618 // Type or member is obsolete // Connect to Azure DB with password and retrieve user name using custom authentication provider using (SqlConnection conn = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) @@ -283,7 +282,7 @@ public static void TestCustomProviderAuthentication() } // Reset to driver internal provider. #pragma warning disable 0618 // Type or member is obsolete - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new ActiveDirectoryAuthenticationProvider(DataTestUtility.ApplicationClientId)); + SqlAuthenticationProviderManager.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new ActiveDirectoryAuthenticationProvider(DataTestUtility.ApplicationClientId)); #pragma warning restore 0618 // Type or member is obsolete } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs index 3e7076d52d..9a6b4c7552 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs @@ -25,7 +25,6 @@ public static void TestDataClassificationResultSetRank() try { sqlConnection.Open(); - Assert.True(DataTestUtility.IsSupportedDataClassification()); CreateTable(sqlCommand); AddSensitivity(sqlCommand, rankEnabled: true); InsertData(sqlCommand); @@ -48,7 +47,6 @@ public static void TestDataClassificationResultSet() try { sqlConnection.Open(); - Assert.True(DataTestUtility.IsSupportedDataClassification()); CreateTable(sqlCommand); AddSensitivity(sqlCommand); InsertData(sqlCommand); diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Microsoft.Data.SqlClient.PerformanceTests.csproj b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Microsoft.Data.SqlClient.PerformanceTests.csproj index 24bcaab242..4b9a8d99f4 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Microsoft.Data.SqlClient.PerformanceTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Microsoft.Data.SqlClient.PerformanceTests.csproj @@ -15,12 +15,22 @@ - + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs index bf47188b34..72ed2e63a0 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs @@ -45,6 +45,9 @@ public class Config public static Config Load(string configPath = @"config.json") { + // Allow an override of the config path via an environment variable. + configPath = Environment.GetEnvironmentVariable("MDS_TEST_CONFIG") ?? configPath; + try { using (StreamReader r = new StreamReader(configPath)) diff --git a/tools/props/Versions.props b/tools/props/Versions.props index d82a04cce2..51e16268a7 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -57,19 +57,12 @@ $(MdsPackageVersion) + + + 7.0.0 $(AkvVersionDefault).$(BuildNumber)-dev - - - $(MdsPackageVersion) diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index 2e15d7076d..57cf2ae755 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -29,8 +29,6 @@ sqlclient microsoft.data.sqlclient - - @@ -46,8 +44,6 @@ - - @@ -61,8 +57,6 @@ - - @@ -76,8 +70,6 @@ - - diff --git a/tools/specs/Microsoft.SqlServer.Server.nuspec b/tools/specs/Microsoft.SqlServer.Server.nuspec index eec275b292..df75137b92 100644 --- a/tools/specs/Microsoft.SqlServer.Server.nuspec +++ b/tools/specs/Microsoft.SqlServer.Server.nuspec @@ -36,15 +36,20 @@ Microsoft.SqlServer.Server.Format + + - - - + + + - - - + + + diff --git a/tools/targets/GenerateSqlServerPackage.targets b/tools/targets/GenerateSqlServerPackage.targets index ea6655dcee..909c0da6e0 100644 --- a/tools/targets/GenerateSqlServerPackage.targets +++ b/tools/targets/GenerateSqlServerPackage.targets @@ -8,6 +8,6 @@ - + diff --git a/tools/targets/GenerateThisAssemblyCs.targets b/tools/targets/GenerateThisAssemblyCs.targets index 6230af8ae7..86e896013c 100644 --- a/tools/targets/GenerateThisAssemblyCs.targets +++ b/tools/targets/GenerateThisAssemblyCs.targets @@ -4,18 +4,31 @@ + + + +[assembly: System.CLSCompliant(true)] + + + + System + + -[assembly: System.CLSCompliant(true)] -namespace System +$(ThisAssemblyClsCompliantContent) +namespace $(ThisAssemblyNamespace) { -internal static class ThisAssembly -{ -internal const string InformationalVersion = "$(AssemblyFileVersion)"%3B -internal const string NuGetPackageVersion = "$(Version)"%3B -} + internal static class ThisAssembly + { + internal const string InformationalVersion = "$(AssemblyFileVersion)"%3B + internal const string NuGetPackageVersion = "$(Version)"%3B + } } From bdd1c133bf17f96d521d587fd63e26c4ee81de69 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:25:24 -0300 Subject: [PATCH 04/16] - Added obsolete Get/Set Provider methods on SqlAuthenticationProvider to maintain API for now. - Addressed review comments. --- .../build-abstractions-package-ci-stage.yml | 2 +- .../stages/build-azure-package-ci-stage.yml | 2 +- src/Directory.Packages.props | 2 +- .../doc/SqlAuthenticationMethod.xml | 5 + .../doc/SqlAuthenticationParameters.xml | 17 +- .../doc/SqlAuthenticationProvider.xml | 34 +++- .../SqlAuthenticationProviderException.xml | 5 + .../doc/SqlAuthenticationToken.xml | 7 +- .../Abstractions/src/Abstractions.csproj | 9 +- .../src/SqlAuthenticationParameters.cs | 12 +- .../src/SqlAuthenticationProvider.cs | 26 ++- .../src/SqlAuthenticationProviderInternal.cs | 150 ++++++++++++++++++ .../test/Abstractions.Test.csproj | 2 +- .../test/SqlAuthenticationMethodTest.cs | 4 + .../test/SqlAuthenticationParametersTest.cs | 10 ++ .../SqlAuthenticationProviderExceptionTest.cs | 8 +- .../test/SqlAuthenticationProviderTest.cs | 43 +++++ .../test/SqlAuthenticationTokenTest.cs | 8 +- .../ActiveDirectoryAuthenticationProvider.xml | 7 +- .../ActiveDirectoryAuthenticationProvider.cs | 12 +- .../Azure/src/AuthenticationException.cs | 4 + .../Azure/src/Azure.csproj | 9 +- .../Azure/test/Azure.Test.csproj | 2 +- .../SqlClient/SqlInternalConnectionTds.cs | 2 +- .../SqlClient/SqlInternalConnectionTds.cs | 2 +- .../SqlAuthenticationParametersBuilder.cs | 10 +- .../SqlAuthenticationProviderManager.cs | 11 +- .../FunctionalTests/AADAuthenticationTests.cs | 57 ++++++- .../ConnectivityTests/AADConnectionTest.cs | 2 +- 29 files changed, 425 insertions(+), 39 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs diff --git a/eng/pipelines/stages/build-abstractions-package-ci-stage.yml b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml index 2a9268e737..0d84dc6ccb 100644 --- a/eng/pipelines/stages/build-abstractions-package-ci-stage.yml +++ b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml @@ -85,7 +85,7 @@ stages: vmImage: windows-latest buildConfiguration: ${{ parameters.buildConfiguration }} netRuntimes: [net8.0, net9.0] - netFrameworkRuntimes: [net462, net47, net471, net472, net48, net481] + netFrameworkRuntimes: [net462] verbosity: ${{ parameters.verbosity }} # ------------------------------------------------------------------------ diff --git a/eng/pipelines/stages/build-azure-package-ci-stage.yml b/eng/pipelines/stages/build-azure-package-ci-stage.yml index c88a2f5db5..90beb30caf 100644 --- a/eng/pipelines/stages/build-azure-package-ci-stage.yml +++ b/eng/pipelines/stages/build-azure-package-ci-stage.yml @@ -118,7 +118,7 @@ stages: buildConfiguration: ${{ parameters.buildConfiguration }} displayNamePrefix: Win jobNameSuffix: windows - netFrameworkRuntimes: [net462, net47, net471, net472, net48, net481] + netFrameworkRuntimes: [net462] netRuntimes: [net8.0, net9.0] poolName: Azure Pipelines referenceType: ${{ parameters.referenceType }} diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 03ea8a9942..4d877d6deb 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -140,6 +140,6 @@ - + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationMethod.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationMethod.xml index cd15a65ec2..c804a8fb3a 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationMethod.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationMethod.xml @@ -1,3 +1,8 @@ + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml index 759919e48f..dfc2ca27da 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml @@ -1,4 +1,9 @@ - + + @@ -17,7 +22,7 @@ The user login name/ID, or null if not applicable. The user password, or null if not applicable. The connection ID. - The connection timeout, in seconds. + The authentication timeout, in seconds. Gets the authentication method. @@ -51,8 +56,16 @@ Gets the connection ID. The connection ID. + + Gets the authentication timeout value. + The authentication timeout, in seconds. + Gets the connection timeout value. + + This property is deprecated and will be removed in future versions. Use + AuthenticationTimeout instead. + The connection timeout, in seconds. diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml index 66c7f1a5bc..52cdba787a 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml @@ -1,4 +1,9 @@ - + + Defines the core behavior of authentication providers and provides a base class for derived classes. @@ -116,10 +121,33 @@ The authentication method. - Acquires a security token from the authority. - The Active Directory authentication parameters passed by the driver to authentication providers. + Acquires an access token from the authority. + The parameters passed to the provider by the driver. If any errors occur. Represents an asynchronous operation that returns the AD authentication token. + + Gets an authentication provider by method. + + This method is obsolete and will be removed in future versions. Use + the SqlAuthenticationProviderManager.GetProvider() method in the + Microsoft.Data.SqlClient package instead. + + The authentication method. + The authentication provider or if not found. + + + Sets an authentication provider by method. + + This method is obsolete and will be removed in future versions. Use + the SqlAuthenticationProviderManager.SetProvider() method in the + Microsoft.Data.SqlClient package instead. + + The authentication method. + The authentication provider. + + if the operation succeeded; otherwise, (for example, the existing provider disallows overriding). + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProviderException.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProviderException.xml index dbdf2966f9..648e4a7e2f 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProviderException.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProviderException.xml @@ -1,3 +1,8 @@ + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationToken.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationToken.xml index 11ce14db28..4eb9a78f52 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationToken.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationToken.xml @@ -1,4 +1,9 @@ - + + Represents an authentication token. diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj index 802a80068e..c1725ee867 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj @@ -42,8 +42,15 @@ <_Parameter1>true + + + CDP_BUILD_TYPE is a OneBranch governed pipeline variable. + --> + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationParameters.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationParameters.cs index c3d6dc4a2b..f0759fa94e 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationParameters.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationParameters.cs @@ -31,8 +31,14 @@ public sealed class SqlAuthenticationParameters /// public string DatabaseName { get; } + /// + public int AuthenticationTimeout { get; } + /// - public int ConnectionTimeout { get; } + [Obsolete( + "This property is deprecated and will be removed in future versions. " + + "Use AuthenticationTimeout instead.")] + public int ConnectionTimeout { get { return AuthenticationTimeout; } } /// public SqlAuthenticationParameters( @@ -44,7 +50,7 @@ public SqlAuthenticationParameters( string? userId, string? password, Guid connectionId, - int connectionTimeout) + int authenticationTimeout) { AuthenticationMethod = authenticationMethod; ServerName = serverName; @@ -54,6 +60,6 @@ public SqlAuthenticationParameters( UserId = userId; Password = password; ConnectionId = connectionId; - ConnectionTimeout = connectionTimeout; + AuthenticationTimeout = authenticationTimeout; } } diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs index 36d9bce99e..20d76f4342 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs @@ -5,7 +5,7 @@ namespace Microsoft.Data.SqlClient; /// -public abstract class SqlAuthenticationProvider +public abstract partial class SqlAuthenticationProvider { /// public virtual void BeforeLoad(SqlAuthenticationMethod authenticationMethod) { } @@ -18,4 +18,28 @@ public virtual void BeforeUnload(SqlAuthenticationMethod authenticationMethod) { /// public abstract Task AcquireTokenAsync(SqlAuthenticationParameters parameters); + + + /// + [Obsolete( + "This method is deprecated and will be removed in future versions. " + + "Use SqlAuthenticationProviderManager.GetProvider() from the " + + "Microsoft.Data.SqlClient package instead.")] + public static SqlAuthenticationProvider? GetProvider( + SqlAuthenticationMethod authenticationMethod) + { + return Internal.GetProvider(authenticationMethod); + } + + /// + [Obsolete( + "This method is deprecated and will be removed in future versions. " + + "Use SqlAuthenticationProviderManager.SetProvider() from the " + + "Microsoft.Data.SqlClient package instead.")] + public static bool SetProvider( + SqlAuthenticationMethod authenticationMethod, + SqlAuthenticationProvider provider) + { + return Internal.SetProvider(authenticationMethod, provider); + } } diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs new file mode 100644 index 0000000000..8db0c86c54 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs @@ -0,0 +1,150 @@ +// 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. + +using System.Reflection; + +namespace Microsoft.Data.SqlClient; + +/// +public abstract partial class SqlAuthenticationProvider +{ + private static class Internal + { + private static MethodInfo? _getProvider = null; + private static MethodInfo? _setProvider = null; + + static Internal() + { + // If the MDS package is present, load its + // SqlAuthenticationProviderManager class and get/set methods. + const string assemblyName = "Microsoft.Data.SqlClient"; + + try + { + // Try to load the MDS assembly. + var assembly = Assembly.Load(assemblyName); + + if (assembly is null) + { + // TODO: Logging + // SqlClientEventSource.Log.TryTraceEvent( + // nameof(SqlAuthenticationProviderManager) + + // $": Azure extension assembly={assemblyName} not found; " + + // "no default provider installed"); + return; + } + + // TODO(ADO-39845): Verify the assembly is signed by us? + + // TODO: Logging + // SqlClientEventSource.Log.TryTraceEvent( + // nameof(SqlAuthenticationProviderManager) + + // $": Azure extension assembly={assemblyName} found; " + + // "attempting to set as default provider for all Active " + + // "Directory authentication methods"); + + // Look for the manager class. + const string className = "Microsoft.Data.SqlClient.SqlAuthenticationProviderManager"; + var manager = assembly.GetType(className); + + if (manager is null) + { + // TODO: Logging + // SqlClientEventSource.Log.TryTraceEvent( + // nameof(SqlAuthenticationProviderManager) + + // $": Azure extension does not contain class={className}; " + + // "no default Active Directory provider installed"); + + return; + } + + // Get handles to the get/set static methods. + _getProvider = manager.GetMethod( + "GetProvider", + BindingFlags.Public | BindingFlags.Static); + _setProvider = manager.GetMethod( + "SetProvider", + BindingFlags.Public | BindingFlags.Static); + + // TODO: Logging + // SqlClientEventSource.Log.TryTraceEvent( + // nameof(SqlAuthenticationProviderManager) + + // $": Azure extension class={className} installed as " + + // "provider for all Active Directory authentication methods"); + } + // All of these exceptions mean we couldn't find the get/set + // methods. + catch (Exception ex) + when (ex is AmbiguousMatchException || + ex is BadImageFormatException || + ex is FileLoadException || + ex is FileNotFoundException) + { + // SqlClientEventSource.Log.TryTraceEvent( + // nameof(SqlAuthenticationProviderManager) + + // $": Azure extension assembly={assemblyName} not found or " + + // "not usable; no default provider installed; " + + // $"{ex.GetType().Name}: {ex.Message}"); + } + // Any other exceptions are fatal. + } + + public static SqlAuthenticationProvider? GetProvider( + SqlAuthenticationMethod authenticationMethod) + { + if (_getProvider is null) + { + return null; + } + + try + { + return _getProvider.Invoke(null, [authenticationMethod]) + as SqlAuthenticationProvider; + } + catch (Exception ex) + when (ex is InvalidOperationException || + ex is MemberAccessException || + ex is MethodAccessException || + ex is NotSupportedException || + ex is TargetInvocationException) + { + return null; + } + } + + public static bool SetProvider( + SqlAuthenticationMethod authenticationMethod, + SqlAuthenticationProvider provider) + { + if (_setProvider is null) + { + return false; + } + + try + { + bool? result = + _setProvider.Invoke(null, [authenticationMethod, provider]) + as bool?; + + if (!result.HasValue) + { + return false; + } + + return result.Value; + } + catch (Exception ex) + when (ex is InvalidOperationException || + ex is MemberAccessException || + ex is MethodAccessException || + ex is NotSupportedException || + ex is TargetInvocationException) + { + return false; + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj index 118b215737..ddc3ae6fad 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj @@ -1,7 +1,7 @@  - net462;net47;net471;net472;net48;net481;net8.0;net9.0 + net462;net8.0;net9.0 enable enable false diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationMethodTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationMethodTest.cs index da8739ac9f..434ad94177 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationMethodTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationMethodTest.cs @@ -1,3 +1,7 @@ +// 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. + namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test; public class SqlAuthenticationMethodTest diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationParametersTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationParametersTest.cs index 87a442f798..feecbbd831 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationParametersTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationParametersTest.cs @@ -1,3 +1,7 @@ +// 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. + namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test; public class SqlAuthenticationParametersTest @@ -36,7 +40,10 @@ public void Constructor_WithNulls() Assert.Null(parameters.UserId); Assert.Null(parameters.Password); Assert.Equal(id, parameters.ConnectionId); + Assert.Equal(timeout, parameters.AuthenticationTimeout); + #pragma warning disable CS0618 // Type or member is obsolete Assert.Equal(timeout, parameters.ConnectionTimeout); + #pragma warning restore CS0618 // Type or member is obsolete } // Verify that the properties are set correctly when nullable arguments @@ -73,6 +80,9 @@ public void Constructor_WithoutNulls() Assert.Equal(user, parameters.UserId); Assert.Equal(pass, parameters.Password); Assert.Equal(id, parameters.ConnectionId); + Assert.Equal(timeout, parameters.AuthenticationTimeout); + #pragma warning disable CS0618 // Type or member is obsolete Assert.Equal(timeout, parameters.ConnectionTimeout); + #pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderExceptionTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderExceptionTest.cs index 13a9798dc7..d8a5ecaa03 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderExceptionTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderExceptionTest.cs @@ -1,3 +1,7 @@ +// 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. + namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test; public class SqlAuthenticationProviderExceptionTest @@ -57,7 +61,7 @@ public void Constructor_Minimal_WithCausedBy() Assert.False(ex.ShouldRetry); Assert.Equal(0, ex.RetryPeriod); Assert.Equal(message, ex.Message); - Assert.Equal(causedBy, ex.InnerException); + Assert.Same(causedBy, ex.InnerException); } // Verify that all properties are set correctly. The causedBy argument is @@ -112,6 +116,6 @@ public void Constructor_All_WithCausedBy() Assert.Equal(shouldRetry, ex.ShouldRetry); Assert.Equal(retryPeriod, ex.RetryPeriod); Assert.Equal(message, ex.Message); - Assert.Equal(causedBy, ex.InnerException); + Assert.Same(causedBy, ex.InnerException); } } diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs new file mode 100644 index 0000000000..950f03ca8b --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs @@ -0,0 +1,43 @@ +// 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. + +namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test; + +public class SqlAuthenticationProviderTest +{ + [Fact] + public void GetProvider() + { + #pragma warning disable CS0618 // Type or member is obsolete + Assert.Null( + SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive)); + #pragma warning restore CS0618 // Type or member is obsolete + } + + private sealed class Provider : SqlAuthenticationProvider + { + public override bool IsSupported( + SqlAuthenticationMethod authenticationMethod) + { + return true; + } + + public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) + { + throw new NotImplementedException(); + } + } + + [Fact] + public void SetProvider() + { + #pragma warning disable CS0618 // Type or member is obsolete + Assert.False( + SqlAuthenticationProvider.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryIntegrated, + new Provider())); + #pragma warning restore CS0618 // Type or member is obsolete + } +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationTokenTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationTokenTest.cs index d90d25407b..6c419576db 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationTokenTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationTokenTest.cs @@ -1,3 +1,7 @@ +// 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. + namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test; public class SqlAuthenticationTokenTest @@ -22,7 +26,7 @@ public void Constructor_NullToken() string? token = null; var expiry = DateTimeOffset.UtcNow.AddHours(1); - var ex = Assert.Throws(() => + var ex = Assert.ThrowsAny(() => { new SqlAuthenticationToken(token!, expiry); }); @@ -37,7 +41,7 @@ public void Constructor_EmptyToken() string token = string.Empty; var expiry = DateTimeOffset.UtcNow.AddHours(1); - var ex = Assert.Throws(() => + var ex = Assert.ThrowsAny(() => { new SqlAuthenticationToken(token, expiry); }); diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/doc/ActiveDirectoryAuthenticationProvider.xml b/src/Microsoft.Data.SqlClient.Extensions/Azure/doc/ActiveDirectoryAuthenticationProvider.xml index a940011eb3..c680d0dd8b 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/doc/ActiveDirectoryAuthenticationProvider.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/doc/ActiveDirectoryAuthenticationProvider.xml @@ -1,4 +1,9 @@ - + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs index 7208fe3b06..cd9e2704bc 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs @@ -125,18 +125,18 @@ public override async Task AcquireTokenAsync(SqlAuthenti { using CancellationTokenSource cts = new(); - // Use Connection timeout value to cancel token acquire request - // after certain period of time. - if (parameters.ConnectionTimeout > 0) + // Use the authentication timeout value to cancel token acquire + // request after certain period of time. + if (parameters.AuthenticationTimeout > 0) { // Safely convert to milliseconds. - if (int.MaxValue / 1000 > parameters.ConnectionTimeout) + if (int.MaxValue / 1000 > parameters.AuthenticationTimeout) { cts.CancelAfter(int.MaxValue); } else { - cts.CancelAfter(parameters.ConnectionTimeout * 1000); + cts.CancelAfter(parameters.AuthenticationTimeout * 1000); } } @@ -291,7 +291,9 @@ previousPw is byte[] previousPwBytes && if (result == null) { + #pragma warning disable CS0618 // Type or member is obsolete result = await app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, parameters.Password) + #pragma warning disable CS0618 // Type or member is obsolete .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync(cancellationToken: cts.Token) .ConfigureAwait(false); diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AuthenticationException.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AuthenticationException.cs index 55a7abf2d2..b8d1eb5b5c 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AuthenticationException.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AuthenticationException.cs @@ -1,3 +1,7 @@ +// 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. + namespace Microsoft.Data.SqlClient.Extensions.Azure; // This exception is used internally by authentication providers to signal diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj index 52f2966c2b..50d70d3228 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj @@ -42,8 +42,15 @@ <_Parameter1>true + + + CDP_BUILD_TYPE is a OneBranch governed pipeline variable. + --> + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj index f391b7393c..72459b823e 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj @@ -1,7 +1,7 @@  - net462;net47;net471;net472;net48;net481;net8.0;net9.0 + net462;net8.0;net9.0 enable enable false diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 983f36693c..f7cb28bc57 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -2519,7 +2519,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) serverName: ConnectionOptions.DataSource, databaseName: ConnectionOptions.InitialCatalog) .WithConnectionId(_clientConnectionId) - .WithConnectionTimeout(ConnectionOptions.ConnectTimeout); + .WithAuthenticationTimeout(ConnectionOptions.ConnectTimeout); switch (ConnectionOptions.Authentication) { case SqlAuthenticationMethod.ActiveDirectoryIntegrated: diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index bc6e9c13de..5585690736 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -2574,7 +2574,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) serverName: ConnectionOptions.DataSource, databaseName: ConnectionOptions.InitialCatalog) .WithConnectionId(_clientConnectionId) - .WithConnectionTimeout(ConnectionOptions.ConnectTimeout); + .WithAuthenticationTimeout(ConnectionOptions.ConnectTimeout); switch (ConnectionOptions.Authentication) { case SqlAuthenticationMethod.ActiveDirectoryIntegrated: diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs index 5fdd7f020e..d95427d052 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs @@ -19,7 +19,7 @@ internal sealed class SqlAuthenticationParametersBuilder private string _userId; private string _password; private Guid _connectionId = Guid.NewGuid(); - private int _connectionTimeout = ADP.DefaultConnectionTimeout; + private int _authenticationTimeout = ADP.DefaultConnectionTimeout; /// /// Implicitly converts to . @@ -35,7 +35,7 @@ public static implicit operator SqlAuthenticationParameters(SqlAuthenticationPar userId: builder._userId, password: builder._password, connectionId: builder._connectionId, - connectionTimeout: builder._connectionTimeout); + authenticationTimeout: builder._authenticationTimeout); } /// @@ -84,11 +84,11 @@ public SqlAuthenticationParametersBuilder WithConnectionId(Guid connectionId) } /// - /// Set connection timeout. + /// Set authentication timeout. /// - public SqlAuthenticationParametersBuilder WithConnectionTimeout(int timeout) + public SqlAuthenticationParametersBuilder WithAuthenticationTimeout(int timeout) { - _connectionTimeout = timeout; + _authenticationTimeout = timeout; return this; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index ab3614a751..e2db8ed6fc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -58,7 +58,16 @@ static SqlAuthenticationProviderManager() // Try to load our Azure extension. var assembly = Assembly.Load(assemblyName); - // TODO: Verify the assembly is signed by us? + if (assembly is null) + { + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension assembly={assemblyName} not found; " + + "no default Active Directory provider installed"); + return; + } + + // TODO(ADO-39845): Verify the assembly is signed by us? SqlClientEventSource.Log.TryTraceEvent( nameof(SqlAuthenticationProviderManager) + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs index 208ddacd92..3215c17049 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs @@ -49,8 +49,8 @@ private void InvalidCombinationCheck(SqlCredential credential) Assert.Throws(() => connection.AccessToken = "SampleAccessToken"); } } - - #if NETFRAMEWORK + +#if NETFRAMEWORK // This test is only valid for .NET Framework /// @@ -69,7 +69,58 @@ public async Task IsDummySqlAuthenticationProviderSetByDefault() var token = await provider.AcquireTokenAsync(null); Assert.Equal(token.AccessToken, DummySqlAuthenticationProvider.DUMMY_TOKEN_STR); } - #endif +#endif + + // Verify that we can get and set providers via both the Abstractions + // package and Manager class interchangeably. + // + // This tests the dynamic assembly loading code in the Abstractions + // package. + [Fact] + public void Abstractions_And_Manager_GetSetProvider_Equivalent() + { + // Set via Manager, get via both. + DummySqlAuthenticationProvider provider1 = new(); + + Assert.True( + SqlAuthenticationProviderManager.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive, + provider1)); + + Assert.Same( + provider1, + SqlAuthenticationProviderManager.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive)); + + Assert.Same( + provider1, + #pragma warning disable CS0618 // Type or member is obsolete + SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive)); + #pragma warning restore CS0618 // Type or member is obsolete + + // Set via Abstractions, get via both. + DummySqlAuthenticationProvider provider2 = new(); + + Assert.True( + #pragma warning disable CS0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive, + provider2)); + #pragma warning restore CS0618 // Type or member is obsolete + + Assert.Same( + provider2, + SqlAuthenticationProviderManager.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive)); + + Assert.Same( + provider2, + #pragma warning disable CS0618 // Type or member is obsolete + SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive)); + #pragma warning restore CS0618 // Type or member is obsolete + } [Fact] public void CustomActiveDirectoryProviderTest() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs index 1cb7ac6cbf..2221148d59 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -34,7 +34,7 @@ public override async Task AcquireTokenAsync(SqlAuthenti _ = parameters.ConnectionId; var cts = new CancellationTokenSource(); - cts.CancelAfter(parameters.ConnectionTimeout * 1000); + cts.CancelAfter(parameters.AuthenticationTimeout * 1000); string[] scopes = new string[] { scope }; SecureString password = new SecureString(); From 522e5a45830cf91f864e3123cb07f865772187c9 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:34:36 -0300 Subject: [PATCH 05/16] - Fixed dummy auth provider tests on .NET Framework. - Added some config improvements to xUnit runners. --- .../FunctionalTests/AADAuthenticationTests.cs | 15 ++++++----- .../DummySqlAuthenticationProvider.cs | 25 +++++++++++++++++-- .../tests/FunctionalTests/app.config | 1 + .../xunit.runner.json | 5 +++- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs index 3215c17049..83f6dc921f 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs @@ -84,19 +84,22 @@ public void Abstractions_And_Manager_GetSetProvider_Equivalent() Assert.True( SqlAuthenticationProviderManager.SetProvider( - SqlAuthenticationMethod.ActiveDirectoryInteractive, + // GOTCHA: On .NET Framework, the dummy provider is already + // registered as the default provider for Interactive, so we + // use DeviceCodeFlow instead. + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, provider1)); Assert.Same( provider1, SqlAuthenticationProviderManager.GetProvider( - SqlAuthenticationMethod.ActiveDirectoryInteractive)); + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); Assert.Same( provider1, #pragma warning disable CS0618 // Type or member is obsolete SqlAuthenticationProvider.GetProvider( - SqlAuthenticationMethod.ActiveDirectoryInteractive)); + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); #pragma warning restore CS0618 // Type or member is obsolete // Set via Abstractions, get via both. @@ -105,20 +108,20 @@ public void Abstractions_And_Manager_GetSetProvider_Equivalent() Assert.True( #pragma warning disable CS0618 // Type or member is obsolete SqlAuthenticationProvider.SetProvider( - SqlAuthenticationMethod.ActiveDirectoryInteractive, + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, provider2)); #pragma warning restore CS0618 // Type or member is obsolete Assert.Same( provider2, SqlAuthenticationProviderManager.GetProvider( - SqlAuthenticationMethod.ActiveDirectoryInteractive)); + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); Assert.Same( provider2, #pragma warning disable CS0618 // Type or member is obsolete SqlAuthenticationProvider.GetProvider( - SqlAuthenticationMethod.ActiveDirectoryInteractive)); + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); #pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/DummySqlAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/DummySqlAuthenticationProvider.cs index bb5e2e2e52..b924b33e5e 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/DummySqlAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/DummySqlAuthenticationProvider.cs @@ -20,8 +20,29 @@ public class DummySqlAuthenticationProvider : SqlAuthenticationProvider public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.FromResult(new SqlAuthenticationToken(DUMMY_TOKEN_STR, new DateTimeOffset(DateTime.Now.AddHours(2)))); - // Supported authentication modes don't matter for dummy test, but added to demonstrate config file usage. + // We support two methods: + // + // ActiveDirectoryInteractive: + // We are installed as a default provider for this method by the + // app.config file for .NET Framework only. Default providers + // cannot be overridden by application code. + // + // ActiveDirectoryDeviceCodeFlow: + // We support this method, but are not installed as a default + // provider for it. This allows the tests to explicitly set us + // as the provider. + // public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) - => authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive; + { + switch (authenticationMethod) + { + case SqlAuthenticationMethod.ActiveDirectoryInteractive: + case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: + return true; + + default: + return false; + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config index fb7f63f65f..9fc08c65a7 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config @@ -7,6 +7,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/xunit.runner.json b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/xunit.runner.json index 8faf1479cc..42755c93ec 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/xunit.runner.json +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/xunit.runner.json @@ -2,5 +2,8 @@ "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "diagnosticMessages": true, "parallelizeAssembly": true, - "shadowCopy": false + "shadowCopy": false, + "printMaxEnumerableLength": 0, + "printMaxStringLength": 0, + "showLiveOutput": false } From d4c681a16ea46329d7d6c7601ccb8719005da16d Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:05:51 -0300 Subject: [PATCH 06/16] - Attempt to fix sensitive diagnostics tests having trouble with CI-SqlClient-Package runs. --- .../TracingTests/DiagnosticTest.cs | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs index 29272784a7..462c780634 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs @@ -246,9 +246,9 @@ public void ExecuteXmlReaderErrorTest() [Fact] public void ExecuteScalarAsyncTest() { - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke(async () => { - CollectStatisticsDiagnosticsAsync(async connectionString => + await CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -264,7 +264,7 @@ public void ExecuteScalarAsyncTest() conn.Open(); await cmd.ExecuteScalarAsync(); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -272,9 +272,9 @@ public void ExecuteScalarAsyncTest() [Fact] public void ExecuteScalarAsyncErrorTest() { - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke(async () => { - CollectStatisticsDiagnosticsAsync(async connectionString => + await CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -290,7 +290,7 @@ public void ExecuteScalarAsyncErrorTest() conn.Open(); await Assert.ThrowsAsync(() => cmd.ExecuteScalarAsync()); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -298,9 +298,9 @@ public void ExecuteScalarAsyncErrorTest() [Fact] public void ExecuteNonQueryAsyncTest() { - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke(async () => { - CollectStatisticsDiagnosticsAsync(async connectionString => + await CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -316,7 +316,7 @@ public void ExecuteNonQueryAsyncTest() conn.Open(); await cmd.ExecuteNonQueryAsync(); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -324,9 +324,9 @@ public void ExecuteNonQueryAsyncTest() [Fact] public void ExecuteNonQueryAsyncErrorTest() { - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke(async () => { - CollectStatisticsDiagnosticsAsync(async connectionString => + await CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -342,7 +342,7 @@ public void ExecuteNonQueryAsyncErrorTest() conn.Open(); await Assert.ThrowsAsync(() => cmd.ExecuteNonQueryAsync()); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -350,9 +350,9 @@ public void ExecuteNonQueryAsyncErrorTest() [Fact] public void ExecuteReaderAsyncTest() { - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke(async () => { - CollectStatisticsDiagnosticsAsync(async connectionString => + await CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -372,7 +372,7 @@ public void ExecuteReaderAsyncTest() // Read to end } } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -380,9 +380,9 @@ public void ExecuteReaderAsyncTest() [Fact] public void ExecuteReaderAsyncErrorTest() { - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke(async () => { - CollectStatisticsDiagnosticsAsync(async connectionString => + await CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -399,7 +399,7 @@ public void ExecuteReaderAsyncErrorTest() // @TODO: TestTdsServer should not throw on ExecuteReader, should throw on reader.Read await Assert.ThrowsAsync(() => cmd.ExecuteReaderAsync()); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -409,9 +409,9 @@ public void ExecuteReaderAsyncErrorTest() public void ExecuteXmlReaderAsyncTest() { // @TODO: TestTdsServer does not handle xml reader, so connect to a real server as a workaround - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke(async () => { - CollectStatisticsDiagnosticsAsync(async _ => + await CollectStatisticsDiagnosticsAsync(async _ => { #if NET await using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) @@ -431,7 +431,7 @@ public void ExecuteXmlReaderAsyncTest() // Read to end } } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -440,10 +440,9 @@ public void ExecuteXmlReaderAsyncTest() public void ExecuteXmlReaderAsyncErrorTest() { // @TODO: TestTdsServer does not handle xml reader, so connect to a real server as a workaround - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke(async () => { - - CollectStatisticsDiagnosticsAsync(async _ => + await CollectStatisticsDiagnosticsAsync(async _ => { #if NET await using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) @@ -466,7 +465,7 @@ public void ExecuteXmlReaderAsyncErrorTest() XmlReader reader = await cmd.ExecuteXmlReaderAsync(); await Assert.ThrowsAsync(() => reader.ReadAsync()); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -506,9 +505,9 @@ public void ConnectionOpenErrorTest() [Fact] public void ConnectionOpenAsyncTest() { - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke(async () => { - CollectStatisticsDiagnosticsAsync(async connectionString => + await CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection sqlConnection = new SqlConnection(connectionString)) @@ -518,7 +517,7 @@ public void ConnectionOpenAsyncTest() { await sqlConnection.OpenAsync(); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -526,9 +525,9 @@ public void ConnectionOpenAsyncTest() [Fact] public void ConnectionOpenAsyncErrorTest() { - RemoteExecutor.Invoke(() => + RemoteExecutor.Invoke(async () => { - CollectStatisticsDiagnosticsAsync(async _ => + await CollectStatisticsDiagnosticsAsync(async _ => { #if NET await using (SqlConnection sqlConnection = new SqlConnection(BadConnectionString)) @@ -538,7 +537,7 @@ public void ConnectionOpenAsyncErrorTest() { await Assert.ThrowsAsync(() => sqlConnection.OpenAsync()); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenError]).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenError]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } From 807bfa702da4c681047dbdda235cb5754a4610b1 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:38:44 -0300 Subject: [PATCH 07/16] - Replaced Console output with xUnit output to avoid confusing interleaved output. --- .../TracingTests/DiagnosticTest.cs | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs index 462c780634..4c9d16e297 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs @@ -14,11 +14,14 @@ using Microsoft.SqlServer.TDS.Servers; using Microsoft.SqlServer.TDS.SQLBatch; using Xunit; +using Xunit.Abstractions; using System.Runtime.CompilerServices; using System; using System.Data; using Microsoft.DotNet.RemoteExecutor; +#nullable enable + namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public class DiagnosticTest @@ -35,6 +38,13 @@ public class DiagnosticTest private const string WriteConnectionCloseAfter = "Microsoft.Data.SqlClient.WriteConnectionCloseAfter"; private const string WriteConnectionCloseError = "Microsoft.Data.SqlClient.WriteConnectionCloseError"; + private readonly ITestOutputHelper _output; + + public DiagnosticTest(ITestOutputHelper output) + { + _output = output; + } + [Fact] public void ExecuteScalarTest() { @@ -542,7 +552,7 @@ await CollectStatisticsDiagnosticsAsync(async _ => }).Dispose(); } - private static void CollectStatisticsDiagnostics(Action sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") + private void CollectStatisticsDiagnostics(Action sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") { bool statsLogged = false; bool operationHasError = false; @@ -730,12 +740,12 @@ private static void CollectStatisticsDiagnostics(Action sqlOperation, st using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { - Console.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); + _output.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); using (var server = new TdsServer(new DiagnosticsQueryEngine(), new TdsServerArguments())) { server.Start(methodName); - Console.WriteLine(string.Format("Test: {0} Started Server", methodName)); + _output.WriteLine(string.Format("Test: {0} Started Server", methodName)); var connectionString = new SqlConnectionStringBuilder { @@ -745,7 +755,7 @@ private static void CollectStatisticsDiagnostics(Action sqlOperation, st sqlOperation(connectionString); - Console.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName)); + _output.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName)); Assert.True(statsLogged); @@ -755,14 +765,14 @@ private static void CollectStatisticsDiagnostics(Action sqlOperation, st Assert.True(diagnosticListenerObserver.HasReceivedDiagnostic(expected), $"Missing diagnostic '{expected}'"); } - Console.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); + _output.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); } - Console.WriteLine(string.Format("Test: {0} Server Disposed", methodName)); + _output.WriteLine(string.Format("Test: {0} Server Disposed", methodName)); } - Console.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); + _output.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); } - private static async Task CollectStatisticsDiagnosticsAsync(Func sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") + private async Task CollectStatisticsDiagnosticsAsync(Func sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") { bool statsLogged = false; bool operationHasError = false; @@ -934,11 +944,11 @@ private static async Task CollectStatisticsDiagnosticsAsync(Func s diagnosticListenerObserver.Enable(); using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { - Console.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); + _output.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); using (var server = new TdsServer(new DiagnosticsQueryEngine(), new TdsServerArguments())) { server.Start(methodName); - Console.WriteLine(string.Format("Test: {0} Started Server", methodName)); + _output.WriteLine(string.Format("Test: {0} Started Server", methodName)); var connectionString = new SqlConnectionStringBuilder { @@ -947,7 +957,7 @@ private static async Task CollectStatisticsDiagnosticsAsync(Func s }.ConnectionString; await sqlOperation(connectionString); - Console.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName)); + _output.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName)); Assert.True(statsLogged); @@ -957,20 +967,20 @@ private static async Task CollectStatisticsDiagnosticsAsync(Func s Assert.True(diagnosticListenerObserver.HasReceivedDiagnostic(expected), $"Missing diagnostic '{expected}'"); } - Console.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); + _output.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); } - Console.WriteLine(string.Format("Test: {0} Server Disposed", methodName)); + _output.WriteLine(string.Format("Test: {0} Server Disposed", methodName)); } - Console.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); + _output.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); } private static T GetPropertyValueFromType(object obj, string propName) { Type type = obj.GetType(); - PropertyInfo pi = type.GetRuntimeProperty(propName); + PropertyInfo? pi = type.GetRuntimeProperty(propName); - var propertyValue = pi.GetValue(obj); - return (T)propertyValue; + var propertyValue = pi?.GetValue(obj); + return (T)propertyValue!; } } From 39d2d10aff9f9abed7cc9bce7070eee68b9b3a91 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:45:33 -0300 Subject: [PATCH 08/16] Revert "- Replaced Console output with xUnit output to avoid confusing interleaved output." This reverts commit 807bfa702da4c681047dbdda235cb5754a4610b1. --- .../TracingTests/DiagnosticTest.cs | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs index 4c9d16e297..462c780634 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs @@ -14,14 +14,11 @@ using Microsoft.SqlServer.TDS.Servers; using Microsoft.SqlServer.TDS.SQLBatch; using Xunit; -using Xunit.Abstractions; using System.Runtime.CompilerServices; using System; using System.Data; using Microsoft.DotNet.RemoteExecutor; -#nullable enable - namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public class DiagnosticTest @@ -38,13 +35,6 @@ public class DiagnosticTest private const string WriteConnectionCloseAfter = "Microsoft.Data.SqlClient.WriteConnectionCloseAfter"; private const string WriteConnectionCloseError = "Microsoft.Data.SqlClient.WriteConnectionCloseError"; - private readonly ITestOutputHelper _output; - - public DiagnosticTest(ITestOutputHelper output) - { - _output = output; - } - [Fact] public void ExecuteScalarTest() { @@ -552,7 +542,7 @@ await CollectStatisticsDiagnosticsAsync(async _ => }).Dispose(); } - private void CollectStatisticsDiagnostics(Action sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") + private static void CollectStatisticsDiagnostics(Action sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") { bool statsLogged = false; bool operationHasError = false; @@ -740,12 +730,12 @@ private void CollectStatisticsDiagnostics(Action sqlOperation, string[] using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { - _output.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); + Console.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); using (var server = new TdsServer(new DiagnosticsQueryEngine(), new TdsServerArguments())) { server.Start(methodName); - _output.WriteLine(string.Format("Test: {0} Started Server", methodName)); + Console.WriteLine(string.Format("Test: {0} Started Server", methodName)); var connectionString = new SqlConnectionStringBuilder { @@ -755,7 +745,7 @@ private void CollectStatisticsDiagnostics(Action sqlOperation, string[] sqlOperation(connectionString); - _output.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName)); + Console.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName)); Assert.True(statsLogged); @@ -765,14 +755,14 @@ private void CollectStatisticsDiagnostics(Action sqlOperation, string[] Assert.True(diagnosticListenerObserver.HasReceivedDiagnostic(expected), $"Missing diagnostic '{expected}'"); } - _output.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); + Console.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); } - _output.WriteLine(string.Format("Test: {0} Server Disposed", methodName)); + Console.WriteLine(string.Format("Test: {0} Server Disposed", methodName)); } - _output.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); + Console.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); } - private async Task CollectStatisticsDiagnosticsAsync(Func sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") + private static async Task CollectStatisticsDiagnosticsAsync(Func sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") { bool statsLogged = false; bool operationHasError = false; @@ -944,11 +934,11 @@ private async Task CollectStatisticsDiagnosticsAsync(Func sqlOpera diagnosticListenerObserver.Enable(); using (DiagnosticListener.AllListeners.Subscribe(diagnosticListenerObserver)) { - _output.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); + Console.WriteLine(string.Format("Test: {0} Enabled Listeners", methodName)); using (var server = new TdsServer(new DiagnosticsQueryEngine(), new TdsServerArguments())) { server.Start(methodName); - _output.WriteLine(string.Format("Test: {0} Started Server", methodName)); + Console.WriteLine(string.Format("Test: {0} Started Server", methodName)); var connectionString = new SqlConnectionStringBuilder { @@ -957,7 +947,7 @@ private async Task CollectStatisticsDiagnosticsAsync(Func sqlOpera }.ConnectionString; await sqlOperation(connectionString); - _output.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName)); + Console.WriteLine(string.Format("Test: {0} SqlOperation Successful", methodName)); Assert.True(statsLogged); @@ -967,20 +957,20 @@ private async Task CollectStatisticsDiagnosticsAsync(Func sqlOpera Assert.True(diagnosticListenerObserver.HasReceivedDiagnostic(expected), $"Missing diagnostic '{expected}'"); } - _output.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); + Console.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); } - _output.WriteLine(string.Format("Test: {0} Server Disposed", methodName)); + Console.WriteLine(string.Format("Test: {0} Server Disposed", methodName)); } - _output.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); + Console.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); } private static T GetPropertyValueFromType(object obj, string propName) { Type type = obj.GetType(); - PropertyInfo? pi = type.GetRuntimeProperty(propName); + PropertyInfo pi = type.GetRuntimeProperty(propName); - var propertyValue = pi?.GetValue(obj); - return (T)propertyValue!; + var propertyValue = pi.GetValue(obj); + return (T)propertyValue; } } From 278c87279373f5805a9ae157806d27fd6a21d011 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:45:50 -0300 Subject: [PATCH 09/16] Revert "- Attempt to fix sensitive diagnostics tests having trouble with CI-SqlClient-Package runs." This reverts commit d4c681a16ea46329d7d6c7601ccb8719005da16d. --- .../TracingTests/DiagnosticTest.cs | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs index 462c780634..29272784a7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs @@ -246,9 +246,9 @@ public void ExecuteXmlReaderErrorTest() [Fact] public void ExecuteScalarAsyncTest() { - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(() => { - await CollectStatisticsDiagnosticsAsync(async connectionString => + CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -264,7 +264,7 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => conn.Open(); await cmd.ExecuteScalarAsync(); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -272,9 +272,9 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => [Fact] public void ExecuteScalarAsyncErrorTest() { - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(() => { - await CollectStatisticsDiagnosticsAsync(async connectionString => + CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -290,7 +290,7 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => conn.Open(); await Assert.ThrowsAsync(() => cmd.ExecuteScalarAsync()); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -298,9 +298,9 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => [Fact] public void ExecuteNonQueryAsyncTest() { - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(() => { - await CollectStatisticsDiagnosticsAsync(async connectionString => + CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -316,7 +316,7 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => conn.Open(); await cmd.ExecuteNonQueryAsync(); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -324,9 +324,9 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => [Fact] public void ExecuteNonQueryAsyncErrorTest() { - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(() => { - await CollectStatisticsDiagnosticsAsync(async connectionString => + CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -342,7 +342,7 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => conn.Open(); await Assert.ThrowsAsync(() => cmd.ExecuteNonQueryAsync()); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -350,9 +350,9 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => [Fact] public void ExecuteReaderAsyncTest() { - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(() => { - await CollectStatisticsDiagnosticsAsync(async connectionString => + CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -372,7 +372,7 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => // Read to end } } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -380,9 +380,9 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => [Fact] public void ExecuteReaderAsyncErrorTest() { - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(() => { - await CollectStatisticsDiagnosticsAsync(async connectionString => + CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection conn = new SqlConnection(connectionString)) @@ -399,7 +399,7 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => // @TODO: TestTdsServer should not throw on ExecuteReader, should throw on reader.Read await Assert.ThrowsAsync(() => cmd.ExecuteReaderAsync()); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -409,9 +409,9 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => public void ExecuteXmlReaderAsyncTest() { // @TODO: TestTdsServer does not handle xml reader, so connect to a real server as a workaround - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(() => { - await CollectStatisticsDiagnosticsAsync(async _ => + CollectStatisticsDiagnosticsAsync(async _ => { #if NET await using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) @@ -431,7 +431,7 @@ await CollectStatisticsDiagnosticsAsync(async _ => // Read to end } } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -440,9 +440,10 @@ await CollectStatisticsDiagnosticsAsync(async _ => public void ExecuteXmlReaderAsyncErrorTest() { // @TODO: TestTdsServer does not handle xml reader, so connect to a real server as a workaround - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(() => { - await CollectStatisticsDiagnosticsAsync(async _ => + + CollectStatisticsDiagnosticsAsync(async _ => { #if NET await using (SqlConnection conn = new SqlConnection(DataTestUtility.TCPConnectionString)) @@ -465,7 +466,7 @@ await CollectStatisticsDiagnosticsAsync(async _ => XmlReader reader = await cmd.ExecuteXmlReaderAsync(); await Assert.ThrowsAsync(() => reader.ReadAsync()); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -505,9 +506,9 @@ public void ConnectionOpenErrorTest() [Fact] public void ConnectionOpenAsyncTest() { - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(() => { - await CollectStatisticsDiagnosticsAsync(async connectionString => + CollectStatisticsDiagnosticsAsync(async connectionString => { #if NET await using (SqlConnection sqlConnection = new SqlConnection(connectionString)) @@ -517,7 +518,7 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => { await sqlConnection.OpenAsync(); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -525,9 +526,9 @@ await CollectStatisticsDiagnosticsAsync(async connectionString => [Fact] public void ConnectionOpenAsyncErrorTest() { - RemoteExecutor.Invoke(async () => + RemoteExecutor.Invoke(() => { - await CollectStatisticsDiagnosticsAsync(async _ => + CollectStatisticsDiagnosticsAsync(async _ => { #if NET await using (SqlConnection sqlConnection = new SqlConnection(BadConnectionString)) @@ -537,7 +538,7 @@ await CollectStatisticsDiagnosticsAsync(async _ => { await Assert.ThrowsAsync(() => sqlConnection.OpenAsync()); } - }, [WriteConnectionOpenBefore, WriteConnectionOpenError]); + }, [WriteConnectionOpenBefore, WriteConnectionOpenError]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } From 5d5fa8440cefa1f2dc12fee9b82d3716008a56df Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:03:48 -0300 Subject: [PATCH 10/16] - Addressed review comments. - Reverted SqlAuthenticationParameters authenticationTimeout constructor argument to its original name connectionTimeout and added docs explaining why. --- .../doc/SqlAuthenticationParameters.xml | 16 +++++-- .../src/SqlAuthenticationMethod.cs | 1 + .../src/SqlAuthenticationParameters.cs | 8 +++- .../src/SqlAuthenticationProvider.cs | 2 + .../src/SqlAuthenticationProviderInternal.cs | 19 +++++++- .../test/Abstractions.Test.csproj | 1 + .../test/SqlAuthenticationProviderTest.cs | 44 +++++++++++++------ .../Azure/test/Azure.Test.csproj | 1 + .../SqlClient/SqlInternalConnectionTds.cs | 14 +++--- .../SqlClient/SqlInternalConnectionTds.cs | 12 ++--- 10 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml index dfc2ca27da..731fd316e7 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml @@ -22,7 +22,18 @@ See the LICENSE file in the project root for more information. The user login name/ID, or null if not applicable. The user password, or null if not applicable. The connection ID. - The authentication timeout, in seconds. + + The authentication timeout, in seconds. The overall connection timeout + is managed by the driver; this timeout only applies to authentication. + + The value supplied here is used to set the AuthenticationTimeout + property. + + Note that C# doesn't have a mechanism to deprecate constructor parameter + names in a backwards-compatible fashion, so we must keep the old name + (connectionTimeout) even though this parameter should really be named + authenticationTimeout + Gets the authentication method. @@ -61,12 +72,11 @@ See the LICENSE file in the project root for more information. The authentication timeout, in seconds. - Gets the connection timeout value. + Gets the AuthenticationTimeout property value. This property is deprecated and will be removed in future versions. Use AuthenticationTimeout instead. - The connection timeout, in seconds. diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationMethod.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationMethod.cs index a5b6708400..e5413110ae 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationMethod.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationMethod.cs @@ -15,6 +15,7 @@ public enum SqlAuthenticationMethod : int /// [Obsolete("ActiveDirectoryPassword is deprecated, use a more secure authentication method. See https://aka.ms/SqlClientEntraIDAuthentication for more details.")] + // Obsoleted with MDS 7.0.0; to be removed at least 2 major versions later. ActiveDirectoryPassword, /// diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationParameters.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationParameters.cs index f0759fa94e..04bd5aa939 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationParameters.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationParameters.cs @@ -38,6 +38,7 @@ public sealed class SqlAuthenticationParameters [Obsolete( "This property is deprecated and will be removed in future versions. " + "Use AuthenticationTimeout instead.")] + // Obsoleted with MDS 7.0.0; to be removed at least 2 major versions later. public int ConnectionTimeout { get { return AuthenticationTimeout; } } /// @@ -50,7 +51,10 @@ public SqlAuthenticationParameters( string? userId, string? password, Guid connectionId, - int authenticationTimeout) + // This parameter should really be named authenticationTimeout, but we + // must keep the old name for backwards compatibility. The value + // supplied here is used to set the AuthenticationTimeout property. + int connectionTimeout) { AuthenticationMethod = authenticationMethod; ServerName = serverName; @@ -60,6 +64,6 @@ public SqlAuthenticationParameters( UserId = userId; Password = password; ConnectionId = connectionId; - AuthenticationTimeout = authenticationTimeout; + AuthenticationTimeout = connectionTimeout; } } diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs index 20d76f4342..c7df239f38 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs @@ -25,6 +25,7 @@ public virtual void BeforeUnload(SqlAuthenticationMethod authenticationMethod) { "This method is deprecated and will be removed in future versions. " + "Use SqlAuthenticationProviderManager.GetProvider() from the " + "Microsoft.Data.SqlClient package instead.")] + // Obsoleted with MDS 7.0.0; to be removed at least 2 major versions later. public static SqlAuthenticationProvider? GetProvider( SqlAuthenticationMethod authenticationMethod) { @@ -36,6 +37,7 @@ public virtual void BeforeUnload(SqlAuthenticationMethod authenticationMethod) { "This method is deprecated and will be removed in future versions. " + "Use SqlAuthenticationProviderManager.SetProvider() from the " + "Microsoft.Data.SqlClient package instead.")] + // Obsoleted with MDS 7.0.0; to be removed at least 2 major versions later. public static bool SetProvider( SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs index 8db0c86c54..2423b2e384 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs @@ -9,11 +9,17 @@ namespace Microsoft.Data.SqlClient; /// public abstract partial class SqlAuthenticationProvider { + // This class implements the obsolete static GetProvider and SetProvider + // methods by using reflection to call into the Microsoft.Data.SqlClient + // package's SqlAuthenticationProviderManager class, if that assembly is + // present. private static class Internal { + // Handles to the reflected get/set methods. private static MethodInfo? _getProvider = null; private static MethodInfo? _setProvider = null; + // Static construction performs the reflection lookups. static Internal() { // If the MDS package is present, load its @@ -66,7 +72,7 @@ static Internal() _setProvider = manager.GetMethod( "SetProvider", BindingFlags.Public | BindingFlags.Static); - + // TODO: Logging // SqlClientEventSource.Log.TryTraceEvent( // nameof(SqlAuthenticationProviderManager) + @@ -90,6 +96,11 @@ ex is FileLoadException || // Any other exceptions are fatal. } + // Call the reflected GetProvider method. + // + // Returns null if reflection failed or any exceptions occur. + // Otherwise, returns as the reflected method does. + // public static SqlAuthenticationProvider? GetProvider( SqlAuthenticationMethod authenticationMethod) { @@ -114,6 +125,12 @@ ex is NotSupportedException || } } + + // Call the reflected SetProvider method. + // + // Returns false if reflection failed, invocation fails, or any + // exceptions occur. Otherwise, returns as the reflected method does. + // public static bool SetProvider( SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj index ddc3ae6fad..053804342e 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs index 950f03ca8b..6dada18678 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs @@ -2,20 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Reflection; + namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test; +// Tests for the obsolete SqlAuthenticationProvider.GetProvider and SetProvider +// methods. public class SqlAuthenticationProviderTest { - [Fact] - public void GetProvider() - { - #pragma warning disable CS0618 // Type or member is obsolete - Assert.Null( - SqlAuthenticationProvider.GetProvider( - SqlAuthenticationMethod.ActiveDirectoryInteractive)); - #pragma warning restore CS0618 // Type or member is obsolete - } - + // A dummy provider that supports all authentication methods. private sealed class Provider : SqlAuthenticationProvider { public override bool IsSupported( @@ -24,20 +19,43 @@ public override bool IsSupported( return true; } - public override Task AcquireTokenAsync(SqlAuthenticationParameters parameters) + public override Task AcquireTokenAsync( + SqlAuthenticationParameters parameters) { throw new NotImplementedException(); } } + // Test that GetProvider fails predictably when the MDS assembly can't be + // found. + [Fact] + public void GetProvider() + { + // Confirm that the MDS assembly is indeed not present. + Assert.Throws( + () => Assembly.Load("Microsoft.Data.SqlClient")); + +#pragma warning disable CS0618 // Type or member is obsolete + Assert.Null( + SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive)); +#pragma warning restore CS0618 // Type or member is obsolete + } + + // Test that SetProvider fails predictably when the MDS assembly can't be + // found. [Fact] public void SetProvider() { - #pragma warning disable CS0618 // Type or member is obsolete + // Confirm that the MDS assembly is indeed not present. + Assert.Throws( + () => Assembly.Load("Microsoft.Data.SqlClient")); + +#pragma warning disable CS0618 // Type or member is obsolete Assert.False( SqlAuthenticationProvider.SetProvider( SqlAuthenticationMethod.ActiveDirectoryIntegrated, new Provider())); - #pragma warning restore CS0618 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete } } diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj index 72459b823e..ad94e9b4b1 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 347c400545..3f8eed9dea 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -2488,8 +2488,8 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // retry period is not specified by the provider. const int defaultRetryPeriod = 100; - // Number of retry attempts we are willing to perform. - const int maxRetryAttempts = 1; + // Number of attempts we are willing to perform. + const int maxAttempts = 1; // Username to use in error messages. string? username = null; @@ -2502,7 +2502,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // We will perform retries if the provider indicates an error that // is retryable. - for (int retryAttempt = 0; retryAttempt <= maxRetryAttempts; ++retryAttempt) + for (int attempt = 0; attempt <= maxAttempts; ++attempt) { try { @@ -2639,7 +2639,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) if (_timeout.IsExpired || _timeout.MillisecondsRemaining <= 0) { // No, so we throw. - SqlClientEventSource.Log.TryTraceEvent(" Retry Attempt: {0}, Timeout: {1}", retryAttempt, ex.FailureCode); + SqlClientEventSource.Log.TryTraceEvent(" Attempt: {0}, Timeout: {1}", attempt, ex.FailureCode); throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), ex.FailureCode, ex); } @@ -2648,10 +2648,10 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) int retryPeriod = ex.RetryPeriod > 0 ? ex.RetryPeriod - : defaultRetryPeriod * (2 ^ retryAttempt); + : defaultRetryPeriod * (2 ^ attempt); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Retry Attempt: {1}, sleeping {2}[Milliseconds]", ObjectID, retryAttempt, retryPeriod); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Retry Attempt: {1}, remaining {2}[Milliseconds]", ObjectID, retryAttempt, _timeout.MillisecondsRemaining); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Attempt: {1}, sleeping {2}[Milliseconds]", ObjectID, attempt, retryPeriod); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Attempt: {1}, remaining {2}[Milliseconds]", ObjectID, attempt, _timeout.MillisecondsRemaining); // Sleep for the desired period. Thread.Sleep(retryPeriod); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index e63376f7ab..8b8e7e4f88 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -2545,7 +2545,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) const int defaultRetryPeriod = 100; // Number of retry attempts we are willing to perform. - const int maxRetryAttempts = 1; + const int maxAttempts = 1; // Username to use in error messages. string? username = null; @@ -2558,7 +2558,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // We will perform retries if the provider indicates an error that // is retryable. - for (int retryAttempt = 0; retryAttempt < maxRetryAttempts; ++retryAttempt) + for (int attempt = 0; attempt < maxAttempts; ++attempt) { try { @@ -2686,7 +2686,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) if (_timeout.IsExpired || _timeout.MillisecondsRemaining <= 0) { // No, so we throw. - SqlClientEventSource.Log.TryTraceEvent(" Retry Attempt: {0}, Timeout: {1}", retryAttempt, ex.FailureCode); + SqlClientEventSource.Log.TryTraceEvent(" Attempt: {0}, Timeout: {1}", attempt, ex.FailureCode); throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), ex.FailureCode, ex); } @@ -2695,10 +2695,10 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) int retryPeriod = ex.RetryPeriod > 0 ? ex.RetryPeriod - : defaultRetryPeriod * (2 ^ retryAttempt); + : defaultRetryPeriod * (2 ^ attempt); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Retry Attempt: {1}, sleeping {2}[Milliseconds]", ObjectID, retryAttempt, retryPeriod); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Retry Attempt: {1}, remaining {2}[Milliseconds]", ObjectID, retryAttempt, _timeout.MillisecondsRemaining); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Attempt: {1}, sleeping {2}[Milliseconds]", ObjectID, attempt, retryPeriod); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Attempt: {1}, remaining {2}[Milliseconds]", ObjectID, attempt, _timeout.MillisecondsRemaining); // Sleep for the desired period. Thread.Sleep(retryPeriod); From 65091f46051f0237c9ea52e8c6b69835f4f60116 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:54:59 -0300 Subject: [PATCH 11/16] - Fixed build error related to SqlAuthenticationParameters. --- src/Microsoft.Data.SqlClient.sln | 3 --- .../Data/SqlClient/SqlAuthenticationParametersBuilder.cs | 2 +- .../tests/StressTests/Directory.Build.props | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index 5a64096ba6..d5efe9e4b9 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -774,14 +774,11 @@ Global {B21E7C94-D805-427E-928A-8DE8EA2F08CC} = {210228A5-979A-DE06-EE1F-B35C65E1583C} {59667E4C-0BD2-9F48-FB50-9E55DD8B1011} = {556B486E-F9B0-7EA9-6A25-DA560C312761} {04ACBF75-CFF2-41AB-B652-776BC0533490} = {59667E4C-0BD2-9F48-FB50-9E55DD8B1011} -<<<<<<< HEAD {A20114E1-82D8-903A-C389-726EB4FD943F} = {19F1F1E5-3013-7660-661A-2A15F7D606C1} {0D2F834B-6D91-18D0-3F09-672D448751BD} = {A20114E1-82D8-903A-C389-726EB4FD943F} {20C16035-7293-45AC-8217-9B86A389E571} = {0D2F834B-6D91-18D0-3F09-672D448751BD} {5AF52CDD-DF78-3712-7516-5B49F94F9491} = {A20114E1-82D8-903A-C389-726EB4FD943F} {A7C0B6C7-A4B2-43CA-921B-D4FAEE86ACBC} = {5AF52CDD-DF78-3712-7516-5B49F94F9491} -======= ->>>>>>> feat/azure-split EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {01D48116-37A2-4D33-B9EC-94793C702431} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs index d95427d052..a9863ee2ac 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs @@ -35,7 +35,7 @@ public static implicit operator SqlAuthenticationParameters(SqlAuthenticationPar userId: builder._userId, password: builder._password, connectionId: builder._connectionId, - authenticationTimeout: builder._authenticationTimeout); + connectionTimeout: builder._authenticationTimeout); } /// diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props index 66fbacae6c..26de699227 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props @@ -6,7 +6,7 @@ - net462;net47;net471;net472;net48;net481;net8.0;net9.0 + net462;net8.0;net9.0 Microsoft.Data.SqlClient.Extensions.Abstractions - $(AssemblyName) + + + Microsoft.Data.SqlClient.Extensions.Azure - $(AssemblyName) + + +