From d576833254a0174df108ff91b875eac347a03921 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/13] Abstractions Package - C# project changes (#3626) --- .editorconfig | 2 +- BUILDGUIDE.md | 81 +++-- Directory.Packages.props | 8 + NuGet.config | 13 +- build.proj | 161 ++++++++-- .../jobs/build-signed-package-job.yml | 1 + .../templates/jobs/ci-build-nugets-job.yml | 2 + .../steps/generate-nuget-package-step.yml | 10 +- .../libraries/ci-build-variables.yml | 10 + eng/pipelines/libraries/common-variables.yml | 12 +- src/Directory.Build.props | 1 - .../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 | 45 ++- .../add-ons/Directory.Build.props | 1 - .../ref/Microsoft.Data.SqlClient.csproj | 16 + .../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, 941 insertions(+), 171 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/Directory.Packages.props b/Directory.Packages.props index c339554838..28929a939a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,6 +5,14 @@ + + + + + + diff --git a/NuGet.config b/NuGet.config index 3233e60161..6c4f535818 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,7 +1,18 @@  + + - + + + diff --git a/build.proj b/build.proj index 330a76be56..62154461a7 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,11 +214,15 @@ - + - + @@ -166,12 +240,17 @@ - + - - + + @@ -180,7 +259,9 @@ - + @@ -189,12 +270,18 @@ - + - + @@ -203,7 +290,10 @@ - + @@ -360,14 +450,14 @@ - + - - - - - - + + + + + + @@ -402,7 +492,10 @@ - + @@ -412,7 +505,9 @@ - + @@ -422,7 +517,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 a482761610..486211a74f 100644 --- a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml @@ -55,6 +55,7 @@ jobs: buildConfiguration: Release OutputDirectory: $(artifactDirectory) installNuget: false + 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 7b72820534..0a0b62f7fd 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -60,6 +60,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 @@ -69,6 +70,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 0754735e28..cc6518bb39 100644 --- a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml +++ b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml @@ -35,11 +35,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 }}: diff --git a/eng/pipelines/libraries/ci-build-variables.yml b/eng/pipelines/libraries/ci-build-variables.yml index 0d9154da16..dc5c1b8020 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -21,3 +21,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 9dd1726c44..9eeedbff32 100644 --- a/eng/pipelines/libraries/common-variables.yml +++ b/eng/pipelines/libraries/common-variables.yml @@ -32,7 +32,7 @@ variables: - name: Patch value: '0' - # Update this for preview releases. + # Update this for preview releases. - name: Preview value: '-preview' - name: Revision @@ -46,3 +46,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 16535cf9a7..1a41fd0a43 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/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 8b4f420f19..9de2a7feaa 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 + +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 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient.Samples", "..\doc\Samples\Microsoft.Data.SqlClient.Samples.csproj", "{C09B9D2F-E463-BEBD-34E4-E8F2C201A277}" EndProject @@ -657,6 +669,30 @@ Global {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Release|Any CPU.ActiveCfg = Release|Any CPU {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Release|x64.ActiveCfg = Release|x64 {C09B9D2F-E463-BEBD-34E4-E8F2C201A277}.Release|x86.ActiveCfg = Release|x86 + {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 @@ -714,6 +750,11 @@ Global {4A9C11F4-9577-ABEC-C070-83A194746D9B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {FAA1E517-581A-D3DC-BAC9-FAD1D5A5142C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {C09B9D2F-E463-BEBD-34E4-E8F2C201A277} = {ED952CF7-84DF-437A-B066-F516E9BE1C2C} + {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 e6ffc2526b..011fdafffb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -50,6 +50,22 @@ + + + + + + + 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 c7983fcc15..cddab6fd9f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -1088,6 +1088,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 1d9c1985bd..19be9bb567 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 ea6b7de6ce..ce36beb46e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -1079,8 +1079,25 @@ + + + + + + + + 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 9cb42a9ed6..18921fc2be 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -32,6 +32,7 @@ + @@ -49,6 +50,7 @@ + @@ -61,6 +63,7 @@ + @@ -73,6 +76,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 57312df06f7161d8d7a1fe48831cf1d35faf23e7 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/13] 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 | 87 ++++++-- .../templates/jobs/ci-run-tests-job.yml | 110 ++++++---- .../jobs/run-tests-package-reference-job.yml | 8 +- .../jobs/validate-signed-package-job.yml | 84 +++----- .../templates/stages/ci-run-tests-stage.yml | 62 ++++-- ...ld-all-configurations-signed-dlls-step.yml | 34 ++-- .../templates/steps/build-all-tests-step.yml | 48 ++--- .../build-and-run-tests-netcore-step.yml | 12 +- .../steps/build-and-run-tests-netfx-step.yml | 12 +- .../templates/steps/ci-prebuild-step.yml | 30 +-- .../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 | 17 +- .../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 | 190 +++++++++++------- ...qlclient-ci-package-reference-pipeline.yml | 2 +- ...qlclient-ci-project-reference-pipeline.yml | 2 +- .../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 | 14 +- .../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, 1187 insertions(+), 592 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 62154461a7..7fe296591a 100644 --- a/build.proj +++ b/build.proj @@ -456,7 +456,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 486211a74f..276893cc4d 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,15 +39,45 @@ jobs: - powershell: | Write-Host "##vso[task.setvariable variable=CDP_BUILD_TYPE_COPY;isOutput=true]$($env:CDP_BUILD_TYPE)" name: GetBuildType + + # 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 - - template: ../steps/build-all-configurations-signed-dlls-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: - buildConfiguration: Release + packagePath: $(REPOROOT)/packages - - template: ../steps/code-analyze-step.yml@self + # 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: - analyzeType: all - + # 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 @@ -53,9 +85,13 @@ jobs: - template: ../steps/generate-nuget-package-step.yml@self parameters: buildConfiguration: Release - OutputDirectory: $(artifactDirectory) + displayName: 'Create MDS NuGet Package' installNuget: false + nuspecPath: $(nuspecPath) + outputDirectory: $(artifactDirectory) + packageVersion: $(mdsPackageVersion) properties: 'AbstractionsPackageVersion=$(abstractionsPackageVersion)' + referenceType: Package - template: ../steps/esrp-code-signing-step.yml@self parameters: @@ -71,4 +107,4 @@ jobs: parameters: buildConfiguration: Release 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 0a0b62f7fd..da83c2077c 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 @@ -30,8 +45,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 }} @@ -46,36 +68,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 }} buildConfiguration: ${{ parameters.buildConfiguration }} + referenceType: ${{ parameters.referenceType }} operatingSystem: Windows - build: all + build: MDS + abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} - template: ../steps/generate-nuget-package-step.yml@self parameters: - NugetPackageVersion: $(NugetPackageVersion) buildConfiguration: ${{ parameters.buildConfiguration }} - nuspecPath: 'tools/specs/Microsoft.Data.SqlClient.nuspec' - OutputDirectory: $(packagePath) + displayName: 'Create MDS NuGet Package' generateSymbolsPackage: false - properties: 'AbstractionsPackageVersion=$(abstractionsPackageVersion)' - displayName: 'Generate NuGet package M.D.SqlClient' + packageVersion: ${{ parameters.mdsPackageVersion }} + nuspecPath: 'tools/specs/Microsoft.Data.SqlClient.nuspec' + outputDirectory: $(packagePath) + properties: 'AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' + referenceType: ${{ parameters.referenceType }} + + - template: ../steps/ci-project-build-step.yml@self + parameters: + platform: ${{ parameters.platform }} + buildConfiguration: ${{ parameters.buildConfiguration }} + referenceType: ${{ parameters.referenceType }} + operatingSystem: Windows + build: AKV + mdsPackageVersion: ${{parameters.mdsPackageVersion}} - template: ../steps/generate-nuget-package-step.yml@self parameters: - NugetPackageVersion: $(NugetPackageVersion) buildConfiguration: ${{ parameters.buildConfiguration }} - nuspecPath: 'tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec' - OutputDirectory: $(packagePath) + displayName: 'Create AKV NuGet Package' generateSymbolsPackage: false - properties: 'MdsPackageVersion=$(mdsPackageVersion)' installNuget: false - displayName: 'Generate NuGet package AKV Provider' + nuspecPath: 'tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec' + outputDirectory: $(packagePath) + packageVersion: $(akvPackageVersion) + properties: 'MdsPackageVersion=${{ parameters.mdsPackageVersion }}' + referenceType: ${{ parameters.referenceType }} - - 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 a2b7bdc2fa..c4b64cec96 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,46 +40,14 @@ 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: enableX86Test - type: boolean - default: false - - - name: enableX64Test - type: boolean - default: true - - - name: testSet - type: string - - - name: publishTestResults - type: boolean - default: false - - - name: configSqlFor - type: string # local, azure, or enclave - default: local - name: operatingSystem type: string @@ -71,17 +58,38 @@ parameters: values: - Debug - Release + + - name: poolName + type: string + + - name: prebuildSteps + type: stepList + default: [] - - name: buildType - default: Project + - name: publishTestResults + type: boolean + default: false + + - name: referenceType + type: string values: - Project - Package + - name: targetFramework + type: string + + - name: testSet + type: string + # The timeout, in minutes, for this job. - name: timeout type: number + - name: usemanagedSNI + type: boolean + default: false + jobs: - job: ${{ format('{0}', coalesce(parameters.jobDisplayName, parameters.image, 'unknown_image')) }} @@ -102,6 +110,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 @@ -232,8 +256,10 @@ jobs: parameters: targetFramework: ${{ parameters.targetFramework }} buildConfiguration: ${{ parameters.buildConfiguration }} - referenceType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} ${{ if ne(parameters.operatingSystem, 'Windows') }}: OSGroup: Unix @@ -243,7 +269,7 @@ jobs: debug: ${{ parameters.debug }} targetFramework: ${{ parameters.targetFramework }} buildConfiguration: ${{ parameters.buildConfiguration }} - referenceType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} operatingSystem: ${{ parameters.operatingSystem }} @@ -286,13 +312,13 @@ jobs: debug: ${{ parameters.debug }} targetFramework: ${{ parameters.targetFramework }} buildConfiguration: ${{ parameters.buildConfiguration }} - 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 59700587f7..61cb1db200 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 @@ -50,9 +50,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,7 +66,7 @@ jobs: referenceType: Package buildConfiguration: Release ${{ if parameters.isPreview }}: - nugetPackageVersion: $(PreviewNugetPackageVersion) + mdsPackageVersion: $(previewMdsPackageVersion) - template: ../steps/build-and-run-tests-netcore-step.yml parameters: @@ -76,4 +74,4 @@ jobs: buildConfiguration: Release 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 363535aa7e..77cc320af0 100644 --- a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml +++ b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml @@ -4,22 +4,11 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - - name: debug - type: boolean - default: false - - - name: testConfigurations - type: object - - - name: dependsOn + - name: abstractionsArtifactName type: string - default: '' - - name: buildType - default: Project - values: - - Project - - Package + - name: abstractionsPackageVersion + type: string - name: buildConfiguration type: string @@ -27,14 +16,38 @@ parameters: - Debug - Release - - name: prebuildSteps - type: stepList + - name: debug + type: boolean + default: false + + - name: dependsOn + type: object default: [] + + - name: mdsArtifactName + type: string + default: MDS.Artifact + + - name: mdsPackageVersion + type: string - name: postTestJobs type: jobList default: [] + - name: prebuildSteps + type: stepList + default: [] + + - name: referenceType + default: Project + values: + - Project + - Package + + - name: testConfigurations + type: object + # The timeout, in minutes, for each test job. - name: testJobTimeout type: number @@ -43,10 +56,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 }}: @@ -56,13 +66,17 @@ stages: parameters: debug: ${{ parameters.debug }} buildConfiguration: ${{ parameters.buildConfiguration }} - buildType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} timeout: ${{ parameters.testJobTimeout }} 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 }} @@ -83,7 +97,7 @@ stages: parameters: debug: ${{ parameters.debug }} buildConfiguration: ${{ parameters.buildConfiguration }} - buildType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} timeout: ${{ parameters.testJobTimeout }} poolName: ${{ config.value.pool }} hostedPool: ${{ eq(config.value.hostedPool, true) }} @@ -94,6 +108,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 ef19650643..760024ae9f 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,9 +4,12 @@ # 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: buildConfiguration type: string @@ -14,15 +17,17 @@ parameters: - Debug - Release - - 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 @@ -49,10 +54,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.buildConfiguration }}' - 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.buildConfiguration }}' + 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 1074c97632..a83108740c 100644 --- a/eng/pipelines/common/templates/steps/build-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/build-all-tests-step.yml @@ -4,16 +4,8 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - - name: targetFramework - type: string - - - name: nugetPackageVersion - type: string - default: $(NugetPackageVersion) - - - name: platform + - name: abstractionsPackageVersion type: string - default: $(Platform) - name: buildConfiguration type: string @@ -21,16 +13,26 @@ parameters: - Debug - Release + - name: mdsPackageVersion + type: string + + - name: osGroup + type: string + default: '' + + - name: platform + type: string + default: $(Platform) + - name: referenceType - default: Package + type: string values: - Project - Package - - - name: OSGroup - type: string - default: '' + - name: targetFramework + type: string + - name: testSet type: string @@ -42,26 +44,16 @@ steps: solution: build.proj platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' - 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.buildConfiguration }}' -# 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.buildConfiguration }}' - 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 @@ -71,7 +63,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.buildConfiguration }}' + 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.buildConfiguration }} -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 aabbef29eb..3d9ee4cf65 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 @@ -20,9 +20,9 @@ parameters: - Project - Package - - name: NugetPackageVersion + - name: mdsPackageVersion type: string - default: $(NugetPackageVersion) + default: $(mdsPackageVersion) - name: platform type: string @@ -57,14 +57,14 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.buildConfiguration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }}' + msbuildArguments: '-p:Configuration=${{parameters.buildConfiguration }} -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.buildConfiguration }}' + msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:Configuration=${{parameters.buildConfiguration }}' # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -73,12 +73,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.buildConfiguration }} -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.buildConfiguration }} -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.buildConfiguration }} -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.buildConfiguration }} -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 20d6ee9e5b..b8a24b7083 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 @@ -20,9 +20,9 @@ parameters: - Project - Package - - name: NugetPackageVersion + - name: mdsPackageVersion type: string - default: $(NugetPackageVersion) + default: $(mdsPackageVersion) - name: platform type: string @@ -57,13 +57,13 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.buildConfiguration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }}' + msbuildArguments: '-p:Configuration=${{parameters.buildConfiguration }} -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.buildConfiguration }} -p:Platform=${{parameters.platform }}' + msbuildArguments: ' -t:BuildTestsNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }} -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:Configuration=${{parameters.buildConfiguration }} -p:Platform=${{parameters.platform }}' # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -72,12 +72,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.buildConfiguration }} -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.buildConfiguration }} -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.buildConfiguration }} -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.buildConfiguration }} -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 4920482059..6762da1829 100644 --- a/eng/pipelines/common/templates/steps/ci-prebuild-step.yml +++ b/eng/pipelines/common/templates/steps/ci-prebuild-step.yml @@ -8,18 +8,8 @@ parameters: type: boolean default: false - - name: artifactName + - name: referenceType type: string - default: Artifacts - - - name: buildConfiguration - type: string - values: - - Debug - - Release - - - name: buildType - default: Project values: - Project - Package @@ -48,22 +38,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 - buildConfiguration: ${{ parameters.buildConfiguration }} + 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 2b40aa8216..1424fdf2ea 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -13,6 +13,12 @@ parameters: values: - Debug - Release + + - name: referenceType + type: string + values: + - Package + - Project - name: buildNumber type: string @@ -36,6 +42,16 @@ 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: @@ -47,32 +63,27 @@ steps: packageType: 'runtime' 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.buildConfiguration }}' - 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')) }}: @@ -84,7 +95,7 @@ steps: msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.buildConfiguration }}' - 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')) }}: @@ -96,7 +107,7 @@ steps: msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.buildConfiguration }}' - 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]' @@ -106,17 +117,17 @@ steps: msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.buildConfiguration }}' - 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.buildConfiguration }}' + 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 c33bd36645..711c3a9b8f 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 @@ -85,6 +85,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 cc6518bb39..3a57aa93a8 100644 --- a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml +++ b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml @@ -6,13 +6,11 @@ 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' @@ -29,12 +27,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 @@ -58,6 +61,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.buildConfiguration}};ReferenceType=${{parameters.referenceType}}"' + arguments: 'pack -Symbols -SymbolPackageFormat snupkg ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.buildConfiguration}};ReferenceType=${{parameters.referenceType}};${{parameters.properties}}"' ${{else }}: - arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.buildConfiguration}};ReferenceType=${{parameters.referenceType}}"' + arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.buildConfiguration}};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 a3cd272958..3f80ae8d2d 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 91f68eaede..ac080c0019 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 @@ -64,9 +64,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ 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 @@ -78,9 +78,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ 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 @@ -92,9 +92,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ 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 @@ -106,7 +106,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.buildConfiguration }}' + 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.buildConfiguration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 @@ -118,7 +118,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.buildConfiguration }}' + 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.buildConfiguration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 @@ -130,7 +130,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.buildConfiguration }}' + 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.buildConfiguration }}' 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 f94432880f..71025ac738 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -68,9 +68,12 @@ parameters: type: object default: [net462, net8.0, net9.0, net10.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 @@ -103,17 +106,45 @@ parameters: variables: - template: libraries/ci-build-variables.yml@self - - name: artifactName - value: Artifacts + - name: abstractionsArtifactName + value: Abstractions.Artifact + + - name: mdsArtifactName + value: MDS.Artifact 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: buildConfiguration: ${{ parameters.buildConfiguration }} - artifactName: $(artifactName) + referenceType: ${{ parameters.referenceType }} + abstractionsPackageVersion: $(abstractionsPackageVersion) + abstractionsArtifactName: $(abstractionsArtifactName) + mdsPackageVersion: $(mdsPackageVersion) + mdsArtifactName: $(mdsArtifactName) ${{if ne(parameters.SNIVersion, '')}}: prebuildSteps: - template: common/templates/steps/override-sni-version.yml@self @@ -121,46 +152,50 @@ 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 }} buildConfiguration: ${{ parameters.buildConfiguration }} - buildType: ${{ parameters.buildType }} + referenceType: ${{ parameters.referenceType }} + abstractionsArtifactName: $(abstractionsArtifactName) + abstractionsPackageVersion: $(abstractionsPackageVersion) + mdsArtifactName: $(mdsArtifactName) + mdsPackageVersion: $(mdsPackageVersion) testJobTimeout: ${{ parameters.testJobTimeout }} ${{ 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 + # 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 }} - buildConfiguration: ${{ parameters.buildConfiguration }} - ${{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 }} - buildConfiguration: ${{ parameters.buildConfiguration }} - + + - template: common/templates/steps/ci-prebuild-step.yml@self + parameters: + debug: ${{ parameters.debug }} + referenceType: ${{ parameters.referenceType }} + # Include the code coverage job if the build type is Project. ${{ if eq(parameters.buildType, 'Project') }}: # Jobs to run as part of the tests stage, after the tests are done. @@ -371,9 +406,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 @@ -400,9 +434,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 @@ -410,39 +443,6 @@ stages: LocalDbAppName: $(LocalDbAppName) LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - # Only run the AE tests if enable and if we have access to the necessary - # secrets. - ${{ if and(eq(parameters.runAlwaysEncryptedTests, true), 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) - # self hosted SQL Server on Linux linux_ub20_22_sql_22: pool: ${{parameters.defaultPoolName }} @@ -489,9 +489,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 @@ -499,9 +498,64 @@ stages: 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) + + # Enclave tests + # # Only run the AE tests if enable and if we have access to the necessary # secrets. ${{ if and(eq(parameters.runAlwaysEncryptedTests, true), 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: diff --git a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml index 6c2f4ea169..88c3abfc01 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml @@ -173,7 +173,7 @@ extends: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildPlatforms: ${{ parameters.buildPlatforms }} - buildType: Package + referenceType: Package codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} debug: ${{ parameters.debug }} enableStressTests: ${{ parameters.enableStressTests }} diff --git a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml index d83e65dcb0..65104e6961 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml @@ -173,7 +173,7 @@ extends: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildPlatforms: ${{ parameters.buildPlatforms }} - buildType: Project + referenceType: Project codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} debug: ${{ parameters.debug }} enableStressTests: ${{ parameters.enableStressTests }} diff --git a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml index abf2d148d9..a05cc5401f 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 5ce8376845..c72e4ad51c 100644 --- a/eng/pipelines/jobs/build-akv-official-job.yml +++ b/eng/pipelines/jobs/build-akv-official-job.yml @@ -5,14 +5,17 @@ ################################################################################# 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 @@ -20,9 +23,6 @@ parameters: - Debug - Release - - name: nugetPackageVersion - type: string - - name: mdsPackageVersion type: string @@ -89,9 +89,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 }}' @@ -104,11 +113,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 }}' @@ -123,10 +127,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: @@ -141,7 +146,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)' @@ -154,4 +159,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 dc5c1b8020..1a83c24cd8 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -6,28 +6,21 @@ variables: - group: 'ADO Build properties' - - group: 'ADO CI Packaging' - group: 'ADO Test Configuration Properties' - name: buildNumber value: '$(Build.BuildNumber)' - 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 9eeedbff32..7ced0c28d2 100644 --- a/eng/pipelines/libraries/common-variables.yml +++ b/eng/pipelines/libraries/common-variables.yml @@ -24,6 +24,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' @@ -38,21 +71,11 @@ variables: - name: Revision value: '3' - - 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 2dd21d8514..c58d3febb9 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 @@ -41,12 +44,6 @@ steps: packageType: 'runtime' 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: @@ -54,8 +51,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 b07252e29d..41905c8483 100644 --- a/eng/pipelines/steps/compound-nuget-pack-step.yml +++ b/eng/pipelines/steps/compound-nuget-pack-step.yml @@ -26,8 +26,14 @@ 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: # This tool is failing on OneBranch pipelines, possibly due to new @@ -63,7 +69,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' @@ -74,4 +80,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 ebb6ff0801..8b2b4bd331 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 values: @@ -28,7 +40,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 011fdafffb..843f8c0bfe 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -58,12 +58,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 cddab6fd9f..bf8c916315 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -1096,12 +1096,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 19be9bb567..6ab452e0c2 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 ce36beb46e..a4cc2aa55a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -1087,12 +1087,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 + + - - - - - + + + + + - - - + + + - - - + + + - + - - + + + + + + + + + - + - + + @@ -28,7 +41,8 @@ - + + @@ -48,7 +62,8 @@ - + + @@ -62,14 +77,21 @@ --> + + + + + + + - + @@ -81,11 +103,13 @@ + + @@ -97,12 +121,23 @@ - + + + - + + + + + + + + + + diff --git a/NuGet.config.local b/NuGet.config.local new file mode 100644 index 0000000000..3e23ac372c --- /dev/null +++ b/NuGet.config.local @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/build.proj b/build.proj index 7fe296591a..d8343382e3 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) - - + + + + + + - + - + @@ -244,29 +309,29 @@ Name="BuildUnitTestsNetCore" DependsOnTargets="RestoreTestsNetCore;BuildNetCore" Condition="$(ReferenceType.Contains('Project'))"> - + - + - + - + - + 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 276893cc4d..939ad9ab0d 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 c4b64cec96..fa95ea52b0 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' @@ -126,6 +132,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 @@ -259,6 +271,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 77cc320af0..6d70f29900 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: buildConfiguration type: string values: @@ -74,7 +80,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 }} @@ -109,7 +117,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 a83108740c..7216451a3d 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: buildConfiguration type: string values: @@ -44,7 +47,7 @@ steps: solution: build.proj platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' - 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 @@ -53,7 +56,7 @@ steps: solution: build.proj platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' - 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 @@ -63,7 +66,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.buildConfiguration }} -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.buildConfiguration }} -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 3d9ee4cf65..9114907632 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 @@ -57,14 +57,14 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.buildConfiguration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' + msbuildArguments: '-p:Configuration=${{parameters.buildConfiguration }} -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.buildConfiguration }}' + msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:Configuration=${{parameters.buildConfiguration }}' # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -73,12 +73,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.buildConfiguration }} -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.buildConfiguration }} -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.buildConfiguration }} -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.buildConfiguration }} -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 b8a24b7083..cc75abd154 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 @@ -57,13 +57,13 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.buildConfiguration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.mdsPackageVersion }}' + msbuildArguments: '-p:Configuration=${{parameters.buildConfiguration }} -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.buildConfiguration }} -p:Platform=${{parameters.platform }}' + msbuildArguments: ' -t:BuildTestsNetFx -p:ReferenceType=${{parameters.referenceType }} -p:MdsPackageVersion=${{parameters.mdsPackageVersion }} -p:TargetNetFxVersion=${{parameters.TargetNetFxVersion }} -p:Configuration=${{parameters.buildConfiguration }} -p:Platform=${{parameters.platform }}' # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -72,12 +72,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.buildConfiguration }} -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.buildConfiguration }} -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.buildConfiguration }} -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.buildConfiguration }} -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 ac080c0019..ae0a3b4364 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -64,9 +64,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ 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 @@ -78,9 +78,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ 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 @@ -92,9 +92,9 @@ steps: platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' ${{ 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 @@ -106,7 +106,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.buildConfiguration }}' + 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.buildConfiguration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 @@ -118,7 +118,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.buildConfiguration }}' + 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.buildConfiguration }}' verbosityRestore: Detailed verbosityPack: Detailed retryCountOnTaskFailure: 1 @@ -130,7 +130,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.buildConfiguration }}' + 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.buildConfiguration }}' 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 71025ac738..dd11491fdc 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -108,6 +108,9 @@ variables: - name: abstractionsArtifactName value: Abstractions.Artifact + + - name: azureArtifactName + value: Azure.Artifact - name: mdsArtifactName value: MDS.Artifact @@ -118,7 +121,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')}}: @@ -152,6 +155,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 @@ -171,17 +192,20 @@ stages: referenceType: ${{ parameters.referenceType }} abstractionsArtifactName: $(abstractionsArtifactName) abstractionsPackageVersion: $(abstractionsPackageVersion) + azureArtifactName: $(azureArtifactName) + azurePackageVersion: $(azurePackageVersion) mdsArtifactName: $(mdsArtifactName) mdsPackageVersion: $(mdsPackageVersion) testJobTimeout: ${{ parameters.testJobTimeout }} ${{ if eq(parameters.buildType, 'Package') }}: dependsOn: build_nugets - # 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 c72e4ad51c..12909a04e0 100644 --- a/eng/pipelines/jobs/build-akv-official-job.yml +++ b/eng/pipelines/jobs/build-akv-official-job.yml @@ -90,7 +90,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 1a83c24cd8..f561c922b9 100644 --- a/eng/pipelines/libraries/ci-build-variables.yml +++ b/eng/pipelines/libraries/ci-build-variables.yml @@ -16,6 +16,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-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 new file mode 100644 index 0000000000..90beb30caf --- /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] + 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/src/Directory.Build.props b/src/Directory.Build.props index 1a41fd0a43..381a2d9817 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -12,12 +12,10 @@ Project 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 95% rename from doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml rename to src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationMethod.xml index cd15a65ec2..c804a8fb3a 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationMethod.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationMethod.xml @@ -1,3 +1,8 @@ + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationParameters.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml similarity index 62% rename from doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationParameters.xml rename to src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml index 7c409cdc5c..087045b2c9 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationParameters.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationParameters.xml @@ -1,4 +1,9 @@ - + + @@ -6,23 +11,34 @@ - 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 authentication timeout, in seconds. The overall connection timeout + is managed by the driver; this timeout only applies to authentication. + 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 +49,18 @@ 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. + Gets the authentication timeout value, in seconds. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml similarity index 92% rename from doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml rename to src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProvider.xml index 75cc752d76..7848aaec1a 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/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. @@ -89,49 +94,50 @@ - - - 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. + 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. + The authentication method. + The authentication provider or if not found. + + + Sets an authentication provider by method. + 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 new file mode 100644 index 0000000000..648e4a7e2f --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationProviderException.xml @@ -0,0 +1,82 @@ + + + + + + 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 55% rename from doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationToken.xml rename to src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationToken.xml index 52f79dd535..4eb9a78f52 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationToken.xml +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/doc/SqlAuthenticationToken.xml @@ -1,25 +1,30 @@ - + + 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..9436e9067b 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj @@ -23,26 +23,41 @@ Microsoft.Data.SqlClient.Extensions.Abstractions - Microsoft.Data.SqlClient.Extensions.Abstractions + + + - $(_DefaultMajorVersion).0.0.0 + $(AbstractionsDefaultMajorVersion).0.0.0 $(AbstractionsAssemblyFileVersion) $(AbstractionsAssemblyFileVersion) $(AbstractionsPackageVersion) + + $(Artifacts)/doc/$(TargetFramework)/$(AssemblyName).xml <_Parameter1>true + + - + CDP_BUILD_TYPE is a OneBranch governed pipeline variable. + --> + + - <_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) + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs new file mode 100644 index 0000000000..ac40d8fdea --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/ActiveDirectoryAuthenticationProvider.cs @@ -0,0 +1,896 @@ +// 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.Collections.Concurrent; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; +using Azure.Core; +using Azure.Identity; +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 const int s_accountPwCacheTtlInHours = 2; + private const string s_nativeClientRedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"; + private const 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 = "2fd908ad-0664-4344-b9be-cd3e8b574c38"; + + // The MSAL error code that indicates the action should be retried. + // + // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 + private const int MsalRetryStatusCode = 429; + + /// + public ActiveDirectoryAuthenticationProvider() + : this(DefaultDeviceFlowCallback) + { + } + + /// + public ActiveDirectoryAuthenticationProvider(string applicationClientId) + : this(DefaultDeviceFlowCallback, applicationClientId) + { + } + + /// + public ActiveDirectoryAuthenticationProvider(Func deviceCodeFlowCallbackMethod, string? applicationClientId = null) + { + _deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; + if (applicationClientId is not null) + { + _applicationClientId = applicationClientId; + } + } + + /// + 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 CS0618 // Type or member is obsolete + || authentication == SqlAuthenticationMethod.ActiveDirectoryPassword + #pragma warning restore CS0618 // 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) + { + try + { + using CancellationTokenSource cts = new(); + + // Use the authentication timeout value to cancel token acquire + // request after certain period of time. + if (parameters.ConnectionTimeout > 0) + { + // Safely convert to milliseconds. + if (int.MaxValue / 1000 > parameters.ConnectionTimeout) + { + cts.CancelAfter(int.MaxValue); + } + else + { + cts.CancelAfter(parameters.ConnectionTimeout * 1000); + } + } + + string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix, StringComparison.Ordinal) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; + string[] scopes = [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); + string password = parameters.Password is null ? string.Empty : parameters.Password; + AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, 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 = + #if NETFRAMEWORK + new(parameters.Authority, redirectUri, _applicationClientId, _iWin32WindowFunc); + #else + new(parameters.Authority, redirectUri, _applicationClientId); + #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) + { + // 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. + var builder = + #pragma warning disable CS0618 // Type or member is obsolete + app.AcquireTokenByIntegratedWindowsAuth(scopes) + #pragma warning restore CS0618 // Type or member is obsolete + .WithCorrelationId(parameters.ConnectionId); + + if (!string.IsNullOrEmpty(parameters.UserId)) + { + builder = builder.WithUsername(parameters.UserId); + } + + result = await builder + .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 CS0618 // Type or member is obsolete + else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword) + #pragma warning restore CS0618 // Type or member is obsolete + { + string pwCacheKey = GetAccountPwCacheKey(parameters); + object? previousPw = s_accountPwCache.Get(pwCacheKey); + string password = parameters.Password is null ? string.Empty : parameters.Password; + byte[] currPwHash = GetHash(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 disable 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 = currPwHash; + 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 should be null here, but we make sure of that. + Debug.Assert(result is null); + result = null; + } + + 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 new Extensions.Azure.AuthenticationException( + parameters.AuthenticationMethod, + $"Authentication method {parameters.AuthenticationMethod} not supported."); + } + + // TODO: Existing bug? result may be null here. + if (result is null) + { + throw new Extensions.Azure.AuthenticationException( + parameters.AuthenticationMethod, + "Internal error - authentication result is null"); + } + + return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); + } + catch (MsalException ex) + { + // Check for an explicitly retryable error. + if (ex is MsalServiceException svcEx && + svcEx.StatusCode == MsalRetryStatusCode) + { + int retryPeriod = 0; + + var retryAfter = svcEx.Headers.RetryAfter; + if (retryAfter is not null) + { + if (retryAfter.Delta.HasValue) + { + retryPeriod = retryAfter.Delta.Value.Milliseconds; + } + else if (retryAfter.Date.HasValue) + { + retryPeriod = Convert.ToInt32(retryAfter.Date.Value.Offset.TotalMilliseconds); + } + + throw new Extensions.Azure.AuthenticationException( + parameters.AuthenticationMethod, + ex.ErrorCode, + true, + retryPeriod, + ex.Message, + ex); + } + + // Fall through to check the ErrorCode... + } + + // Check for an unknown error, which we will treat as implicitly + // retryable, but without a suggested period. + if (ex.ErrorCode == MsalError.UnknownError) + { + throw new Extensions.Azure.AuthenticationException( + parameters.AuthenticationMethod, + ex.ErrorCode, + true, + // Don't suggest a retry period. + 0, + ex.Message, + ex); + } + + // The error isn't retryable. + throw new Extensions.Azure.AuthenticationException( + parameters.AuthenticationMethod, + ex.ErrorCode, + false, + 0, + ex.Message, + ex); + } + catch (Exception ex) + when (ex is + AuthenticationFailedException or + AuthenticationRequiredException or + CredentialUnavailableException) + { + // These errors aren't retryable. + throw new Extensions.Azure.AuthenticationException( + parameters.AuthenticationMethod, + "Unknown", + false, + 0, + $"Azure.Identity error: {ex.Message}", + ex); + } + catch (Exception ex) + { + // These errors aren't retryable. + throw new Extensions.Azure.AuthenticationException( + parameters.AuthenticationMethod, + "Unknown", + false, + 0, + $"Unexpected error: {ex.Message}", + ex); + } + } + + 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 + // + // Wait up to 3 minutes. + 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 + { + return await app.AcquireTokenWithDeviceCode(scopes, + deviceCodeResult => deviceCodeFlowCallback(deviceCodeResult)) + .WithCorrelationId(connectionId) + .ExecuteAsync(cancellationToken: cts.Token) + .ConfigureAwait(false); + } + } + catch (OperationCanceledException ex) + { + SqlClientEventSource.Log.TryTraceEvent("AcquireTokenInteractiveDeviceFlowAsync | Operation timed out while acquiring access token."); + + throw new Extensions.Azure.AuthenticationException( + authenticationMethod, + "OperationCanceled", + false, + 0, + // TODO: This used to use the following localized strings + // depending on the method: + // + // Strings.SQL_Timeout_Active_Directory_Interactive_Authentication + // Strings.SQL_Timeout_Active_Directory_DeviceFlow_Authentication + ex.Message, + ex); + } + } + + 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 = typeof(ActiveDirectoryAuthenticationProvider).FullName, + ClientVersion = Extensions.Azure.ThisAssembly.InformationalVersion, + 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; + defaultAzureCredentialOptions.SharedTokenCacheUsername = tokenCredentialKey._clientId; + 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(); + } + +} + +internal class SqlClientLogger +{ + public void LogInfo(string type, string method, string message) + { + SqlClientEventSource.Log.TryTraceEvent( + "{3}", type, method, LogLevel.Info, message); + } +} + +internal class SqlClientEventSource +{ + internal class Logger + { + public void TryTraceEvent(string message, params object?[] args) + { + } + } + + public static readonly Logger Log = new(); +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AuthenticationException.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AuthenticationException.cs new file mode 100644 index 0000000000..8676cf7566 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/AuthenticationException.cs @@ -0,0 +1,51 @@ +// 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 +/// authentication failures. It is not exposed publicly. +/// +internal class AuthenticationException : SqlAuthenticationProviderException +{ + /// + /// Construct with just a method and message. Other properties are set to + /// defaults per the base class. + /// + /// The authentication method. + /// The error message. + internal AuthenticationException( + SqlAuthenticationMethod method, + string message) + : base($"Failed to acquire access token for {method}: {message}", null) + { + } + + /// + /// Construct with all properties specified. See the base class for details. + /// + /// The authentication method. + /// The failure code. + /// Whether the operation should be retried. + /// The retry period. + /// The error message. + /// The exception that caused this error. + internal AuthenticationException( + SqlAuthenticationMethod method, + string failureCode, + bool shouldRetry, + int retryPeriod, + string message, + Exception? causedBy = null) + : base( + method, + failureCode, + shouldRetry, + retryPeriod, + $"Failed to acquire access token for {method}: {message}", + causedBy) + { + } +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj new file mode 100644 index 0000000000..74dba18d1f --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj @@ -0,0 +1,126 @@ + + + + + + + + netstandard2.0 + + + + + enable + enable + + + + + Microsoft.Data.SqlClient.Extensions.Azure + + + + + + $(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..ad94e9b4b1 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj @@ -0,0 +1,27 @@ + + + + net462;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 9de2a7feaa..490d5339ce 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.36203.30 @@ -328,6 +328,15 @@ 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient.Samples", "..\doc\Samples\Microsoft.Data.SqlClient.Samples.csproj", "{C09B9D2F-E463-BEBD-34E4-E8F2C201A277}" +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 @@ -693,6 +702,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 @@ -755,6 +788,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 8bdb0e520a..80a965f20c 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 @@ -29,14 +29,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 197a396a0c..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..6c74d2e85f 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,6 @@ protected SqlAuthenticationInitializer() { } /// public abstract void Initialize(); } - /// - public enum SqlAuthenticationMethod - { - /// - 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 843f8c0bfe..aaccec8c11 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 bf8c916315..6995dea555 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -647,18 +647,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 @@ -1076,8 +1070,6 @@ - - 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..30816757eb 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,85 +96,6 @@ protected SqlAuthenticationInitializer() { } /// public abstract void Initialize(); } - /// - public enum SqlAuthenticationMethod - { - /// - 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 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 6ab452e0c2..29fbddc3b6 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 a4cc2aa55a..804b0aea4f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -645,18 +645,12 @@ 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 - - Microsoft\Data\SqlClient\SqlAuthenticationToken.cs - Microsoft\Data\SqlClient\SqlBatchCommand.cs @@ -1059,8 +1053,6 @@ - - All diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 9bfceb3dc0..3403d2efd6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -69,8 +69,6 @@ - - All @@ -91,8 +89,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 abbeeda0eb..b3fe5ebd8b 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 Microsoft.Data.SqlClient.Connection; -using Microsoft.Identity.Client; using Microsoft.SqlServer.Server; using IsolationLevel = System.Data.IsolationLevel; @@ -501,11 +500,15 @@ internal static ArgumentException InvalidArgumentLength(string argumentName, int internal static ArgumentException MustBeReadOnly(string argumentName) => Argument(StringsHelper.GetString(Strings.ADP_MustBeReadOnly, argumentName)); +<<<<<<< HEAD internal static Exception CreateSqlException( MsalException msalException, SqlConnectionString connectionOptions, SqlConnectionInternal sender, string username) +======= + internal static Exception CreateSqlException(SqlAuthenticationProviderException authException, SqlConnectionString connectionOptions, SqlInternalConnectionTds sender, string username) +>>>>>>> 4e2c6b4a (Move AAD/Entra Authentication into new Azure package (#3680)) { // Error[0] SqlErrorCollection sqlErs = new(); @@ -513,20 +516,20 @@ internal static Exception CreateSqlException( 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/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index 5567dac6d5..efd54ae383 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -8,7 +8,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Net.Http.Headers; using System.Security; using System.Text; using System.Threading; @@ -19,7 +18,6 @@ using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.Connection; using Microsoft.Data.SqlClient.ConnectionPool; -using Microsoft.Identity.Client; using IsolationLevel = System.Data.IsolationLevel; namespace Microsoft.Data.SqlClient.Connection @@ -34,13 +32,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable // @TODO: Can be private? internal const int MaxNumberOfRedirectRoute = 10; - /// - /// Status code that indicates MSAL request should be retried. - /// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 - /// - // @TODO: Can be private? - internal const int MsalHttpRetryStatusCode = 429; - /// /// The timespan defining the amount of time the authentication context needs to be valid /// for at-least, to re-use the cached context, without making an attempt to refresh it. IF @@ -132,20 +123,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable #region Debug/Test Behavior Overrides #if DEBUG - /// - /// This is a test hook to enable testing of the retry paths for MSAL get access token. - /// - /// - /// Type type = typeof(SqlConnection).Assembly.GetType("Microsoft.Data.SqlClient.SQLInternalConnectionTds"); - /// FieldInfo field = type.GetField("_forceMsalRetry", BindingFlags.NonPublic | BindingFlags.Static); - /// if (field != null) - /// { - /// field.SetValue(null, true); - /// } - /// - /// @TODO: For unit tests, it should not be necessary to do this via reflection. - internal static bool _forceMsalRetry = false; - /// /// This is a test hook to simulate a token expiring within the next 45 minutes. /// @@ -619,7 +596,7 @@ internal bool Is2008OrNewer internal override bool IsAccessTokenExpired { get => _federatedAuthenticationInfoRequested && - DateTime.FromFileTimeUtc(_fedAuthToken.expirationFileTime) < DateTime.UtcNow.AddSeconds(accessTokenExpirationBufferTime); + DateTime.FromFileTimeUtc(_fedAuthToken.ExpirationFileTime) < DateTime.UtcNow.AddSeconds(accessTokenExpirationBufferTime); } /// @@ -1806,8 +1783,8 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) // 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); + fedAuthInfo.StsUrl, + fedAuthInfo.Spn); // Try to retrieve the authentication context from the pool, if one does exist for // this key. @@ -1928,14 +1905,12 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) // 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 = new SqlFedAuthToken - { - accessToken = dbConnectionPoolAuthenticationContext.AccessToken, - expirationFileTime = dbConnectionPoolAuthenticationContext.ExpirationTime.ToFileTime() - }; + _fedAuthToken = new( + dbConnectionPoolAuthenticationContext.AccessToken, + dbConnectionPoolAuthenticationContext.ExpirationTime.ToFileTime()); } - Debug.Assert(_fedAuthToken?.accessToken != null, + Debug.Assert(_fedAuthToken?.AccessToken != null, "_fedAuthToken and _fedAuthToken.accessToken cannot be null."); _parser.SendFedAuthToken(_fedAuthToken); @@ -2703,61 +2678,47 @@ private void FailoverPermissionDemand() => PoolGroupProviderInfo?.FailoverPermissionDemand(); #endif + #nullable enable + /// /// Get the Federated Authentication Token. /// /// Information obtained from server as Federated Authentication Info. private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { - Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null."); - - // Number of milliseconds to sleep for the initial back off. - int sleepInterval = 100; - - // Number 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 attempts we are willing to perform. + const int maxAttempts = 1; // Username to use in error messages. - string username = null; - - SqlAuthenticationProvider authProvider = - SqlAuthenticationProvider.GetProvider(ConnectionOptions.Authentication); + string? username = null; + 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). - // @TODO: Can we pick one or the other? - // @TODO: Wait ... are we counting up but only looping while the number of attempts is <=1 ? Huh? - // @TODO: Can we consider using a for loop here since there's a fixed number of times to loop - - #if NET - while (numberOfAttempts <= 1) - #else - while (numberOfAttempts <= 1 && sleepInterval <= _timeout.MillisecondsRemaining) - #endif + // We will perform retries if the provider indicates an error that + // is retryable. + for (int attempt = 0; attempt <= maxAttempts; ++attempt) { - numberOfAttempts++; try { - var authParamsBuilder = new SqlAuthenticationParameters.Builder( - authenticationMethod: ConnectionOptions.Authentication, - resource: fedAuthInfo.spn, - authority: fedAuthInfo.stsurl, - serverName: ConnectionOptions.DataSource, - databaseName: ConnectionOptions.InitialCatalog) + var authParamsBuilder = new SqlAuthenticationParametersBuilder( + authenticationMethod: ConnectionOptions.Authentication, + resource: fedAuthInfo.Spn, + authority: fedAuthInfo.StsUrl, + serverName: ConnectionOptions.DataSource, + databaseName: ConnectionOptions.InitialCatalog) .WithConnectionId(_clientConnectionId) - .WithConnectionTimeout(ConnectionOptions.ConnectTimeout); + .WithAuthenticationTimeout(ConnectionOptions.ConnectTimeout); switch (ConnectionOptions.Authentication) { case SqlAuthenticationMethod.ActiveDirectoryIntegrated: - #if NET // In some scenarios for .NET Core, MSAL cannot detect the current user and needs it passed in // for Integrated auth. Allow the user/application to pass it in to work around those scenarios. if (!string.IsNullOrEmpty(ConnectionOptions.UserID)) @@ -2769,9 +2730,6 @@ private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { username = TdsEnums.NTAUTHORITYANONYMOUSLOGON; } - #else - username = TdsEnums.NTAUTHORITYANONYMOUSLOGON; - #endif if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying) { @@ -2779,20 +2737,13 @@ private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) } else { - // 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 - // @TODO: Verify that the wrapping/unwrapping is necessary. - _fedAuthToken = Task.Run(async () => - await authProvider.AcquireTokenAsync(authParamsBuilder)) - .GetAwaiter() - .GetResult() - .ToSqlFedAuthToken(); + // 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 = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); + _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } - break; - case SqlAuthenticationMethod.ActiveDirectoryInteractive: case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: case SqlAuthenticationMethod.ActiveDirectoryManagedIdentity: @@ -2806,16 +2757,13 @@ await authProvider.AcquireTokenAsync(authParamsBuilder)) 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; - #pragma warning disable 0618 // Type or member is obsolete case SqlAuthenticationMethod.ActiveDirectoryPassword: #pragma warning restore 0618 // Type or member is obsolete - case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal: if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying) { @@ -2823,32 +2771,21 @@ await authProvider.AcquireTokenAsync(authParamsBuilder)) } else { - // @TODO: _fedAuthToken assignment is identical in both cases - move outside if (_credential != null) { 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; } - break; - default: if (_accessTokenCallback == null) { @@ -2871,150 +2808,87 @@ await authProvider.AcquireTokenAsync(authParamsBuilder)) authParamsBuilder.WithUserId(ConnectionOptions.UserID); authParamsBuilder.WithPassword(ConnectionOptions.Password); } - SqlAuthenticationParameters parameters = authParamsBuilder; - using CancellationTokenSource cts = new(); - - // Use Connection timeout value to cancel token acquire request - // after certain period of time.(int) - if (_timeout.MillisecondsRemaining < int.MaxValue) + CancellationTokenSource cts = new(); + // Use Connection timeout value to cancel token acquire request after certain period of time.(int) + if (_timeout.MillisecondsRemaining < Int32.MaxValue) { 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; } - catch (MsalServiceException serviceException) + catch (SqlAuthenticationProviderException ex) { - // Deal with Msal service exceptions first, retry if 429 received. - 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); - } + // It's fatal, so translate into a SqlException. + throw ADP.CreateSqlException(ex, ConnectionOptions, this, username); + } - // 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( - $"SqlInternalConnectionTds.GetFedAuthToken.MsalServiceException | ERR | " + - $"Timeout: {serviceException.ErrorCode}"); + // We should retry. - throw SQL.ActiveDirectoryTokenRetrievingTimeout( - Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), - serviceException.ErrorCode, - serviceException); - } - } - else + // Could we retry if we wanted to? + if (_timeout.IsExpired || _timeout.MillisecondsRemaining <= 0) { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.GetFedAuthToken.MsalServiceException | ERR | " + - $"{serviceException.ErrorCode}"); - - throw ADP.CreateSqlException(serviceException, ConnectionOptions, this, username); + // No, so we throw. + SqlClientEventSource.Log.TryTraceEvent(" Attempt: {0}, Timeout: {1}", attempt, ex.FailureCode); + throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), ex.FailureCode, ex); } - } - catch (MsalException msalException) - { - // Deal with normal MsalExceptions. - if (MsalError.UnknownError != msalException.ErrorCode || - _timeout.IsExpired || - _timeout.MillisecondsRemaining <= sleepInterval) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.GetFedAuthToken.MSALException | ERR | " + - $"{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 ^ attempt); - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.GetFedAuthToken | ADV | " + - $"Object ID: {ObjectID}, " + - $"sleeping {sleepInterval}ms"); - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.GetFedAuthToken | ADV | " + - $"Object ID: {ObjectID}, " + - $"remaining {_timeout.MillisecondsRemaining}ms"); + 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); - Thread.Sleep(sleepInterval); + // Sleep for the desired period. + Thread.Sleep(retryPeriod); - sleepInterval *= 2; - } - // All other exceptions from MSAL/Azure Identity APIs - catch (Exception e) - { - SqlError error = new( - infoNumber: 0, - errorState: 0x00, - errorClass: TdsEnums.FATAL_ERROR_CLASS, - server: ConnectionOptions.DataSource, - errorMessage: e.Message, - procedure: ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, - lineNumber: 0); - - throw SqlException.CreateException( - error, - serverVersion: string.Empty, - internalConnection: this, - innerException: e); + // Fall through to retry... } } - Debug.Assert(_fedAuthToken != null, "fedAuthToken should not be null."); - Debug.Assert(_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( - $"SqlInternalConnectionTds.GetFedAuthToken | " + - $"Object ID {ObjectID}, " + - $"Finished generating federated authentication token."); - + SqlClientEventSource.Log.TryTraceEvent(" {0}, Finished generating federated authentication token.", ObjectID); return _fedAuthToken; } + #nullable disable + private void Login( ServerInfo server, TimeoutTimer timeout, 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..a9863ee2ac --- /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 _authenticationTimeout = 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._authenticationTimeout); + } + + /// + /// 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 authentication timeout. + /// + public SqlAuthenticationParametersBuilder WithAuthenticationTimeout(int timeout) + { + _authenticationTimeout = 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..06d7423028 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -6,12 +6,13 @@ 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 { [Obsolete("ActiveDirectoryPassword is deprecated, use a more secure authentication method. See https://aka.ms/SqlClientEntraIDAuthentication for more details.")] @@ -27,7 +28,7 @@ internal sealed class SqlAuthenticationProviderManager static SqlAuthenticationProviderManager() { - SqlAuthenticationProviderConfigurationSection configurationSection = null; + SqlAuthenticationProviderConfigurationSection? configurationSection = null; try { @@ -46,49 +47,125 @@ 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); + + 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) + + $": 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 +189,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 +215,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 +249,48 @@ public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSe } } - /// - /// Get an authentication provider by method. - /// - /// Authentication method. - /// Authentication provider or null if not found. - public SqlAuthenticationProvider GetProvider(SqlAuthenticationMethod authenticationMethod) + internal 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) + internal 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 +301,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 +350,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 +377,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 b392a94521..17fccca6c5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlException.cs @@ -254,8 +254,6 @@ internal static SqlException CreateException( 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++) { @@ -266,7 +264,11 @@ internal static SqlException CreateException( 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); } @@ -279,7 +281,10 @@ internal static SqlException CreateException( 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/TdsEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs index f6df0a82fe..27a87d29e4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -1132,44 +1132,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 @@ -1179,12 +1141,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 07ab80b96a..23063704ae 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -4307,7 +4307,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); @@ -4400,15 +4401,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: @@ -4423,15 +4424,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; } @@ -9593,11 +9595,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 71335f36ba..d790614874 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 4a031a49a6..e8d684f944 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..32050a9716 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs @@ -49,16 +49,20 @@ private void InvalidCombinationCheck(SqlCredential credential) Assert.Throws(() => connection.AccessToken = "SampleAccessToken"); } } - - #if NETFRAMEWORK - // This test is only valid for .NET Framework /// - /// Tests whether SQL Auth provider is overridden using app.config file. - /// This use case is only supported for .NET Framework applications, as driver doesn't support reading configuration from appsettings.json file. - /// In future if need be, appsettings.json support can be added. + /// Tests whether a dummy SQL Auth provider is registered due to + /// configuration in an app.config file. Only .NET Framework reads + /// from the app.config file, so this test is only valid for that + /// runtime. + /// + /// See the app.config file in the same directory as this file. + /// + /// .NET (Core) reads similar configuration from appsettings.json, but + /// our SqlAuthenticationProviderManager does not currently support + /// that configuration source. /// - [Fact] + [ConditionalFact(typeof(TestUtility), nameof(TestUtility.IsNetFramework))] public async Task IsDummySqlAuthenticationProviderSetByDefault() { var provider = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive); @@ -69,14 +73,13 @@ public async Task IsDummySqlAuthenticationProviderSetByDefault() var token = await provider.AcquireTokenAsync(null); Assert.Equal(token.AccessToken, DummySqlAuthenticationProvider.DUMMY_TOKEN_STR); } - #endif [Fact] public void CustomActiveDirectoryProviderTest() { SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(static (result) => Task.CompletedTask); SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); - Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + Assert.Same(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); } [Fact] @@ -84,7 +87,7 @@ public void CustomActiveDirectoryProviderTest_AppClientId() { SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(Guid.NewGuid().ToString()); SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); - Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + Assert.Same(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); } [Fact] @@ -92,7 +95,7 @@ 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)); + Assert.Same(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AssertExtensions.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AssertExtensions.cs index 1a37bc6f12..7a2acc1160 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AssertExtensions.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AssertExtensions.cs @@ -13,7 +13,7 @@ namespace System { public static class AssertExtensions { - private static bool IsFullFramework => TestUtility.IsFullFramework; + private static bool IsFullFramework => TestUtility.IsNetFramework; public static void Throws(Action action, string message) where T : Exception @@ -36,7 +36,7 @@ public static void Throws(string netCoreParamName, string netFxParamName, Act IsFullFramework ? netFxParamName : netCoreParamName; - if (!TestUtility.NetNative) + if (!TestUtility.IsNetNative) { Assert.Equal(expectedParamName, exception.ParamName); } @@ -57,7 +57,7 @@ public static void Throws(string netCoreParamName, string netFxParamName, Fun IsFullFramework ? netFxParamName : netCoreParamName; - if (!TestUtility.NetNative) + if (!TestUtility.IsNetNative) { Assert.Equal(expectedParamName, exception.ParamName); } @@ -68,7 +68,7 @@ public static T Throws(string paramName, Action action) { T exception = Assert.Throws(action); - if (!TestUtility.NetNative) + if (!TestUtility.IsNetNative) { Assert.Equal(paramName, exception.ParamName); } @@ -89,7 +89,7 @@ public static T Throws(string paramName, Func testCode) { T exception = Assert.Throws(testCode); - if (!TestUtility.NetNative) + if (!TestUtility.IsNetNative) { Assert.Equal(paramName, exception.ParamName); } @@ -102,7 +102,7 @@ public static async Task ThrowsAsync(string paramName, Func testCode { T exception = await Assert.ThrowsAsync(testCode); - if (!TestUtility.NetNative) + if (!TestUtility.IsNetNative) { Assert.Equal(paramName, exception.ParamName); } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/DummySqlAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/DummySqlAuthenticationProvider.cs index bb5e2e2e52..c68baf63eb 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/DummySqlAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/DummySqlAuthenticationProvider.cs @@ -20,8 +20,9 @@ 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. public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) - => authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive; + { + return authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive; + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/TestUtility.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/TestUtility.cs index c4c2ac4d0f..bcbc68a502 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/TestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/DataCommon/TestUtility.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Runtime.InteropServices; namespace Microsoft.Data.SqlClient.Tests @@ -10,7 +9,18 @@ namespace Microsoft.Data.SqlClient.Tests public static class TestUtility { public static readonly bool IsNotArmProcess = RuntimeInformation.ProcessArchitecture != Architecture.Arm; - public static bool IsFullFramework => RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework"); - public static bool NetNative => RuntimeInformation.FrameworkDescription.StartsWith(".NET Native"); + public static bool IsNet + { + get + { + return + !IsNetCore && !IsNetFramework && !IsNetNative && + RuntimeInformation.FrameworkDescription.StartsWith(".NET"); + } + } + + public static bool IsNetCore => RuntimeInformation.FrameworkDescription.StartsWith(".NET Core"); + public static bool IsNetFramework => RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework"); + public static bool IsNetNative => RuntimeInformation.FrameworkDescription.StartsWith(".NET Native"); } } 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..c0315ce2f0 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs @@ -25,17 +25,12 @@ public void DefaultAuthenticationProviders(SqlAuthenticationMethod method) Assert.IsType(SqlAuthenticationProvider.GetProvider(method)); } - #if NETFRAMEWORK - // This test is only valid for .NET Framework - // Overridden by app.config in this project - [Theory] + [ConditionalTheory(typeof(TestUtility), nameof(TestUtility.IsNetFramework))] [InlineData(SqlAuthenticationMethod.ActiveDirectoryInteractive)] public void DefaultAuthenticationProviders_Interactive(SqlAuthenticationMethod method) { Assert.IsType(SqlAuthenticationProvider.GetProvider(method)); } - - #endif } } 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/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 3155b87f81..c889f02444 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 @@ -293,6 +293,8 @@ + + Common @@ -313,11 +315,32 @@ - - - + + + + + + + + + + + + + + + + + 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..2c46c2598d 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; 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 380e3c8a8e..239be4d3a2 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/StressTests/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props index 6ff30d72c8..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;net10.0 + net462;net8.0;net9.0 + + 7.0.0 $(AkvVersionDefault).$(BuildNumber)-dev - - - $(MdsPackageVersion) diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index 3bad2e763a..4792149ed2 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -29,8 +29,6 @@ sqlclient microsoft.data.sqlclient - - @@ -47,8 +45,6 @@ - - @@ -60,8 +56,6 @@ - - @@ -73,8 +67,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 da3b96a8d8d80a364d4f2b8d1c81024056d7f3dd Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:02:33 -0400 Subject: [PATCH 04/13] Fixed compilation issues after rebase from main. --- ...ent.AlwaysEncrypted.AzureKeyVaultProvider.csproj | 1 - .../netcore/src/Microsoft.Data.SqlClient.csproj | 3 --- .../netfx/src/Microsoft.Data.SqlClient.csproj | 4 ---- .../src/Microsoft.Data.SqlClient.csproj | 13 +++++++++++++ .../src/Microsoft/Data/Common/AdapterUtil.cs | 6 +----- ...rosoft.Data.SqlClient.ManualTesting.Tests.csproj | 1 - 6 files changed, 14 insertions(+), 14 deletions(-) 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 80a965f20c..967196ab83 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 @@ -45,6 +45,5 @@ - 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 6995dea555..8432a6427c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -242,9 +242,6 @@ Microsoft\Data\SqlClient\AAsyncCallContext.cs - - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationProvider.cs - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationTimeoutRetryHelper.cs 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 804b0aea4f..61c0d4274a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -306,9 +306,6 @@ Microsoft\Data\SqlClient\AAsyncCallContext.cs - - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationProvider.cs - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationTimeoutRetryHelper.cs @@ -1086,7 +1083,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 3403d2efd6..9bc0137542 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -99,6 +99,19 @@ + + + + + + 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 b3fe5ebd8b..16a482d4de 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -500,15 +500,11 @@ internal static ArgumentException InvalidArgumentLength(string argumentName, int internal static ArgumentException MustBeReadOnly(string argumentName) => Argument(StringsHelper.GetString(Strings.ADP_MustBeReadOnly, argumentName)); -<<<<<<< HEAD internal static Exception CreateSqlException( - MsalException msalException, + SqlAuthenticationProviderException authException, SqlConnectionString connectionOptions, SqlConnectionInternal sender, string username) -======= - internal static Exception CreateSqlException(SqlAuthenticationProviderException authException, SqlConnectionString connectionOptions, SqlInternalConnectionTds sender, string username) ->>>>>>> 4e2c6b4a (Move AAD/Entra Authentication into new Azure package (#3680)) { // Error[0] SqlErrorCollection sqlErs = new(); 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 c889f02444..a459a760c7 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 @@ -356,7 +356,6 @@ - From 55035f820fdb1ac2b9dbd8e337a943d7821a5d39 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 2 Jan 2026 13:54:27 -0400 Subject: [PATCH 05/13] Removed duplicate test job config for macOS. --- eng/pipelines/dotnet-sqlclient-ci-core.yml | 23 ---------------------- 1 file changed, 23 deletions(-) diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index dd11491fdc..bf18ff2f2d 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -607,26 +607,3 @@ stages: UserManagedIdentityClientId: $(UserManagedIdentityClientId) LocalDbAppName: $(LocalDbAppName) LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - - # Self hosted SQL Server on Mac - mac_sql_22: - pool: Azure Pipelines - hostedPool: true - images: - MacOSLatest_Sql22: macos-latest - TargetFrameworks: ${{parameters.targetFrameworksUnix }} - 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) From 6cefed9239eb431d62da6303a5320fdd87ecbfef Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:23:42 -0400 Subject: [PATCH 06/13] Converted all Azure Pipelines template references to absolute paths. --- .../jobs/build-signed-package-job.yml | 20 +++++++------- .../templates/jobs/ci-build-nugets-job.yml | 12 ++++----- .../templates/jobs/ci-code-coverage-job.yml | 4 +-- .../templates/jobs/ci-run-tests-job.yml | 16 ++++++------ .../jobs/run-tests-package-reference-job.yml | 14 +++++----- .../jobs/validate-signed-package-job.yml | 2 +- .../templates/stages/ci-run-tests-stage.yml | 4 +-- .../templates/steps/ci-prebuild-step.yml | 8 +++--- .../templates/steps/ci-project-build-step.yml | 4 +-- .../steps/configure-sql-server-step.yml | 6 ++--- .../common/templates/steps/pre-build-step.yml | 6 ++--- eng/pipelines/dotnet-sqlclient-ci-core.yml | 26 +++++++++---------- .../dotnet-sqlclient-signing-pipeline.yml | 6 ++--- eng/pipelines/jobs/build-akv-official-job.yml | 16 ++++++------ eng/pipelines/libraries/build-variables.yml | 4 +-- .../libraries/mds-validation-variables.yml | 4 +-- eng/pipelines/libraries/variables.yml | 2 +- .../build-abstractions-package-ci-stage.yml | 8 +++--- .../stages/build-azure-package-ci-stage.yml | 8 +++--- .../stages/stress-tests-ci-stage.yml | 6 ++--- 20 files changed, 87 insertions(+), 89 deletions(-) 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 939ad9ab0d..f11e8f0396 100644 --- a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml @@ -25,7 +25,7 @@ jobs: type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs variables: - - template: ../../../libraries/variables.yml@self + - template: /eng/pipelines/libraries/variables.yml@self - ${{ if parameters.isPreview }}: - name: abstractionsPackageVersion value: $(abstractionsPackagePreviewVersion) @@ -50,12 +50,12 @@ jobs: msbuildArguments: -t:BuildTools # Perform analysis before building, since this step will clobber build output. - - template: ../steps/code-analyze-step.yml@self + - template: /eng/pipelines/common/templates/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 + - template: /eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml@self parameters: packagePath: $(REPOROOT)/packages @@ -68,21 +68,21 @@ jobs: msbuildArguments: -t:BuildAbstractions # Build MDS in Package mode, producing signed DLLs. - - template: ../steps/build-all-configurations-signed-dlls-step.yml@self + - template: /eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml@self parameters: # These variables are sourced from common-variables.yml. abstractionsAssemblyFileVersion: $(abstractionsAssemblyFileVersion) abstractionsPackageVersion: $(abstractionsPackageVersion) - configuration: $(Configuration) + buildConfiguration: $(Configuration) mdsAssemblyFileVersion: $(mdsAssemblyFileVersion) mdsPackageVersion: $(mdsPackageVersion) referenceType: Package - - template: ../steps/esrp-code-signing-step.yml@self + - template: /eng/pipelines/common/templates/steps/esrp-code-signing-step.yml@self parameters: artifactType: dll - - template: ../steps/generate-nuget-package-step.yml@self + - template: /eng/pipelines/common/templates/steps/generate-nuget-package-step.yml@self parameters: buildConfiguration: Release displayName: 'Create MDS NuGet Package' @@ -93,17 +93,17 @@ jobs: properties: 'AbstractionsPackageVersion=$(abstractionsPackageVersion)' referenceType: Package - - template: ../steps/esrp-code-signing-step.yml@self + - template: /eng/pipelines/common/templates/steps/esrp-code-signing-step.yml@self parameters: artifactType: pkg - - template: ../steps/copy-dlls-for-test-step.yml@self + - template: /eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml@self parameters: buildConfiguration: Release product: MDS # Publish symbols to servers - - template: ../steps/publish-symbols-step.yml@self + - template: /eng/pipelines/common/templates/steps/publish-symbols-step.yml@self parameters: buildConfiguration: Release publishSymbols: ${{ parameters['PublishSymbols'] }} 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 da83c2077c..b490c2027b 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -62,7 +62,7 @@ jobs: - msbuild variables: - - template: ../../../libraries/ci-build-variables.yml@self + - template: /eng/pipelines/libraries/ci-build-variables.yml@self steps: - ${{ if ne(parameters.prebuildSteps, '') }}: @@ -79,12 +79,12 @@ jobs: 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 + - template: /eng/pipelines/common/templates/steps/ci-prebuild-step.yml@self parameters: debug: ${{ parameters.debug }} referenceType: ${{ parameters.referenceType }} - - template: ../steps/ci-project-build-step.yml@self + - template: /eng/pipelines/common/templates/steps/ci-project-build-step.yml@self parameters: platform: ${{ parameters.platform }} buildConfiguration: ${{ parameters.buildConfiguration }} @@ -93,7 +93,7 @@ jobs: build: MDS abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} - - template: ../steps/generate-nuget-package-step.yml@self + - template: /eng/pipelines/common/templates/steps/generate-nuget-package-step.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} displayName: 'Create MDS NuGet Package' @@ -104,7 +104,7 @@ jobs: properties: 'AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' referenceType: ${{ parameters.referenceType }} - - template: ../steps/ci-project-build-step.yml@self + - template: /eng/pipelines/common/templates/steps/ci-project-build-step.yml@self parameters: platform: ${{ parameters.platform }} buildConfiguration: ${{ parameters.buildConfiguration }} @@ -113,7 +113,7 @@ jobs: build: AKV mdsPackageVersion: ${{parameters.mdsPackageVersion}} - - template: ../steps/generate-nuget-package-step.yml@self + - template: /eng/pipelines/common/templates/steps/generate-nuget-package-step.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} displayName: 'Create AKV NuGet Package' diff --git a/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml b/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml index de1397f680..9cda6aed12 100644 --- a/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml @@ -59,12 +59,12 @@ jobs: - pwsh: 'Get-ChildItem env: | Sort-Object Name' displayName: '[Debug] List Environment Variables' - - template: ../steps/ensure-dotnet-version.yml@self + - template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml@self parameters: packageType: sdk version: '10.0' - - template: ../steps/ensure-dotnet-version.yml@self + - template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml@self parameters: packageType: runtime version: '9.0' 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 fa95ea52b0..d68007e410 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -158,7 +158,7 @@ jobs: displayName: 'Verify Password' - ${{ if ne(parameters.configProperties, '{}') }}: - - template: ../steps/update-config-file-step.yml@self # update config.json file + - template: /eng/pipelines/common/templates/steps/update-config-file-step.yml@self # update config.json file parameters: debug: ${{ parameters.debug }} UseManagedSNIOnWindows: ${{ parameters.usemanagedSNI }} @@ -233,7 +233,7 @@ jobs: displayName: 'Start Sql Browser' condition: eq(variables['Agent.OS'], 'Windows_NT') - ${{ elseif eq(parameters.configSqlFor, 'local') }}: - - template: ../steps/configure-sql-server-step.yml@self # configure SQL Server + - template: /eng/pipelines/common/templates/steps/configure-sql-server-step.yml@self # configure SQL Server parameters: operatingSystem: ${{ parameters.operatingSystem }} netcoreVersionTestUtils: ${{ parameters.netcoreVersionTestUtils }} @@ -264,7 +264,7 @@ jobs: ${{ if parameters.configProperties.FileStreamDirectory }}: fileStreamDirectory: ${{ parameters.configProperties.FileStreamDirectory }} - - template: ../steps/build-all-tests-step.yml@self # build tests + - template: /eng/pipelines/common/templates/steps/build-all-tests-step.yml@self # build tests parameters: targetFramework: ${{ parameters.targetFramework }} buildConfiguration: ${{ parameters.buildConfiguration }} @@ -277,7 +277,7 @@ jobs: OSGroup: Unix - ${{ if eq(parameters.enableX64Test, true) }}: # run native tests - - template: ../steps/run-all-tests-step.yml@self # run tests + - template: /eng/pipelines/common/templates/steps/run-all-tests-step.yml@self # run tests parameters: debug: ${{ parameters.debug }} targetFramework: ${{ parameters.targetFramework }} @@ -298,7 +298,7 @@ jobs: # forever and needs a serious rethinking). - ${{ if ne(variables['dotnetx86RootPath'], '') }}: - ${{ if startswith(parameters.targetFramework, 'net4')}}: - - template: ../steps/ensure-dotnet-version.yml + - template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml parameters: installDir: "$(dotnetx86RootPath)" packageType: "sdk" @@ -313,14 +313,14 @@ jobs: echo %TrimmedFrameworkVersion% echo ##vso[task.setvariable variable=netVersionX86]%TrimmedFrameworkVersion% displayName: "Trim dotnet version" - - template: ../steps/ensure-dotnet-version.yml + - template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml parameters: installDir: "$(dotnetx86RootPath)" packageType: "sdk" usePreview: "false" version: $(netVersionX86) - - template: ../steps/run-all-tests-step.yml@self + - template: /eng/pipelines/common/templates/steps/run-all-tests-step.yml@self parameters: debug: ${{ parameters.debug }} targetFramework: ${{ parameters.targetFramework }} @@ -332,7 +332,7 @@ jobs: operatingSystem: ${{ parameters.operatingSystem }} - ${{ 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 + - template: /eng/pipelines/common/templates/steps/publish-test-results-step.yml@self parameters: debug: ${{ parameters.debug }} targetFramework: ${{ parameters.targetFramework }} 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 61cb1db200..3817fcb483 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 @@ -41,34 +41,34 @@ jobs: vmImage: 'ADO-MMS22-SQL19' variables: # More settings at https://aka.ms/obpipelines/yaml/jobs - - template: ../../../libraries/mds-validation-variables.yml@self + - template: /eng/pipelines/libraries/mds-validation-variables.yml@self steps: - - template: ../steps/pre-build-step.yml + - template: /eng/pipelines/common/templates/steps/pre-build-step.yml - ${{parameters.downloadPackageStep }} - - template: ../steps/update-nuget-config-local-feed-step.yml + - template: /eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml parameters: packagePath: $(Pipeline.Workspace)\${{parameters.packageFolderName }} - - template: ../steps/update-config-file-step.yml + - template: /eng/pipelines/common/templates/steps/update-config-file-step.yml parameters: TCPConnectionString: $(SQL_TCP_CONN_STRING) NPConnectionString: $(SQL_NP_CONN_STRING) SupportsIntegratedSecurity: false - - template: ../steps/prepare-test-db-step.yml + - template: /eng/pipelines/common/templates/steps/prepare-test-db-step.yml # build & test - - template: ../steps/build-and-run-tests-netfx-step.yml + - template: /eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml parameters: referenceType: Package buildConfiguration: Release ${{ if parameters.isPreview }}: mdsPackageVersion: $(previewMdsPackageVersion) - - template: ../steps/build-and-run-tests-netcore-step.yml + - template: /eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml parameters: referenceType: Package buildConfiguration: Release 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 030199e72b..b734f70de7 100644 --- a/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml @@ -40,7 +40,7 @@ jobs: vmImage: 'ADO-MMS22-SQL19' variables: # More settings at https://aka.ms/obpipelines/yaml/jobs - - template: ../../../libraries/mds-validation-variables.yml@self + - template: /eng/pipelines/libraries/mds-validation-variables.yml@self - name: pathToDownloadedNuget # path to the downloaded nuget files value: $(Pipeline.Workspace)\${{parameters.packageFolderName }} 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 6d70f29900..febcbe83df 100644 --- a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml +++ b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml @@ -68,7 +68,7 @@ stages: - ${{ each platform in config.value.buildPlatforms }}: - ${{ each testSet in config.value.TestSets }}: - ${{ if contains(targetFramework, 'net4') }}: # .NET Framework - - template: ../jobs/ci-run-tests-job.yml@self + - template: /eng/pipelines/common/templates/jobs/ci-run-tests-job.yml@self parameters: debug: ${{ parameters.debug }} buildConfiguration: ${{ parameters.buildConfiguration }} @@ -101,7 +101,7 @@ stages: enableX64Test: false - ${{ else }}: # .NET - ${{ each useManagedSNI in config.value.useManagedSNI }}: - - template: ../jobs/ci-run-tests-job.yml@self + - template: /eng/pipelines/common/templates/jobs/ci-run-tests-job.yml@self parameters: debug: ${{ parameters.debug }} buildConfiguration: ${{ parameters.buildConfiguration }} diff --git a/eng/pipelines/common/templates/steps/ci-prebuild-step.yml b/eng/pipelines/common/templates/steps/ci-prebuild-step.yml index 6762da1829..2ed7f4c2fb 100644 --- a/eng/pipelines/common/templates/steps/ci-prebuild-step.yml +++ b/eng/pipelines/common/templates/steps/ci-prebuild-step.yml @@ -15,19 +15,19 @@ parameters: - Package steps: -- template: ensure-dotnet-version.yml +- template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml parameters: packageType: sdk usePreview: false version: 10.0 -- template: ensure-dotnet-version.yml +- template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml parameters: packageType: runtime usePreview: false version: 9.0 -- template: ensure-dotnet-version.yml +- template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml parameters: packageType: runtime usePreview: false @@ -39,7 +39,7 @@ steps: displayName: 'List Environment Variables [debug]' - ${{if eq(parameters.referenceType, 'Package')}}: - - template: update-nuget-config-local-feed-step.yml@self + - template: /eng/pipelines/common/templates/steps/update-nuget-config-local-feed-step.yml@self parameters: debug: ${{ parameters.debug }} 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 1424fdf2ea..7b93c0c1c2 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -53,12 +53,12 @@ parameters: default: '' steps: -- template: ./ensure-dotnet-version.yml@self +- template: /eng/pipelines/common/templates/ensure-dotnet-version.yml@self parameters: packageType: 'sdk' version: '10.0' -- template: ./ensure-dotnet-version.yml@self +- template: /eng/pipelines/common/templates/ensure-dotnet-version.yml@self parameters: packageType: 'runtime' version: '9.0' diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-step.yml index bfb89e34b9..a11dd38409 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-step.yml @@ -71,7 +71,7 @@ parameters: steps: - ${{ if eq(parameters.operatingSystem, 'Windows') }}: # windows only steps - - template: configure-sql-server-win-step.yml@self + - template: /eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml@self parameters: instanceName: ${{parameters.instanceName}} user: ${{parameters.user}} @@ -89,13 +89,13 @@ steps: - ${{ elseif eq(parameters.operatingSystem, 'Linux') }}: # Linux only steps - - template: configure-sql-server-linux-step.yml@self + - template: /eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml@self parameters: password: ${{parameters.password}} - ${{ elseif eq(parameters.operatingSystem, 'Mac') }}: # macOS only steps - - template: configure-sql-server-macos-step.yml@self + - template: /eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml@self parameters: password: ${{parameters.password}} diff --git a/eng/pipelines/common/templates/steps/pre-build-step.yml b/eng/pipelines/common/templates/steps/pre-build-step.yml index cfcd9a46f8..c6cb6ca308 100644 --- a/eng/pipelines/common/templates/steps/pre-build-step.yml +++ b/eng/pipelines/common/templates/steps/pre-build-step.yml @@ -4,18 +4,18 @@ # See the LICENSE file in the project root for more information. # ################################################################################# steps: -- template: ./ensure-dotnet-version.yml@self +- template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml@self parameters: packageType: 'sdk' version: '10.0' usePreview: false -- template: ./ensure-dotnet-version.yml@self +- template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml@self parameters: packageType: 'runtime' version: '9.0' -- template: ./ensure-dotnet-version.yml@self +- template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml@self parameters: packageType: 'runtime' version: '8.0' diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index bf18ff2f2d..0db276944b 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -104,7 +104,7 @@ parameters: default: true variables: - - template: libraries/ci-build-variables.yml@self + - template: /eng/pipelines/libraries/ci-build-variables.yml@self - name: abstractionsArtifactName value: Abstractions.Artifact @@ -119,7 +119,7 @@ stages: # 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 + - template: /eng/pipelines/stages/build-abstractions-package-ci-stage.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} abstractionsPackageVersion: $(abstractionsPackageVersion) @@ -140,7 +140,7 @@ stages: dependsOn: [] jobs: - - template: common/templates/jobs/ci-build-nugets-job.yml@self + - template: /eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} referenceType: ${{ parameters.referenceType }} @@ -150,14 +150,14 @@ stages: mdsArtifactName: $(mdsArtifactName) ${{if ne(parameters.SNIVersion, '')}}: prebuildSteps: - - template: common/templates/steps/override-sni-version.yml@self + - template: /eng/pipelines/common/templates/steps/override-sni-version.yml@self parameters: 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 + - template: /eng/pipelines/stages/build-azure-package-ci-stage.yml@self parameters: abstractionsArtifactName: $(abstractionsArtifactName) abstractionsPackageVersion: $(abstractionsPackageVersion) @@ -175,7 +175,7 @@ stages: # Run the stress tests, if desired. - ${{ if eq(parameters.enableStressTests, true) }}: - - template: stages/stress-tests-ci-stage.yml@self + - template: /eng/pipelines/stages/stress-tests-ci-stage.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} dependsOn: [build_mds_akv_packages_stage] @@ -185,7 +185,7 @@ stages: verbosity: 'detailed' # Run the MDS and AKV tests. - - template: common/templates/stages/ci-run-tests-stage.yml@self + - template: /eng/pipelines/common/templates/stages/ci-run-tests-stage.yml@self parameters: debug: ${{ parameters.debug }} buildConfiguration: ${{ parameters.buildConfiguration }} @@ -197,8 +197,6 @@ stages: mdsArtifactName: $(mdsArtifactName) mdsPackageVersion: $(mdsPackageVersion) testJobTimeout: ${{ parameters.testJobTimeout }} - ${{ if eq(parameters.buildType, 'Package') }}: - dependsOn: build_nugets # When testing MDS via packages, we must depend on the Abstractions, # Azure, and MDS packages. @@ -210,21 +208,21 @@ stages: 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 + - template: /eng/pipelines/common/templates/steps/override-sni-version.yml@self parameters: SNIVersion: ${{parameters.SNIVersion}} SNIValidationFeed: ${{parameters.SNIValidationFeed}} - - template: common/templates/steps/ci-prebuild-step.yml@self + - template: /eng/pipelines/common/templates/steps/ci-prebuild-step.yml@self parameters: debug: ${{ parameters.debug }} referenceType: ${{ parameters.referenceType }} # Include the code coverage job if the build type is Project. - ${{ if eq(parameters.buildType, 'Project') }}: + ${{ if eq(parameters.referenceType, 'Project') }}: # Jobs to run as part of the tests stage, after the tests are done. postTestJobs: - - template: common/templates/jobs/ci-code-coverage-job.yml@self + - template: /eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml@self parameters: debug: ${{ parameters.debug }} image: ADO-MMS22-CodeCov @@ -528,7 +526,7 @@ stages: hostedPool: true images: MacOSLatest_Sql22: macos-latest - TargetFrameworks: ${{parameters.targetFrameworksLinux }} + TargetFrameworks: ${{parameters.targetFrameworksUnix }} netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} buildPlatforms: [AnyCPU] testSets: ${{parameters.testSets }} diff --git a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml index a05cc5401f..c759e8937b 100644 --- a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml @@ -145,7 +145,7 @@ extends: - stage: buildMDS displayName: 'Build MDS' jobs: - - template: eng/pipelines/common/templates/jobs/build-signed-package-job.yml@self + - template: /eng/pipelines/common/templates/jobs/build-signed-package-job.yml@self parameters: symbolsFolder: $(symbolsFolder) softwareFolder: $(softwareFolder) @@ -156,7 +156,7 @@ extends: displayName: 'MDS Package Validation' dependsOn: buildMDS jobs: - - template: eng/pipelines/common/templates/jobs/validate-signed-package-job.yml@self + - template: /eng/pipelines/common/templates/jobs/validate-signed-package-job.yml@self parameters: packageFolderName: $(packageFolderName) isPreview: ${{ parameters['isPreview'] }} @@ -167,7 +167,7 @@ extends: displayName: 'Download NuGet Package' # Disabling as of 10/15/2025 due to OneBranch apparently disallowing MSBuild tasks in validation stages. -# - template: eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml@self +# - template: /eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml@self # parameters: # packageFolderName: $(packageFolderName) # isPreview: ${{ parameters['isPreview'] }} diff --git a/eng/pipelines/jobs/build-akv-official-job.yml b/eng/pipelines/jobs/build-akv-official-job.yml index 12909a04e0..5b2f15d3f4 100644 --- a/eng/pipelines/jobs/build-akv-official-job.yml +++ b/eng/pipelines/jobs/build-akv-official-job.yml @@ -82,7 +82,7 @@ jobs: ob_outputDirectory: '$(ARTIFACT_PATH)' steps: - - template: ../steps/script-output-environment-variables-step.yml@self + - template: /eng/pipelines/steps/script-output-environment-variables-step.yml@self - powershell: | $jsonParams = '${{ convertToJson(parameters) }}' -replace '\\', '\\' @@ -91,13 +91,13 @@ jobs: # Perform analysis before building, since this step will clobber build # output. - - template: ../steps/roslyn-analyzers-akv-step.yml@self + - template: /eng/pipelines/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 + - template: /eng/pipelines/steps/compound-build-akv-step.yml@self parameters: akvAssemblyFileVersion: '${{ parameters.akvAssemblyFileVersion }}' akvPackageVersion: '${{ parameters.akvPackageVersion }}' @@ -105,7 +105,7 @@ jobs: mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' - ${{ each targetFramework in parameters.targetFrameworks }}: - - template: ../steps/compound-extract-akv-apiscan-files-step.yml + - template: /eng/pipelines/steps/compound-extract-akv-apiscan-files-step.yml@self parameters: buildConfiguration: '${{ parameters.buildConfiguration }}' dllPath: '${{ parameters.apiScanDllPath }}' @@ -113,7 +113,7 @@ jobs: referenceType: Package targetFramework: '${{ targetFramework }}' - - template: ../steps/compound-esrp-code-signing-step.yml@self + - template: /eng/pipelines/steps/compound-esrp-code-signing-step.yml@self parameters: appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' @@ -123,7 +123,7 @@ jobs: esrpClientId: '${{ parameters.signingEsrpClientId }}' esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - - template: ../steps/compound-nuget-pack-step.yml@self + - template: /eng/pipelines/steps/compound-nuget-pack-step.yml@self parameters: buildConfiguration: '${{ parameters.buildConfiguration }}' generateSymbolsPackage: true # Always generate symbols, even if they are not published @@ -133,7 +133,7 @@ jobs: referenceType: Package properties: MdsPackageVersion=${{ parameters.mdsPackageVersion }} - - template: ../steps/compound-esrp-code-signing-step.yml@self + - template: /eng/pipelines/steps/compound-esrp-code-signing-step.yml@self parameters: appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' @@ -144,7 +144,7 @@ jobs: esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' - ${{ if parameters.publishSymbols }}: - - template: ../steps/compound-publish-symbols-step.yml@self + - template: /eng/pipelines/steps/compound-publish-symbols-step.yml@self parameters: artifactName: 'akv_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_${{ parameters.akvPackageVersion }}_$(System.TimelineId)' azureSubscription: '${{ parameters.symbolsAzureSubscription }}' diff --git a/eng/pipelines/libraries/build-variables.yml b/eng/pipelines/libraries/build-variables.yml index 3dd87fe7d7..a5b9fc99c4 100644 --- a/eng/pipelines/libraries/build-variables.yml +++ b/eng/pipelines/libraries/build-variables.yml @@ -5,5 +5,5 @@ ################################################################################# variables: - - template: common-variables.yml@self - - template: mds-variables.yml@self + - template: /eng/pipelines/libraries/common-variables.yml@self + - template: /eng/pipelines/libraries/mds-variables.yml@self diff --git a/eng/pipelines/libraries/mds-validation-variables.yml b/eng/pipelines/libraries/mds-validation-variables.yml index 93dc0804ff..f465c9c390 100644 --- a/eng/pipelines/libraries/mds-validation-variables.yml +++ b/eng/pipelines/libraries/mds-validation-variables.yml @@ -5,8 +5,8 @@ ################################################################################# variables: - - template: common-variables.yml@self - - template: mds-variables.yml@self + - template: /eng/pipelines/libraries/common-variables.yml@self + - template: /eng/pipelines/libraries/mds-variables.yml@self - name: TempFolderName # extract the nuget package here value: temp diff --git a/eng/pipelines/libraries/variables.yml b/eng/pipelines/libraries/variables.yml index 57894459d3..03cf141318 100644 --- a/eng/pipelines/libraries/variables.yml +++ b/eng/pipelines/libraries/variables.yml @@ -5,7 +5,7 @@ ################################################################################# variables: - - template: build-variables.yml@self + - template: /eng/pipelines/libraries/build-variables.yml@self # onebranch template variables - name: ob_outputDirectory value: '$(artifactDirectory)' # this directory is uploaded to pipeline artifacts, reddog and cloudvault. More info at https://aka.ms/obpipelines/artifacts diff --git a/eng/pipelines/stages/build-abstractions-package-ci-stage.yml b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml index 0d84dc6ccb..b626e425f2 100644 --- a/eng/pipelines/stages/build-abstractions-package-ci-stage.yml +++ b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml @@ -63,7 +63,7 @@ stages: # ------------------------------------------------------------------------ # Build and test on Linux. - - template: ../jobs/test-abstractions-package-ci-job.yml@self + - template: /eng/pipelines/jobs/test-abstractions-package-ci-job.yml@self parameters: jobNameSuffix: linux displayNamePrefix: Linux @@ -77,7 +77,7 @@ stages: # ------------------------------------------------------------------------ # Build and test on Windows - - template: ../jobs/test-abstractions-package-ci-job.yml@self + - template: /eng/pipelines/jobs/test-abstractions-package-ci-job.yml@self parameters: jobNameSuffix: windows displayNamePrefix: Win @@ -91,7 +91,7 @@ stages: # ------------------------------------------------------------------------ # Build and test on macOS. - - template: ../jobs/test-abstractions-package-ci-job.yml + - template: /eng/pipelines/jobs/test-abstractions-package-ci-job.yml@self parameters: jobNameSuffix: macos displayNamePrefix: macOS @@ -105,7 +105,7 @@ stages: # ------------------------------------------------------------------------ # Create and publish the NuGet package. - - template: ../jobs/pack-abstractions-package-ci-job.yml@self + - template: /eng/pipelines/jobs/pack-abstractions-package-ci-job.yml@self parameters: artifactName: ${{ parameters.artifactName }} buildConfiguration: ${{ parameters.buildConfiguration }} diff --git a/eng/pipelines/stages/build-azure-package-ci-stage.yml b/eng/pipelines/stages/build-azure-package-ci-stage.yml index 90beb30caf..ae5606806f 100644 --- a/eng/pipelines/stages/build-azure-package-ci-stage.yml +++ b/eng/pipelines/stages/build-azure-package-ci-stage.yml @@ -94,7 +94,7 @@ stages: # ------------------------------------------------------------------------ # Build and test on Linux. - - template: ../jobs/test-azure-package-ci-job.yml@self + - template: /eng/pipelines/jobs/test-azure-package-ci-job.yml@self parameters: abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} @@ -111,7 +111,7 @@ stages: # ------------------------------------------------------------------------ # Build and test on Windows - - template: ../jobs/test-azure-package-ci-job.yml@self + - template: /eng/pipelines/jobs/test-azure-package-ci-job.yml@self parameters: abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} @@ -128,7 +128,7 @@ stages: # ------------------------------------------------------------------------ # Build and test on macOS. - - template: ../jobs/test-azure-package-ci-job.yml + - template: /eng/pipelines/jobs/test-azure-package-ci-job.yml@self parameters: abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} @@ -145,7 +145,7 @@ stages: # ------------------------------------------------------------------------ # Create and publish the NuGet package. - - template: ../jobs/pack-azure-package-ci-job.yml@self + - template: /eng/pipelines/jobs/pack-azure-package-ci-job.yml@self parameters: abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} diff --git a/eng/pipelines/stages/stress-tests-ci-stage.yml b/eng/pipelines/stages/stress-tests-ci-stage.yml index b7cea84b82..bc4bda6b56 100644 --- a/eng/pipelines/stages/stress-tests-ci-stage.yml +++ b/eng/pipelines/stages/stress-tests-ci-stage.yml @@ -132,7 +132,7 @@ stages: # -------------------------------------------------------------------------- # Build and test on Linux. - - template: ../jobs/stress-tests-ci-job.yml@self + - template: /eng/pipelines/jobs/stress-tests-ci-job.yml@self parameters: jobNameSuffix: linux displayNamePrefix: Linux @@ -150,7 +150,7 @@ stages: # -------------------------------------------------------------------------- # Build and test on Windows - - template: ../jobs/stress-tests-ci-job.yml + - template: /eng/pipelines/jobs/stress-tests-ci-job.yml@self parameters: jobNameSuffix: windows displayNamePrefix: Win @@ -173,7 +173,7 @@ stages: # -------------------------------------------------------------------------- # Build and test on macOS. - - template: ../jobs/stress-tests-ci-job.yml + - template: /eng/pipelines/jobs/stress-tests-ci-job.yml@self parameters: jobNameSuffix: macos displayNamePrefix: macOS From b3fd95bfb010d436b44085a670b252edba52b954 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:33:36 -0400 Subject: [PATCH 07/13] Fixed stale parameter names in PR pipelines. --- eng/pipelines/sqlclient-pr-package-ref-pipeline.yml | 2 +- eng/pipelines/sqlclient-pr-project-ref-pipeline.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index bc88cb5f38..da3e2f7b70 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -128,7 +128,7 @@ extends: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildPlatforms: ${{ parameters.buildPlatforms }} - buildType: Package + referenceType: Package codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} debug: ${{ parameters.debug }} enableStressTests: ${{ parameters.enableStressTests }} diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index 2fc0cad210..1244476d67 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -128,7 +128,7 @@ extends: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildPlatforms: ${{ parameters.buildPlatforms }} - buildType: Project + referenceType: Project codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} debug: ${{ parameters.debug }} enableStressTests: ${{ parameters.enableStressTests }} From 0e2a83e40aba0fa4f0a2d277486f4a9fa5cebfcc Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:35:12 -0400 Subject: [PATCH 08/13] Fixed incorrect template paths. --- .../common/templates/steps/ci-project-build-step.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7b93c0c1c2..4e6c8ac07d 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -53,12 +53,12 @@ parameters: default: '' steps: -- template: /eng/pipelines/common/templates/ensure-dotnet-version.yml@self +- template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml@self parameters: packageType: 'sdk' version: '10.0' -- template: /eng/pipelines/common/templates/ensure-dotnet-version.yml@self +- template: /eng/pipelines/common/templates/steps/ensure-dotnet-version.yml@self parameters: packageType: 'runtime' version: '9.0' From f07d849ee5f3480eea098b81b0251a7ee5ae4985 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:36:49 -0400 Subject: [PATCH 09/13] Fixed stale parameter names. --- .../common/templates/steps/generate-nuget-package-step.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3a57aa93a8..7ed33cbdc2 100644 --- a/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml +++ b/eng/pipelines/common/templates/steps/generate-nuget-package-step.yml @@ -61,6 +61,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.buildConfiguration}};ReferenceType=${{parameters.referenceType}};${{parameters.properties}}"' + arguments: 'pack -Symbols -SymbolPackageFormat snupkg ${{parameters.nuspecPath}} -Version ${{parameters.packageVersion}} -OutputDirectory ${{parameters.outputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.buildConfiguration}};ReferenceType=${{parameters.referenceType}};${{parameters.properties}}"' ${{else }}: - arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.NugetPackageVersion}} -OutputDirectory ${{parameters.OutputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.buildConfiguration}};ReferenceType=${{parameters.referenceType}};${{parameters.properties}}"' + arguments: 'pack ${{parameters.nuspecPath}} -Version ${{parameters.packageVersion}} -OutputDirectory ${{parameters.outputDirectory}} -properties "COMMITID=$(CommitHead);Configuration=${{parameters.buildConfiguration}};ReferenceType=${{parameters.referenceType}};${{parameters.properties}}"' From 4a0eb74c8fe85e3e70c9f5d2c184aca7a7140fd1 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 2 Jan 2026 19:29:23 -0400 Subject: [PATCH 10/13] - Updated .NET SDK to 10 for Abstractions and Azure packages. - Added .NET 10 as a target for the Abstractions and Azure tests. --- .../jobs/pack-abstractions-package-ci-job.yml | 6 +++--- eng/pipelines/jobs/pack-azure-package-ci-job.yml | 8 ++++---- .../jobs/test-abstractions-package-ci-job.yml | 11 +++++++++-- eng/pipelines/jobs/test-azure-package-ci-job.yml | 11 +++++++++-- .../Abstractions/test/Abstractions.Test.csproj | 2 +- .../Azure/test/Azure.Test.csproj | 2 +- 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml index 13bf3618fb..ce4e332012 100644 --- a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml @@ -102,12 +102,12 @@ jobs: steps: - # Install the .NET 9.0 SDK. + # Install the .NET 10.0 SDK. - task: UseDotNet@2 - displayName: Install .NET 9.0 SDK + displayName: Install .NET 10.0 SDK inputs: packageType: sdk - version: 9.x + version: 10.x # We use the 'custom' command because the DotNetCoreCLI@2 task doesn't # support all of our argument combinations for the different build steps. diff --git a/eng/pipelines/jobs/pack-azure-package-ci-job.yml b/eng/pipelines/jobs/pack-azure-package-ci-job.yml index 9cc33eadb1..9657916e62 100644 --- a/eng/pipelines/jobs/pack-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-azure-package-ci-job.yml @@ -141,13 +141,13 @@ jobs: # 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. + + # Install the .NET 10.0 SDK. - task: UseDotNet@2 - displayName: Install .NET 9.0 SDK + displayName: Install .NET 10.0 SDK inputs: packageType: sdk - version: 9.x + version: 10.x # We use the 'custom' command because the DotNetCoreCLI@2 task doesn't # support all of our argument combinations for the different build steps. diff --git a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml index a5e0f98978..f20793ca86 100644 --- a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml @@ -115,11 +115,18 @@ jobs: steps: - # Install the .NET 9.0 SDK. + # Install the .NET 10.0 SDK. - task: UseDotNet@2 - displayName: Install .NET 9.0 SDK + displayName: Install .NET 10.0 SDK inputs: packageType: sdk + version: 10.x + + # Install the .NET 9.0 runtime. + - task: UseDotNet@2 + displayName: Install .NET 9.0 Runtime + inputs: + packageType: runtime version: 9.x # Install the .NET 8.0 runtime. diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index 8e1b8fc6db..1daffdd012 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -155,11 +155,18 @@ jobs: - pwsh: cp $(Build.SourcesDirectory)/NuGet.config.local $(Build.SourcesDirectory)/NuGet.config displayName: Use local NuGet.config - # Install the .NET 9.0 SDK. + # Install the .NET 10.0 SDK. - task: UseDotNet@2 - displayName: Install .NET 9.0 SDK + displayName: Install .NET 10.0 SDK inputs: packageType: sdk + version: 10.x + + # Install the .NET 9.0 runtime. + - task: UseDotNet@2 + displayName: Install .NET 9.0 Runtime + inputs: + packageType: runtime version: 9.x # Install the .NET 8.0 runtime. 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 053804342e..52247e6130 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;net8.0;net9.0 + net462;net8.0;net9.0;net10.0 enable enable false 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 ad94e9b4b1..a2397fe933 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;net8.0;net9.0 + net462;net8.0;net9.0;net10.0 enable enable false From ea684f50e766117d77bbda316a8490e2c1086993 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 5 Jan 2026 08:18:19 -0400 Subject: [PATCH 11/13] Fixed pipeline pool name for macOS test jobs. --- eng/pipelines/dotnet-sqlclient-ci-core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 0db276944b..8c0b3b011e 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -522,7 +522,7 @@ stages: # Self hosted SQL Server on Mac mac_sql_22: - pool: $(defaultHostedPoolName) + pool: Azure Pipelines hostedPool: true images: MacOSLatest_Sql22: macos-latest From beba4a4478cab6ea39694299f96c15a9f774427c Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 15 Jan 2026 08:53:43 -0400 Subject: [PATCH 12/13] Move ActiveDirectoryAuthenticationProvider Tests (#3717) --- .github/workflows/codeql.yml | 28 +- Directory.Packages.props | 5 + build.proj | 2 +- eng/pipelines/akv-official-pipeline.yml | 2 +- .../templates/steps/ensure-dotnet-version.yml | 7 + .../steps/update-config-file-step.yml | 7 +- eng/pipelines/dotnet-sqlclient-ci-core.yml | 35 +- ...qlclient-ci-package-reference-pipeline.yml | 15 +- ...qlclient-ci-project-reference-pipeline.yml | 15 +- .../dotnet-sqlclient-signing-pipeline.yml | 2 +- .../jobs/pack-abstractions-package-ci-job.yml | 35 +- .../jobs/pack-azure-package-ci-job.yml | 43 +- .../jobs/test-abstractions-package-ci-job.yml | 50 ++- .../jobs/test-azure-package-ci-job.yml | 134 +++++- .../sqlclient-pr-package-ref-pipeline.yml | 2 +- .../sqlclient-pr-project-ref-pipeline.yml | 2 +- .../build-abstractions-package-ci-stage.yml | 57 +-- .../stages/build-azure-package-ci-stage.yml | 139 +++++-- .../stages/stress-tests-ci-stage.yml | 53 ++- .../steps/compound-build-akv-step.yml | 6 + src/Directory.Build.props | 41 +- .../Abstractions/src/Abstractions.csproj | 6 + .../src/SqlAuthenticationProviderInternal.cs | 80 ++-- .../test/Abstractions.Test.csproj | 15 + .../test/SqlAuthenticationProviderTest.cs | 13 +- .../Azure/test/AADAuthenticationTests.cs | 33 ++ .../Azure/test/AADConnectionTest.cs | 377 +++++++++++++++++ .../Azure/test/Azure.Test.csproj | 40 ++ .../Azure/test/Config.cs | 275 ++++++++++++ .../Azure/test/DefaultAuthProviderTests.cs | 62 +++ .../Azure/test/StringExtensions.cs | 13 + .../test/WorkloadIdentityFederationTests.cs | 65 +++ .../ref/Microsoft.Data.SqlClient.csproj | 12 +- .../src/Microsoft.Data.SqlClient.csproj | 11 +- .../netfx/ref/Microsoft.Data.SqlClient.csproj | 12 +- .../netfx/src/Microsoft.Data.SqlClient.csproj | 13 +- .../tests/Common/Common.csproj | 11 +- .../FunctionalTests/AADAuthenticationTests.cs | 26 +- ...soft.Data.SqlClient.FunctionalTests.csproj | 17 +- .../SqlAuthenticationProviderManagerTests.cs | 35 ++ .../SqlAuthenticationProviderTest.cs | 36 -- .../ManualTests/DataCommon/DataTestUtility.cs | 9 + .../DataCommon/ManagedIdentityProvider.cs | 97 +++++ .../DataCommon/UsernamePasswordProvider.cs | 72 ++++ ....Data.SqlClient.ManualTesting.Tests.csproj | 16 +- .../AADFedAuthTokenRefreshTest.cs | 23 +- .../ConnectivityTests/AADConnectionTest.cs | 390 +++++------------- .../tests/StressTests/Directory.Build.props | 2 +- .../StressTests/Directory.Packages.props | 30 +- .../SqlClient.Stress.Framework.csproj | 1 + .../Microsoft.Data.SqlClient.UnitTests.csproj | 14 +- ...rosoft.Data.SqlClient.TestUtilities.csproj | 9 +- .../Utils.cs | 1 - .../config.default.json | 3 +- .../xunit.runner.json | 23 +- .../Microsoft.SqlServer.Server.csproj | 2 +- .../StringsHelper.cs | 4 + tools/specs/Microsoft.Data.SqlClient.nuspec | 159 +++---- ...DllsForNetFxProjectReferenceBuilds.targets | 51 +++ tools/targets/GenerateMdsPackage.targets | 8 +- 60 files changed, 2047 insertions(+), 699 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/test/StringExtensions.cs create mode 100644 src/Microsoft.Data.SqlClient.Extensions/Azure/test/WorkloadIdentityFederationTests.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs delete mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ManagedIdentityProvider.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/UsernamePasswordProvider.cs create mode 100644 tools/targets/CopySniDllsForNetFxProjectReferenceBuilds.targets diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c5a76de2be..cddcff79de 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,9 +13,9 @@ name: "CodeQL Advanced" on: push: - branches: [ "main" ] + branches: [ "main", "feat/*" ] pull_request: - branches: [ "main" ] + branches: [ "main", "feat/*" ] schedule: - cron: '33 23 * * 6' @@ -60,9 +60,8 @@ jobs: - name: Setup .NET Core SDK uses: actions/setup-dotnet@v5.0.1 with: - # TODO: Update this to .NET 10 once PR #3686 is complete. # TODO: Replace this with global-json-file once PR #3797 is complete. - dotnet-version: 9.x + dotnet-version: 10.x dotnet-quality: ga #global-json-file: global.json @@ -88,9 +87,26 @@ jobs: - name: Run manual build steps if: matrix.build-mode == 'manual' shell: bash + # TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/38655): + # For some reason, the StressTests projects fail to restore in this + # environment, so we skip them by explicitly building all of the other + # projects instead of the main solution file. run: | - mkdir packages - dotnet build src/ + dotnet build src/Microsoft.SqlServer.Server + dotnet build src/Microsoft.Data.SqlClient.Extensions/Abstractions/src + dotnet build src/Microsoft.Data.SqlClient.Extensions/Abstractions/test + dotnet build src/Microsoft.Data.SqlClient/netcore/ref + dotnet build src/Microsoft.Data.SqlClient/netcore/src + dotnet build src/Microsoft.Data.SqlClient/netfx/ref + dotnet build src/Microsoft.Data.SqlClient/netfx/src + dotnet build src/Microsoft.Data.SqlClient/src + dotnet build src/Microsoft.Data.SqlClient.Extensions/Azure/src + dotnet build src/Microsoft.Data.SqlClient.Extensions/Azure/test + dotnet build src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider + dotnet build src/Microsoft.Data.SqlClient/tests/UnitTests + dotnet build src/Microsoft.Data.SqlClient/tests/FunctionalTests + dotnet build src/Microsoft.Data.SqlClient/tests/ManualTests + dotnet build src/Microsoft.Data.SqlClient/tests/PerformanceTests - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 diff --git a/Directory.Packages.props b/Directory.Packages.props index 39df423071..e86de72af2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -83,6 +83,11 @@ + + + + + diff --git a/build.proj b/build.proj index d8343382e3..bb574ba093 100644 --- a/build.proj +++ b/build.proj @@ -521,7 +521,7 @@ - + diff --git a/eng/pipelines/akv-official-pipeline.yml b/eng/pipelines/akv-official-pipeline.yml index 316ca9e6ea..f21ca656d3 100644 --- a/eng/pipelines/akv-official-pipeline.yml +++ b/eng/pipelines/akv-official-pipeline.yml @@ -49,7 +49,7 @@ resources: ref: 'refs/heads/main' extends: - template: 'v2/OneBranch.${{ parameters.oneBranchType }}.CrossPlat.yml@templates' + template: /v2/OneBranch.${{ parameters.oneBranchType }}.CrossPlat.yml@templates parameters: featureFlags: diff --git a/eng/pipelines/common/templates/steps/ensure-dotnet-version.yml b/eng/pipelines/common/templates/steps/ensure-dotnet-version.yml index fd6ba9674b..91724bf566 100644 --- a/eng/pipelines/common/templates/steps/ensure-dotnet-version.yml +++ b/eng/pipelines/common/templates/steps/ensure-dotnet-version.yml @@ -10,6 +10,13 @@ # Reason for not using UseDotNet task: # [BUG]: UseDotNet task installs x86 build on Windows arm64 # https://github.com/microsoft/azure-pipelines-tasks/issues/20300 +# +# A possible workaround is discussed here: +# +# https://github.com/microsoft/azure-pipelines-tasks/issues/16501 +# +# TODO: See if we can eliminate this template and just use the above workaround +# for the Windows x86 builds. parameters: - # Directory where dotnet binaries should be installed. If not specified, defaults to the diff --git a/eng/pipelines/common/templates/steps/update-config-file-step.yml b/eng/pipelines/common/templates/steps/update-config-file-step.yml index f49e552323..2bb7426be2 100644 --- a/eng/pipelines/common/templates/steps/update-config-file-step.yml +++ b/eng/pipelines/common/templates/steps/update-config-file-step.yml @@ -124,6 +124,10 @@ parameters: type: boolean default: true + - name: WorkloadIdentityFederationServiceConnectionId + type: string + default: '' + steps: # All properties should be added here, and this template should be used for any manipulation of the config.json file. - pwsh: | @@ -180,6 +184,7 @@ steps: $p.IsDNSCachingSupportedCR=[System.Convert]::ToBoolean("${{parameters.IsDNSCachingSupportedCR }}") $p.TracingEnabled=[System.Convert]::ToBoolean("${{parameters.TracingEnabled }}") $p.EnclaveEnabled=[System.Convert]::ToBoolean("${{parameters.EnclaveEnabled }}") + $p.WorkloadIdentityFederationServiceConnectionId="${{parameters.WorkloadIdentityFederationServiceConnectionId }}" } $jdata | ConvertTo-Json | Set-Content "config.json" workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities @@ -196,4 +201,4 @@ steps: } } workingDirectory: src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities - displayName: 'Read config.json [debug]' + displayName: '[Debug] Read config.json' diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 8c0b3b011e..a1e274a35e 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -103,6 +103,17 @@ parameters: type: boolean default: true +- name: dotnetVerbosity + displayName: dotnet CLI Verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + variables: - template: /eng/pipelines/libraries/ci-build-variables.yml@self @@ -121,11 +132,11 @@ stages: # under the given artifact name. - template: /eng/pipelines/stages/build-abstractions-package-ci-stage.yml@self parameters: - buildConfiguration: ${{ parameters.buildConfiguration }} + abstractionsArtifactName: $(abstractionsArtifactName) abstractionsPackageVersion: $(abstractionsPackageVersion) - artifactName: $(abstractionsArtifactName) - ${{if eq(parameters.debug, 'true')}}: - verbosity: diagnostic + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} # Build MDS and its NuGet packages. - stage: build_mds_akv_packages_stage @@ -164,25 +175,31 @@ stages: azureArtifactName: $(azureArtifactName) azurePackageVersion: $(azurePackageVersion) buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} + mdsArtifactName: $(mdsArtifactName) + mdsPackageVersion: $(mdsPackageVersion) # When building via package references, we must depend on the Abstractions - # package. + # and MDS packages ${{ if eq(parameters.referenceType, 'Package') }}: dependsOn: - build_abstractions_package_stage + - build_mds_akv_packages_stage referenceType: ${{ parameters.referenceType }} ${{if eq(parameters.debug, 'true')}}: - verbosity: diagnostic + dotnetVerbosity: diagnostic # Run the stress tests, if desired. - ${{ if eq(parameters.enableStressTests, true) }}: - template: /eng/pipelines/stages/stress-tests-ci-stage.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} - dependsOn: [build_mds_akv_packages_stage] + dependsOn: + - build_mds_akv_packages_stage + - build_azure_package_stage pipelineArtifactName: $(artifactName) mdsPackageVersion: $(mdsPackageVersion) - ${{ if eq(parameters.debug, 'true') }}: - verbosity: 'detailed' + azurePackageVersion: $(azurePackageVersion) + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} # Run the MDS and AKV tests. - template: /eng/pipelines/common/templates/stages/ci-run-tests-stage.yml@self diff --git a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml index 88c3abfc01..32cd90abb7 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-package-reference-pipeline.yml @@ -168,14 +168,27 @@ parameters: type: object default: [false, true] + # Dotnet CLI verbosity level. + - name: dotnetVerbosity + displayName: dotnet CLI Verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + extends: - template: dotnet-sqlclient-ci-core.yml@self + template: /eng/pipelines/dotnet-sqlclient-ci-core.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildPlatforms: ${{ parameters.buildPlatforms }} referenceType: Package codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} debug: ${{ parameters.debug }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} enableStressTests: ${{ parameters.enableStressTests }} targetFrameworks: ${{ parameters.targetFrameworks }} targetFrameworksUnix: ${{ parameters.targetFrameworksUnix }} diff --git a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml index 65104e6961..5c7dce804c 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-project-reference-pipeline.yml @@ -168,14 +168,27 @@ parameters: type: object default: [false, true] + # Dotnet CLI verbosity level. + - name: dotnetVerbosity + displayName: dotnet CLI Verbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + extends: - template: dotnet-sqlclient-ci-core.yml@self + template: /eng/pipelines/dotnet-sqlclient-ci-core.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildPlatforms: ${{ parameters.buildPlatforms }} referenceType: Project codeCovTargetFrameworks: ${{ parameters.codeCovTargetFrameworks }} debug: ${{ parameters.debug }} + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} enableStressTests: ${{ parameters.enableStressTests }} targetFrameworks: ${{ parameters.targetFrameworks }} targetFrameworksUnix: ${{ parameters.targetFrameworksUnix }} diff --git a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml index c759e8937b..9b45e6820d 100644 --- a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml @@ -88,7 +88,7 @@ resources: ref: refs/heads/main extends: - template: v2/OneBranch.${{parameters.oneBranchType }}.CrossPlat.yml@templates # https://aka.ms/obpipelines/templates + template: /v2/OneBranch.${{parameters.oneBranchType }}.CrossPlat.yml@templates # https://aka.ms/obpipelines/templates parameters: featureFlags: # Suggested by MerlinBot (https://sqlclientdrivers.visualstudio.com/ADO.Net/_git/dotnet-sqlclient/pullrequest/4882) diff --git a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml index ce4e332012..c928baa62d 100644 --- a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml @@ -12,21 +12,26 @@ 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 + - name: abstractionsArtifactName type: string default: Abstractions.Artifact + # The version to apply to the Abstractions NuGet package and its assemblies. + - name: abstractionsPackageVersion + type: string + # The type of build to test (Release or Debug) - name: buildConfiguration type: string values: - Release - Debug + + # True to enable extra debug steps and logging. + - name: debug + type: boolean + default: false # The list of upstream jobs to depend on. - name: dependsOn @@ -34,7 +39,7 @@ parameters: default: [] # The verbosity level for the dotnet CLI commands. - - name: verbosity + - name: dotnetVerbosity type: string default: normal values: @@ -69,13 +74,14 @@ jobs: # dotnet CLI arguments common to all commands. - name: commonArguments value: >- - --verbosity ${{ parameters.verbosity }} + --verbosity ${{ parameters.dotnetVerbosity }} # dotnet CLI arguments for build/test/pack commands - name: buildArguments value: >- $(commonArguments) --configuration ${{ parameters.buildConfiguration }} + -p:ForceMdsAssemblyNameSuffix=true -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} # Explicitly unset the $PLATFORM environment variable that is set by the @@ -102,6 +108,11 @@ jobs: steps: + # Emit environment variables if debug is enabled. + - ${{ if eq(parameters.debug, true) }}: + - pwsh: 'Get-ChildItem Env: | Sort-Object Name' + displayName: '[Debug] Print Environment Variables' + # Install the .NET 10.0 SDK. - task: UseDotNet@2 displayName: Install .NET 10.0 SDK @@ -112,18 +123,18 @@ jobs: # 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. + # Restore the project. - task: DotNetCoreCLI@2 - displayName: Restore Solution + displayName: Restore Project inputs: command: custom custom: restore projects: $(project) arguments: $(commonArguments) - # Build the solution. + # Build the project. - task: DotNetCoreCLI@2 - displayName: Build Solution + displayName: Build Project inputs: command: custom custom: build @@ -144,5 +155,5 @@ jobs: displayName: Publish Pipeline Artifact inputs: targetPath: $(dotnetPackagesDir) - artifactName: ${{ parameters.artifactName }} + artifactName: ${{ parameters.abstractionsArtifactName }} publishLocation: pipeline diff --git a/eng/pipelines/jobs/pack-azure-package-ci-job.yml b/eng/pipelines/jobs/pack-azure-package-ci-job.yml index 9657916e62..c2f6e408fe 100644 --- a/eng/pipelines/jobs/pack-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-azure-package-ci-job.yml @@ -40,12 +40,28 @@ parameters: values: - Release - Debug + + # True to enable extra debug steps and logging. + - name: debug + type: boolean + default: false # The list of upstream jobs to depend on. - name: dependsOn type: object default: [] + # The verbosity level for the dotnet CLI commands. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + # The reference type to use: # # Project - dependent projects are referenced directly. @@ -57,17 +73,6 @@ parameters: - 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 @@ -93,8 +98,9 @@ jobs: # dotnet CLI arguments common to all commands. - name: commonArguments value: >- - --verbosity ${{ parameters.verbosity }} + --verbosity ${{ parameters.dotnetVerbosity }} -p:ReferenceType=${{ parameters.referenceType }} + -p:ForceMdsAssemblyNameSuffix=true -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} # dotnet CLI arguments for build/test/pack commands @@ -128,6 +134,11 @@ jobs: steps: + # Emit environment variables if debug is enabled. + - ${{ if eq(parameters.debug, true) }}: + - pwsh: 'Get-ChildItem Env: | Sort-Object Name' + displayName: '[Debug] Print Environment Variables' + # We have a few extra steps for Package reference builds. - ${{ if eq(parameters.referenceType, 'Package') }}: @@ -152,18 +163,18 @@ jobs: # 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. + # Restore the project. - task: DotNetCoreCLI@2 - displayName: Restore Solution + displayName: Restore Project inputs: command: custom custom: restore projects: $(project) arguments: $(commonArguments) - # Build the solution. + # Build the project. - task: DotNetCoreCLI@2 - displayName: Build Solution + displayName: Build Project inputs: command: custom custom: build diff --git a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml index f20793ca86..965c2d7d0c 100644 --- a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml @@ -7,8 +7,9 @@ # 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. +# This template defines a job named +# 'test_abstractions_package_job_' that can be depended on by +# downstream jobs. parameters: @@ -19,6 +20,11 @@ parameters: - Release - Debug + # True to enable extra debug steps and logging. + - name: debug + type: boolean + default: false + # The prefix to prepend to the job's display name: # # [] Run Stress Tests @@ -26,6 +32,17 @@ parameters: - name: displayNamePrefix type: string + # The verbosity level for the dotnet CLI commands. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + # The suffix to append to the job name. - name: jobNameSuffix type: string @@ -44,17 +61,6 @@ parameters: - 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 @@ -77,19 +83,22 @@ jobs: variables: # The Abstractions test project file to use for all dotnet CLI commands. + # + # Building this project implicitly builds the Abstractions project. - 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 }} + --verbosity ${{ parameters.dotnetVerbosity }} # dotnet CLI arguments for build/test/pack commands - name: buildArguments value: >- $(commonArguments) --configuration ${{ parameters.buildConfiguration }} + -p:ForceMdsAssemblyNameSuffix=true # Explicitly unset the $PLATFORM environment variable that is set by the # 'ADO Build properties' Library in the ADO SqlClientDrivers public project. @@ -115,6 +124,11 @@ jobs: steps: + # Emit environment variables if debug is enabled. + - ${{ if eq(parameters.debug, true) }}: + - pwsh: 'Get-ChildItem Env: | Sort-Object Name' + displayName: '[Debug] Print Environment Variables' + # Install the .NET 10.0 SDK. - task: UseDotNet@2 displayName: Install .NET 10.0 SDK @@ -142,18 +156,18 @@ jobs: # 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. + # Restore the project. - task: DotNetCoreCLI@2 - displayName: Restore Solution + displayName: Restore Project inputs: command: custom custom: restore projects: $(project) arguments: $(commonArguments) - # Build the solution. + # Build the project. - task: DotNetCoreCLI@2 - displayName: Build Solution + displayName: Build Project inputs: command: custom custom: build diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index 1daffdd012..afba00dc03 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -7,8 +7,8 @@ # 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. +# This template defines a job named 'test_azure_package_job_' +# that can be depended on by downstream jobs. parameters: @@ -32,6 +32,11 @@ parameters: - Release - Debug + # True to enable extra debug steps and logging. + - name: debug + type: boolean + default: false + # The prefix to prepend to the job's display name: # # [] Run Stress Tests @@ -39,10 +44,34 @@ parameters: - name: displayNamePrefix type: string + # The verbosity level for the dotnet CLI commands. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + # The suffix to append to the job name. - name: jobNameSuffix type: string + # The name of the MDS pipeline artifact to download. + # + # This is used when the referenceType is 'Package'. + - name: mdsArtifactName + type: string + default: MDS.Artifact + + # The MDS package verion to depend on. + # + # This is used when the referenceType is 'Package'. + - name: mdsPackageVersion + type: string + # The list of .NET Framework runtimes to test against. - name: netFrameworkRuntimes type: object @@ -68,17 +97,6 @@ parameters: - 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 @@ -101,15 +119,19 @@ jobs: variables: # The Azure test project file to use for all dotnet CLI commands. + # + # Building this project implicitly builds the Azure project. - 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 }} + --verbosity ${{ parameters.dotnetVerbosity }} -p:ReferenceType=${{ parameters.referenceType }} + -p:ForceMdsAssemblyNameSuffix=true -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} # dotnet CLI arguments for build/test/pack commands - name: buildArguments @@ -141,6 +163,11 @@ jobs: steps: + # Emit environment variables if debug is enabled. + - ${{ if eq(parameters.debug, true) }}: + - pwsh: 'Get-ChildItem Env: | Sort-Object Name' + displayName: '[Debug] Print Environment Variables' + # We have a few extra steps for Package reference builds. - ${{ if eq(parameters.referenceType, 'Package') }}: @@ -151,6 +178,15 @@ jobs: artifactName: ${{ parameters.abstractionsArtifactName }} targetPath: $(Build.SourcesDirectory)/packages + # Download the MDS package artifacts into packages/. + # + # The Azure project doesn't depend on MDS, but the test project does. + - task: DownloadPipelineArtifact@2 + displayName: Download MDS Package Artifact + inputs: + artifactName: ${{ parameters.mdsArtifactName }} + 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 @@ -179,32 +215,87 @@ jobs: # The Windows agent images include a suitable .NET Framework runtime, so # we don't have to install one explicitly. + # Setup the test config file. + # + # This must be done before building the project. This template updates + # the sample config file, which is then copied into place by the build. + # + - template: /eng/pipelines/common/templates/steps/update-config-file-step.yml@self + parameters: + debug: ${{ parameters.debug }} + + # The config.json file has many options, but only some of them are + # used by the Azure package tests. We only specify the ones that are + # necessary here. + + AADServicePrincipalId: $(AADServicePrincipalId) + AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) + # macOS doesn't support managed identities. + ManagedIdentitySupported: ${{ not(eq(parameters.vmImage, 'macos-latest')) }} + SupportsIntegratedSecurity: ${{ eq(variables['SupportsIntegratedSecurity'], 'true') }} + TCPConnectionString: $(AZURE_DB_TCP_CONN_STRING) + UserManagedIdentityClientId: $(UserManagedIdentityClientId) + WorkloadIdentityFederationServiceConnectionId: $(WorkloadIdentityFederationServiceConnectionId) + # Note: Using the isFork variable to determine if secrets are + # available is not ideal since it's an indirect association. But + # everything else (referencing secret variables various 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) + AADServicePrincipalSecret: $(AADServicePrincipalSecret) + # 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. + # Restore the project. - task: DotNetCoreCLI@2 - displayName: Restore Solution + displayName: Restore Project inputs: command: custom custom: restore projects: $(project) arguments: $(commonArguments) - # Build the solution. + # Build the project. - task: DotNetCoreCLI@2 - displayName: Build Solution + displayName: Build Project inputs: command: custom custom: build projects: $(project) arguments: $(buildArguments) --no-restore + # List the DLLs in the output directory for debugging purposes. + - ${{ if eq(parameters.debug, true) }}: + - pwsh: > + Get-ChildItem + -Path "src/Microsoft.Data.SqlClient.Extensions/Azure/test/bin/${{ parameters.buildConfiguration }}" + -Recurse + displayName: '[Debug] List Output DLLs' + # Run the tests for each .NET runtime. - ${{ each runtime in parameters.netRuntimes }}: - task: DotNetCoreCLI@2 displayName: Test [${{ runtime }}] + env: + # Many of our tests require access to Azure resources that are + # currently only granted by agents running our custom ADO 1ES + # images in our ADO pools. + ${{ if startsWith(parameters.poolName, 'ADO-') }}: + ADO_POOL: 1 + # When using connectedServiceName below, the DotNetCoreCLI task + # needs the system access token to be injected as this environment + # variable. + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + ${{ if eq(parameters.debug, true) }}: + TEST_DEBUG_EMIT: 1 inputs: + # The tests need to access Azure resources, which is achieved via + # this service connection. See: + # + # https://sqlclientdrivers.visualstudio.com/public/_settings/adminservices?resourceId=ec9623b2-829c-497f-ae1f-7461766f9a9c + connectedServiceName: dotnetMSI-managed-identity command: custom custom: test projects: $(project) @@ -214,7 +305,14 @@ jobs: - ${{ each runtime in parameters.netFrameworkRuntimes }}: - task: DotNetCoreCLI@2 displayName: Test [${{ runtime }}] + env: + ${{ if startsWith(parameters.poolName, 'ADO-CI') }}: + ADO_POOL: 1 + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + ${{ if eq(parameters.debug, true) }}: + TEST_DEBUG_EMIT: 1 inputs: + connectedServiceName: dotnetMSI-managed-identity command: custom custom: test projects: $(project) diff --git a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml index da3e2f7b70..1d135cd328 100644 --- a/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-package-ref-pipeline.yml @@ -124,7 +124,7 @@ parameters: default: [false, true] extends: - template: dotnet-sqlclient-ci-core.yml@self + template: /eng/pipelines/dotnet-sqlclient-ci-core.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildPlatforms: ${{ parameters.buildPlatforms }} diff --git a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml index 1244476d67..9da1ddca38 100644 --- a/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml +++ b/eng/pipelines/sqlclient-pr-project-ref-pipeline.yml @@ -124,7 +124,7 @@ parameters: default: [false, true] extends: - template: dotnet-sqlclient-ci-core.yml@self + template: /eng/pipelines/dotnet-sqlclient-ci-core.yml@self parameters: buildConfiguration: ${{ parameters.buildConfiguration }} buildPlatforms: ${{ parameters.buildPlatforms }} diff --git a/eng/pipelines/stages/build-abstractions-package-ci-stage.yml b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml index b626e425f2..1f28d01a8e 100644 --- a/eng/pipelines/stages/build-abstractions-package-ci-stage.yml +++ b/eng/pipelines/stages/build-abstractions-package-ci-stage.yml @@ -25,15 +25,15 @@ 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 + - name: abstractionsArtifactName type: string default: Abstractions.Artifact + # The version to apply to the NuGet package and DLLs. + - name: abstractionsPackageVersion + type: string + # The type of build to produce (Release or Debug) - name: buildConfiguration type: string @@ -42,8 +42,13 @@ parameters: - Release - Debug + # True to enable extra debug steps and logging. + - name: debug + type: boolean + default: false + # The verbosity level for the dotnet CLI commands. - - name: verbosity + - name: dotnetVerbosity type: string default: normal values: @@ -65,55 +70,59 @@ stages: - template: /eng/pipelines/jobs/test-abstractions-package-ci-job.yml@self parameters: - jobNameSuffix: linux + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} displayNamePrefix: Linux + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + jobNameSuffix: linux + netFrameworkRuntimes: [] + netRuntimes: [net8.0, net9.0, net10.0] poolName: Azure Pipelines vmImage: ubuntu-latest - buildConfiguration: ${{ parameters.buildConfiguration }} - netRuntimes: [net8.0, net9.0] - netFrameworkRuntimes: [] - verbosity: ${{ parameters.verbosity }} # ------------------------------------------------------------------------ # Build and test on Windows - template: /eng/pipelines/jobs/test-abstractions-package-ci-job.yml@self parameters: - jobNameSuffix: windows + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} displayNamePrefix: Win + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + jobNameSuffix: windows + netFrameworkRuntimes: [net462] + netRuntimes: [net8.0, net9.0, net10.0] poolName: Azure Pipelines vmImage: windows-latest - buildConfiguration: ${{ parameters.buildConfiguration }} - netRuntimes: [net8.0, net9.0] - netFrameworkRuntimes: [net462] - verbosity: ${{ parameters.verbosity }} # ------------------------------------------------------------------------ # Build and test on macOS. - template: /eng/pipelines/jobs/test-abstractions-package-ci-job.yml@self parameters: - jobNameSuffix: macos + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} displayNamePrefix: macOS + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + jobNameSuffix: macos + netFrameworkRuntimes: [] + netRuntimes: [net8.0, net9.0, net10.0] 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: /eng/pipelines/jobs/pack-abstractions-package-ci-job.yml@self parameters: - artifactName: ${{ parameters.artifactName }} - buildConfiguration: ${{ parameters.buildConfiguration }} + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} - verbosity: ${{ parameters.verbosity }} + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} 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 + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} diff --git a/eng/pipelines/stages/build-azure-package-ci-stage.yml b/eng/pipelines/stages/build-azure-package-ci-stage.yml index ae5606806f..26b1d0d1eb 100644 --- a/eng/pipelines/stages/build-azure-package-ci-stage.yml +++ b/eng/pipelines/stages/build-azure-package-ci-stage.yml @@ -38,6 +38,19 @@ parameters: - name: abstractionsPackageVersion type: string + # The name of the pool to use for jobs that require customized VM images. + - name: adoPoolName + type: string + # This variable should be defined in AzureDevOps Library variable groups, + # for both the Public and ADO.Net projects. + # + # Any pool specified here must contain images with the following names: + # + # - ADO-UB22-SQL22 + # - ADO-CI-Win11 + # + default: $(ci_var_defaultPoolName) + # The name of the pipeline artifact to publish. - name: azureArtifactName type: string @@ -47,6 +60,12 @@ parameters: - name: azurePackageVersion type: string + # The name of the general Azure pool to use for jobs that don't require + # customized VM images. + - name: azurePoolName + type: string + default: Azure Pipelines + # The type of build to produce (Release or Debug) - name: buildConfiguration type: string @@ -55,10 +74,39 @@ parameters: - Release - Debug + # True to enable extra debug steps and logging. + - name: debug + type: boolean + default: false + # The stages we depend on, if any. - name: dependsOn type: object default: [] + + # The dotnet CLI verbosity to use. + - name: dotnetVerbosity + type: string + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic + + # The name of the MDS pipeline artifact to download. + # + # This is used when the referenceType is 'Package'. + - name: mdsArtifactName + type: string + default: MDS.Artifact + + # The MDS package verion to depend on. + # + # This is used when the referenceType is 'Package'. + - name: mdsPackageVersion + type: string # The reference type to use: # @@ -71,17 +119,6 @@ parameters: - 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 @@ -99,49 +136,98 @@ stages: abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} displayNamePrefix: Linux - jobNameSuffix: linux + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + jobNameSuffix: linux_basic + mdsArtifactName: MDS.Artifact + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} netFrameworkRuntimes: [] - netRuntimes: [net8.0, net9.0] - poolName: Azure Pipelines + netRuntimes: [net8.0, net9.0, net10.0] + poolName: ${{ parameters.azurePoolName }} referenceType: ${{ parameters.referenceType }} - verbosity: ${{ parameters.verbosity }} vmImage: ubuntu-latest + # Use our 1ES ADO pool for comprehensive testing. + - template: /eng/pipelines/jobs/test-azure-package-ci-job.yml@self + parameters: + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} + displayNamePrefix: Linux + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + jobNameSuffix: linux_comprehensive + mdsArtifactName: MDS.Artifact + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + netFrameworkRuntimes: [] + netRuntimes: [net8.0, net9.0, net10.0] + poolName: ${{ parameters.adoPoolName }} + referenceType: ${{ parameters.referenceType }} + vmImage: ADO-UB22-SQL22 + # ------------------------------------------------------------------------ # Build and test on Windows + # Use the Azure Pipelines pool for basic testing. - template: /eng/pipelines/jobs/test-azure-package-ci-job.yml@self parameters: abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} displayNamePrefix: Win - jobNameSuffix: windows + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + jobNameSuffix: windows_basic + mdsArtifactName: MDS.Artifact + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} netFrameworkRuntimes: [net462] - netRuntimes: [net8.0, net9.0] - poolName: Azure Pipelines + netRuntimes: [net8.0, net9.0, net10.0] + poolName: ${{ parameters.azurePoolName }} referenceType: ${{ parameters.referenceType }} - verbosity: ${{ parameters.verbosity }} vmImage: windows-latest + # Use our 1ES ADO pool for comprehensive testing. + - template: /eng/pipelines/jobs/test-azure-package-ci-job.yml@self + parameters: + abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} + displayNamePrefix: Win + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} + jobNameSuffix: windows_comprehensive + mdsArtifactName: MDS.Artifact + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} + netFrameworkRuntimes: [net462] + netRuntimes: [net8.0, net9.0, net10.0] + poolName: ${{ parameters.adoPoolName }} + referenceType: ${{ parameters.referenceType }} + vmImage: ADO-CI-Win11 + # ------------------------------------------------------------------------ # Build and test on macOS. + # Use the Azure Pipelines pool for basic testing. - template: /eng/pipelines/jobs/test-azure-package-ci-job.yml@self parameters: abstractionsArtifactName: ${{ parameters.abstractionsArtifactName }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} displayNamePrefix: macOS + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} jobNameSuffix: macos + mdsArtifactName: MDS.Artifact + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} netFrameworkRuntimes: [] - netRuntimes: [net8.0, net9.0] - poolName: Azure Pipelines + netRuntimes: [net8.0, net9.0, net10.0] + poolName: ${{ parameters.azurePoolName }} referenceType: ${{ parameters.referenceType }} - verbosity: ${{ parameters.verbosity }} vmImage: macos-latest + # We do not currently have any images in our 1ES ADO pools for macOS. + # ------------------------------------------------------------------------ # Create and publish the NuGet package. @@ -152,11 +238,14 @@ stages: azureArtifactName: ${{ parameters.azureArtifactName }} azurePackageVersion: ${{ parameters.abstractionsPackageVersion }} buildConfiguration: ${{ parameters.buildConfiguration }} + debug: ${{ parameters.debug }} 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_linux_basic + - test_azure_package_job_linux_comprehensive + - test_azure_package_job_windows_basic + - test_azure_package_job_windows_comprehensive - test_azure_package_job_macos + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} referenceType: ${{ parameters.referenceType }} - verbosity: ${{ parameters.verbosity }} diff --git a/eng/pipelines/stages/stress-tests-ci-stage.yml b/eng/pipelines/stages/stress-tests-ci-stage.yml index bc4bda6b56..e60f5a7a38 100644 --- a/eng/pipelines/stages/stress-tests-ci-stage.yml +++ b/eng/pipelines/stages/stress-tests-ci-stage.yml @@ -20,6 +20,14 @@ # depended on by downstream stages. parameters: + + # The Azure package version to stress test. This version must be available in + # one of the configured NuGet sources. + - name: azurePackageVersion + displayName: Azure Package Version + type: string + default: '' + # The type of build to produce (Debug or Release) - name: buildConfiguration displayName: Build Configuration @@ -36,12 +44,17 @@ parameters: type: object default: [] - # The name of the pipeline artifact to download that contains the MDS package - # to stress test. - - name: pipelineArtifactName - displayName: Pipeline Artifact Name + # The verbosity level for the dotnet CLI commands. + - name: dotnetVerbosity + displayName: dotnet CLI verbosity type: string - default: Artifacts + default: normal + values: + - quiet + - minimal + - normal + - detailed + - diagnostic # The MDS package version to stress test. This version must be available in # one of the configured NuGet sources. @@ -50,29 +63,24 @@ parameters: type: string default: '' - # The list of .NET runtimes to test against. - - name: netTestRuntimes - displayName: .NET Test Runtimes - type: object - default: [net8.0, net9.0] - # The list of .NET Framework runtimes to test against. - name: netFrameworkTestRuntimes displayName: .NET Framework Test Runtimes type: object default: [net462, net47, net471, net472, net48, net481] - # The verbosity level for the dotnet CLI commands. - - name: verbosity - displayName: Dotnet CLI verbosity + # The list of .NET runtimes to test against. + - name: netTestRuntimes + displayName: .NET Test Runtimes + type: object + default: [net8.0, net9.0] + + # The name of the pipeline artifact to download that contains the MDS package + # to stress test. + - name: pipelineArtifactName + displayName: Pipeline Artifact Name type: string - default: normal - values: - - quiet - - minimal - - normal - - detailed - - diagnostic + default: Artifacts stages: - stage: run_stress_tests_stage @@ -96,9 +104,10 @@ stages: # dotnet CLI arguments common to all commands. - name: commonArguments value: >- - --verbosity ${{parameters.verbosity}} + --verbosity ${{parameters.dotnetVerbosity}} --artifacts-path $(dotnetArtifactsDir) -p:MdsPackageVersion=${{parameters.mdsPackageVersion}} + -p:AzurePackageVersion=${{parameters.azurePackageVersion}} # dotnet CLI arguments for build/run commands. - name: buildArguments diff --git a/eng/pipelines/steps/compound-build-akv-step.yml b/eng/pipelines/steps/compound-build-akv-step.yml index c58d3febb9..2586f86aee 100644 --- a/eng/pipelines/steps/compound-build-akv-step.yml +++ b/eng/pipelines/steps/compound-build-akv-step.yml @@ -44,6 +44,12 @@ steps: packageType: 'runtime' 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: diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 381a2d9817..f38c666907 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -17,7 +17,7 @@ See the BUILDGUIDE.md for more details. --> - Project + Project $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) @@ -153,4 +153,43 @@ 14 + + + + false + true + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj index 9436e9067b..53652e0d8b 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj @@ -42,6 +42,12 @@ $(AbstractionsPackageVersion) $(Artifacts)/doc/$(TargetFramework)/$(AssemblyName).xml + + + $(DefineConstants);APPLY_MDS_ASSEMBLY_NAME_SUFFIX diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs index c14a758f76..309edf4998 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Reflection; +using System.Runtime.InteropServices; namespace Microsoft.Data.SqlClient; @@ -31,10 +32,23 @@ private static class Internal /// static Internal() { + // Choose the MDS assembly name based on compilation flags and the + // runtime environment. See the top-level Directory.Build.props for + // more information. + string assemblyName = "Microsoft.Data.SqlClient"; + #if (APPLY_MDS_ASSEMBLY_NAME_SUFFIX) + if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) + { + assemblyName += ".NetFx"; + } + else + { + assemblyName += ".NetCore"; + } + #endif + // 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. @@ -42,35 +56,21 @@ static Internal() if (assembly is null) { - // TODO: Logging - // SqlClientEventSource.Log.TryTraceEvent( - // nameof(SqlAuthenticationProviderManager) + - // $": Azure extension assembly={assemblyName} not found; " + - // "no default provider installed"); + Log($"MDS assembly={assemblyName} not found; " + + "Get/SetProvider() will not function"); 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"); - + Log($"MDS auth manager manager class={className} not found; " + + "Get/SetProvider() will not function"); return; } @@ -78,15 +78,22 @@ static Internal() _getProvider = manager.GetMethod( "GetProvider", BindingFlags.NonPublic | BindingFlags.Static); + + if (_getProvider is null) + { + Log($"MDS GetProvider() method not found; " + + "GetProvider() will not function"); + } + _setProvider = manager.GetMethod( "SetProvider", BindingFlags.NonPublic | BindingFlags.Static); - - // TODO: Logging - // SqlClientEventSource.Log.TryTraceEvent( - // nameof(SqlAuthenticationProviderManager) + - // $": Azure extension class={className} installed as " + - // "provider for all Active Directory authentication methods"); + + if (_setProvider is null) + { + Log($"MDS SetProvider() method not found; " + + "SetProvider() will not function"); + } } // All of these exceptions mean we couldn't find the get/set // methods. @@ -96,12 +103,8 @@ ex is BadImageFormatException || ex is FileLoadException || ex is FileNotFoundException) { - // TODO: Logging - // SqlClientEventSource.Log.TryTraceEvent( - // nameof(SqlAuthenticationProviderManager) + - // $": Azure extension assembly={assemblyName} not found or " + - // "not usable; no default provider installed; " + - // $"{ex.GetType().Name}: {ex.Message}"); + Log($"MDS assembly={assemblyName} not found or not usable; " + + $"Get/SetProvider() will not function: {ex} "); } // Any other exceptions are fatal. } @@ -136,6 +139,8 @@ ex is MethodAccessException || ex is NotSupportedException || ex is TargetInvocationException) { + Log($"GetProvider() invocation failed: " + + $"{ex.GetType().Name}: {ex.Message}"); return null; } } @@ -171,6 +176,8 @@ internal static bool SetProvider( if (!result.HasValue) { + Log($"SetProvider() invocation returned null; " + + "translating to false"); return false; } @@ -183,8 +190,17 @@ ex is MethodAccessException || ex is NotSupportedException || ex is TargetInvocationException) { + Log($"SetProvider() invocation failed: " + + $"{ex.GetType().Name}: {ex.Message}"); return false; } } + + private static void Log(string message) + { + // TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/39080): + // Convert to proper logging. + Console.WriteLine($"SqlAuthenticationProvider.Internal(): {message}"); + } } } 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 52247e6130..23e4ea451f 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj @@ -9,6 +9,14 @@ Microsoft.Data.SqlClient.Extensions.Abstractions.Test + + + $(DefineConstants);APPLY_MDS_ASSEMBLY_NAME_SUFFIX + + @@ -24,4 +32,11 @@ + + + PreserveNewest + xunit.runner.json + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs index 07e2b40078..4c837332e5 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs @@ -8,14 +8,23 @@ namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test; public class SqlAuthenticationProviderTest { + // Choose the MDS assembly name based on compilation flags. See the + // top-level Directory.Build.props for more information. + #if (APPLY_MDS_ASSEMBLY_NAME_SUFFIX && NET) + const string assemblyName = "Microsoft.Data.SqlClient.NetCore"; + #elif (APPLY_MDS_ASSEMBLY_NAME_SUFFIX && NETFRAMEWORK) + const string assemblyName = "Microsoft.Data.SqlClient.NetFx"; + #else + const string assemblyName = "Microsoft.Data.SqlClient"; + #endif + /// /// Construct to confirm preconditions. /// public SqlAuthenticationProviderTest() { // Confirm that the MDS assembly is indeed not present. - Assert.Throws( - () => Assembly.Load("Microsoft.Data.SqlClient")); + Assert.Throws(() => Assembly.Load(assemblyName)); } #region Tests diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs new file mode 100644 index 0000000000..b7f23c1217 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs @@ -0,0 +1,33 @@ +// 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.Test; + +// These tests were moved from MDS FunctionalTests AADAuthenticationTests.cs. +public class AADAuthenticationTests +{ + [Fact] + public void CustomActiveDirectoryProviderTest() + { + SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(static (result) => Task.CompletedTask); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); + Assert.Same(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + } + + [Fact] + public void CustomActiveDirectoryProviderTest_AppClientId() + { + SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(Guid.NewGuid().ToString()); + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); + Assert.Same(authProvider, SqlAuthenticationProvider.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.Same(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + } +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs new file mode 100644 index 0000000000..dda1afabef --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs @@ -0,0 +1,377 @@ +// 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.Text.RegularExpressions; + +namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; + +// These tests were migrated from MDS ManualTests AADConnectionTest.cs. +public class AADConnectionTest +{ + [ConditionalFact( + typeof(Config), + nameof(Config.OnAdoPool), + nameof(Config.HasUserManagedIdentityClientId))] + public static void KustoDatabaseTest() + { + // This is a sample Kusto database that can be connected by any AD account. + using SqlConnection connection = new SqlConnection($"Data Source=help.kusto.windows.net; Authentication=Active Directory Default;Trust Server Certificate=True;User ID = {Config.UserManagedIdentityClientId};"); + connection.Open(); + Assert.True(connection.State == System.Data.ConnectionState.Open); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.HasPasswordConnectionString))] + public static void AADPasswordWithWrongPassword() + { + string[] credKeys = { "Password", "PWD" }; + string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, credKeys) + "Password=TestPassword;"; + + Assert.Throws(() => ConnectAndDisconnect(connStr)); + + // We cannot verify error message with certainty as driver may cache token from other tests for current user + // and error message may change accordingly. + } + + [ConditionalFact( + typeof(Config), + nameof(Config.HasPasswordConnectionString))] + public static void TestADPasswordAuthentication() + { + // Connect to Azure DB with password and retrieve user name. + using (SqlConnection conn = new SqlConnection(Config.PasswordConnectionString)) + { + conn.Open(); + using (SqlCommand sqlCommand = new SqlCommand + ( + cmdText: $"SELECT SUSER_SNAME();", + connection: conn, + transaction: null + )) + { + string customerId = (string)sqlCommand.ExecuteScalar(); + string expected = RetrieveValueFromConnStr(Config.PasswordConnectionString, new string[] { "User ID", "UID" }); + Assert.Equal(expected, customerId); + } + } + } + + [ConditionalFact( + typeof(Config), + nameof(Config.HasPasswordConnectionString))] + public static void EmptyPasswordInConnStrAADPassword() + { + // connection fails with expected error message. + string[] pwdKey = { "Password", "PWD" }; + string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, pwdKey) + "Password=;"; + SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); + + string? user = FetchKeyInConnStr(Config.PasswordConnectionString, new string[] { "User Id", "UID" }); + string expectedMessage = string.Format("Failed to authenticate the user {0} in Active Directory (Authentication=ActiveDirectoryPassword).", user); + Assert.Contains(expectedMessage, e.Message); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.OnWindows), + nameof(Config.HasPasswordConnectionString))] + public static void EmptyCredInConnStrAADPassword() + { + // connection fails with expected error message. + string[] removeKeys = { "User ID", "Password", "UID", "PWD" }; + string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, removeKeys) + "User ID=; Password=;"; + SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); + + string expectedMessage = "Failed to authenticate the user in Active Directory (Authentication=ActiveDirectoryPassword)."; + Assert.Contains(expectedMessage, e.Message); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.OnUnix), + nameof(Config.HasPasswordConnectionString))] + public static void EmptyCredInConnStrAADPasswordAnyUnix() + { + // connection fails with expected error message. + string[] removeKeys = { "User ID", "Password", "UID", "PWD" }; + string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, removeKeys) + "User ID=; Password=;"; + SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); + + string expectedMessage = "MSAL cannot determine the username (UPN) of the currently logged in user.For Integrated Windows Authentication and Username/Password flows, please use .WithUsername() before calling ExecuteAsync()."; + Assert.Contains(expectedMessage, e.Message); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.HasPasswordConnectionString))] + public static void AADPasswordWithInvalidUser() + { + // connection fails with expected error message. + string[] removeKeys = { "User ID", "UID" }; + string user = "testdotnet@domain.com"; + string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, removeKeys) + $"User ID={user}"; + SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); + + string expectedMessage = string.Format("Failed to authenticate the user {0} in Active Directory (Authentication=ActiveDirectoryPassword).", user); + Assert.Contains(expectedMessage, e.Message); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.HasPasswordConnectionString))] + public static void NoCredentialsActiveDirectoryPassword() + { + // test Passes with correct connection string. + ConnectAndDisconnect(Config.PasswordConnectionString); + + // connection fails with expected error message. + string[] credKeys = { "User ID", "Password", "UID", "PWD" }; + string connStrWithNoCred = RemoveKeysInConnStr(Config.PasswordConnectionString, credKeys); + InvalidOperationException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); + + string expectedMessage = "Either Credential or both 'User ID' and 'Password' (or 'UID' and 'PWD') connection string keywords must be specified, if 'Authentication=Active Directory Password'."; + Assert.Contains(expectedMessage, e.Message); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.HasPasswordConnectionString), + nameof(Config.HasServicePrincipal))] + public static void NoCredentialsActiveDirectoryServicePrincipal() + { + // test Passes with correct connection string. + string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, removeKeys) + + $"Authentication=Active Directory Service Principal; User ID={Config.ServicePrincipalId}; PWD={Config.ServicePrincipalSecret};"; + ConnectAndDisconnect(connStr); + + // connection fails with expected error message. + string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStrWithNoCred = RemoveKeysInConnStr(Config.PasswordConnectionString, credKeys) + + "Authentication=Active Directory Service Principal;"; + InvalidOperationException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); + + string expectedMessage = "Either Credential or both 'User ID' and 'Password' (or 'UID' and 'PWD') connection string keywords must be specified, if 'Authentication=Active Directory Service Principal'."; + Assert.Contains(expectedMessage, e.Message); + } + + [ConditionalTheory( + typeof(Config), + nameof(Config.HasPasswordConnectionString), + nameof(Config.HasUserManagedIdentityClientId))] + [InlineData("2445343 2343253")] + [InlineData("2445343$#^@@%2343253")] + public static void ActiveDirectoryManagedIdentityWithInvalidUserIdMustFail(string userId) + { + // connection fails with expected error message. + string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStrWithNoCred = RemoveKeysInConnStr(Config.PasswordConnectionString, credKeys) + + $"Authentication=Active Directory Managed Identity; User Id={userId}"; + + SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); + + Regex expected = new( + @"(\[Managed Identity\]|ManagedIdentityCredential) Authentication unavailable", + RegexOptions.IgnoreCase); + + Assert.Matches(expected, e.GetBaseException().Message); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.OnAdoPool), + nameof(Config.HasPasswordConnectionString), + nameof(Config.HasUserManagedIdentityClientId))] + public static void ActiveDirectoryDefaultMustPass() + { + string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, credKeys) + + $"Authentication=ActiveDirectoryDefault;User ID={Config.UserManagedIdentityClientId};"; + + // Connection should be established using Managed Identity by default. + ConnectAndDisconnect(connStr); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.HasIntegratedSecurityConnectionString), + nameof(Config.HasTcpConnectionString))] + public static void ADIntegratedUsingSSPI() + { + // test Passes with correct connection string. + string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; + string connStr = RemoveKeysInConnStr(Config.TcpConnectionString, removeKeys) + + $"Authentication=Active Directory Integrated;"; + ConnectAndDisconnect(connStr); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.HasPasswordConnectionString), + nameof(Config.SupportsManagedIdentity), + nameof(Config.SupportsSystemAssignedManagedIdentity))] + public static void SystemAssigned_ManagedIdentityTest() + { + string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, removeKeys) + + $"Authentication=Active Directory Managed Identity;"; + ConnectAndDisconnect(connStr); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.OnAdoPool), + nameof(Config.HasPasswordConnectionString), + nameof(Config.HasUserManagedIdentityClientId))] + public static void UserAssigned_ManagedIdentityTest() + { + string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; + string connStr = RemoveKeysInConnStr(Config.PasswordConnectionString, removeKeys) + + $"Authentication=Active Directory Managed Identity; User Id={Config.UserManagedIdentityClientId};"; + ConnectAndDisconnect(connStr); + } + + [ConditionalFact( + typeof(Config), + nameof(Config.HasTcpConnectionString), + nameof(Config.SupportsManagedIdentity), + nameof(Config.SupportsSystemAssignedManagedIdentity), + nameof(Config.IsAzureSqlServer))] + public static void Azure_SystemManagedIdentityTest() + { + string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; + string connectionString = RemoveKeysInConnStr(Config.TcpConnectionString, removeKeys) + + $"Authentication=Active Directory Managed Identity;"; + + using (SqlConnection conn = new SqlConnection(connectionString)) + { + conn.Open(); + + Assert.True(conn.State == System.Data.ConnectionState.Open); + } + } + + [ConditionalFact( + typeof(Config), + nameof(Config.HasTcpConnectionString), + nameof(Config.HasUserManagedIdentityClientId), + nameof(Config.SupportsManagedIdentity), + nameof(Config.SupportsSystemAssignedManagedIdentity), + nameof(Config.IsAzureSqlServer))] + public static void Azure_UserManagedIdentityTest() + { + string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; + string connectionString = RemoveKeysInConnStr(Config.TcpConnectionString, removeKeys) + + $"Authentication=Active Directory Managed Identity; User Id={Config.UserManagedIdentityClientId}"; + + using (SqlConnection conn = new SqlConnection(connectionString)) + { + conn.Open(); + + Assert.True(conn.State == System.Data.ConnectionState.Open); + } + } + + #region Helpers from AADConnectionTest.cs + + private static void ConnectAndDisconnect( + string connectionString, SqlCredential? credential = null) + { + using SqlConnection conn = new(connectionString); + + if (credential is not null) + { + conn.Credential = credential; + } + + conn.Open(); + + Assert.True(conn.State == System.Data.ConnectionState.Open); + } + + #endregion + + #region Helpers from ManualTests DataTestUtility.cs + + public static string RemoveKeysInConnStr(string connStr, string[] keysToRemove) + { + // tokenize connection string and remove input keys. + string res = ""; + if (connStr != null && keysToRemove != null) + { + string[] keys = connStr.Split(';'); + foreach (var key in keys) + { + if (!string.IsNullOrEmpty(key.Trim())) + { + bool removeKey = false; + foreach (var keyToRemove in keysToRemove) + { + if (key.Trim().ToLower().StartsWith(keyToRemove.Trim().ToLower(), StringComparison.Ordinal)) + { + removeKey = true; + break; + } + } + if (!removeKey) + { + res += key + ";"; + } + } + } + } + return res; + } + + public static string? FetchKeyInConnStr(string connStr, string[] keys) + { + // tokenize connection string and find matching key + if (connStr != null && keys != null) + { + string[] connProps = connStr.Split(';'); + foreach (string cp in connProps) + { + if (!string.IsNullOrEmpty(cp.Trim())) + { + foreach (var key in keys) + { + if (cp.Trim().ToLower().StartsWith(key.Trim().ToLower(), StringComparison.Ordinal)) + { + return cp.Substring(cp.IndexOf('=') + 1); + } + } + } + } + } + return null; + } + + public static string RetrieveValueFromConnStr(string connStr, string[] keywords) + { + // tokenize connection string and retrieve value for a specific key. + string res = ""; + if (connStr != null && keywords != null) + { + string[] keys = connStr.Split(';'); + foreach (var key in keys) + { + foreach (var keyword in keywords) + { + if (!string.IsNullOrEmpty(key.Trim())) + { + if (key.Trim().ToLower().StartsWith(keyword.Trim().ToLower(), StringComparison.Ordinal)) + { + res = key.Substring(key.IndexOf('=') + 1).Trim(); + break; + } + } + } + } + } + return res; + } + + #endregion +} 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 a2397fe933..592c4c3c56 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj @@ -18,10 +18,50 @@ + + + + + PreserveNewest + xunit.runner.json + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs new file mode 100644 index 0000000000..1db51d1c87 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs @@ -0,0 +1,275 @@ +// 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.Runtime.InteropServices; +using System.Text.Json; + +using Microsoft.Data.SqlClient.TestUtilities; + +namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; + +/// +/// This class reads configuration information from environment variables and +/// the config.json file for use by our tests. +/// +/// Environment variables take precedence over config.json settings. Note that +/// variable names are case-sensitive on non-Windows platforms. +/// +/// The following variables are supported: +/// +/// ADO_POOL: +/// When defined, indicates that tests are running in an ADO-CI pool. +/// +/// SYSTEM_ACCESSTOKEN: +/// The Azure Pipelines $(System.AccessToken) to use for workload identity +/// federation. +/// +/// TEST_DEBUG_EMIT: +/// When defined, enables debug output of configuration values. +/// +/// TEST_MDS_CONFIG: +/// The path to the config file to use instead of the default. If not +/// supplied, the config file is assumed to be located next to the test +/// assembly and is named config.json. +/// +internal static class Config +{ + #region Config Properties + + internal static bool AdoPool { get; } = false; + internal static bool DebugEmit { get; } = false; + internal static bool IntegratedSecuritySupported { get; } = false; + internal static bool ManagedIdentitySupported { get; } = false; + internal static string PasswordConnectionString { get; } = string.Empty; + internal static string ServicePrincipalId { get; } = string.Empty; + internal static string ServicePrincipalSecret { get; } = string.Empty; + internal static string SystemAccessToken { get; } = string.Empty; + internal static bool SystemAssignedManagedIdentitySupported { get; } = false; + internal static string TcpConnectionString { get; } = string.Empty; + internal static string TenantId { get; } = string.Empty; + internal static bool UseManagedSniOnWindows { get; } = false; + internal static string UserManagedIdentityClientId { get; } = string.Empty; + internal static string WorkloadIdentityFederationServiceConnectionId { get; } = string.Empty; + + #endregion + + #region Conditional Fact/Theory Helpers + + internal static bool HasIntegratedSecurityConnectionString() => + !TcpConnectionString.Empty() && IntegratedSecuritySupported; + internal static bool HasPasswordConnectionString() => !PasswordConnectionString.Empty(); + internal static bool HasServicePrincipal() => !ServicePrincipalId.Empty() && !ServicePrincipalSecret.Empty(); + internal static bool HasSystemAccessToken() => !SystemAccessToken.Empty(); + internal static bool HasTcpConnectionString() => !TcpConnectionString.Empty(); + internal static bool HasTenantId() => !TenantId.Empty(); + internal static bool HasUserManagedIdentityClientId() => !UserManagedIdentityClientId.Empty(); + internal static bool HasWorkloadIdentityFederationServiceConnectionId() => !WorkloadIdentityFederationServiceConnectionId.Empty(); + + internal static bool IsAzureSqlServer() => + Utils.IsAzureSqlServer(new SqlConnectionStringBuilder(TcpConnectionString).DataSource); + + internal static bool OnAdoPool() => AdoPool; + internal static bool OnLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + internal static bool OnMacOS() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + internal static bool OnWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + internal static bool OnUnix() => OnLinux() || OnMacOS(); + + internal static bool SupportsIntegratedSecurity() => IntegratedSecuritySupported; + internal static bool SupportsManagedIdentity() => ManagedIdentitySupported; + internal static bool SupportsSystemAssignedManagedIdentity() => SystemAssignedManagedIdentitySupported; + + #endregion + + #region Static Construction + + /// + /// Static construction reads configuration settings from the config file + /// and environment variables. + /// + static Config() + { + // Read from the config.json file. If the TEST_MDS_CONFIG environment + // variable is set, use it. Otherwise, assume the config file is in the + // working directory and named config.json. + string configPath = GetEnvVar("TEST_MDS_CONFIG"); + if (configPath.Empty()) + { + configPath = "config.json"; + } + + try + { + using JsonDocument doc = + JsonDocument.Parse( + File.ReadAllText(configPath), + new JsonDocumentOptions + { + CommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true + }); + + JsonElement root = doc.RootElement; + // See the sample config file for information about these settings: + // + // src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json + // + // The sample file is copied to the build output directory as + // config.json by the TestUtilities project file. + // + IntegratedSecuritySupported = GetBool(root, "SupportsIntegratedSecurity"); + ManagedIdentitySupported = GetBool(root, "ManagedIdentitySupported"); + PasswordConnectionString = GetString(root, "AADPasswordConnectionString"); + ServicePrincipalId = GetString(root, "AADServicePrincipalId"); + ServicePrincipalSecret = GetString(root, "AADServicePrincipalSecret"); + SystemAssignedManagedIdentitySupported = + GetBool(root, "SupportsSystemAssignedManagedIdentity"); + TcpConnectionString = GetString(root, "TCPConnectionString"); + TenantId = GetString(root, "AzureKeyVaultTenantId"); + UseManagedSniOnWindows = GetBool(root, "UseManagedSNIOnWindows"); + UserManagedIdentityClientId = GetString(root, "UserManagedIdentityClientId"); + WorkloadIdentityFederationServiceConnectionId = + GetString(root, "WorkloadIdentityFederationServiceConnectionId"); + } + catch (Exception ex) + { + Console.WriteLine( + $"Config: Failed to read config file={configPath}: {ex}"); + throw; + } + + // Apply environment variable overrides. + // + // Note that environment variables are case-sensitive on non-Windows + // platforms. + AdoPool = GetEnvFlag("ADO_POOL"); + DebugEmit = GetEnvFlag("TEST_DEBUG_EMIT"); + SystemAccessToken = GetEnvVar("SYSTEM_ACCESSTOKEN"); + + // Emit debug information if requested. + if (DebugEmit) + { + Console.WriteLine("Config:"); + Console.WriteLine( + $" AdoPool: {AdoPool}"); + Console.WriteLine( + $" DebugEmit: {DebugEmit}"); + Console.WriteLine( + $" IntegratedSecuritySupported: {IntegratedSecuritySupported}"); + Console.WriteLine( + $" ManagedIdentitySupported: {ManagedIdentitySupported}"); + Console.WriteLine( + $" PasswordConnectionString: {PasswordConnectionString}"); + Console.WriteLine( + $" ServicePrincipalId: {ServicePrincipalId}"); + Console.WriteLine( + $" ServicePrincipalSecret: {ServicePrincipalSecret.Length}"); + Console.WriteLine( + $" SystemAccessToken: {SystemAccessToken}"); + Console.WriteLine( + $" SystemAssignedManagedIdentitySupported: {SystemAssignedManagedIdentitySupported}"); + Console.WriteLine( + $" TcpConnectionString: {TcpConnectionString}"); + Console.WriteLine( + $" TenantId: {TenantId}"); + Console.WriteLine( + $" UseManagedSniOnWindows: {UseManagedSniOnWindows}"); + Console.WriteLine( + $" UserManagedIdentityClientId: {UserManagedIdentityClientId}"); + Console.WriteLine( + " WorkloadIdentityFederationServiceConnectionId: " + + WorkloadIdentityFederationServiceConnectionId); + } + + // Apply the SNI flag, if necessary. This must occur before any MDS + // APIs are used. + if (UseManagedSniOnWindows) + { + AppContext.SetSwitch( + "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", + true); + } + } + + #endregion + + #region Private Methods + + /// + /// Get a string property from a JSON element. + /// + /// The JSON element to read from. + /// The name of the property to read. + /// The string value of the property, or an empty string if not found or invalid. + private static string GetString(JsonElement element, string name) + { + if (element.TryGetProperty(name, out var property)) + { + try + { + var value = property.GetString(); + if (value is not null) + { + return value; + } + } + catch (InvalidOperationException) + { + // Ignore invalid values. + } + } + + return string.Empty; + } + + /// + /// Get a boolean property from a JSON element. + /// + /// The JSON element to read from. + /// The name of the property to read. + /// The boolean value of the property, or false if not found or invalid. + private static bool GetBool(JsonElement element, string name) + { + if (element.TryGetProperty(name, out var property)) + { + try + { + return property.GetBoolean(); + } + catch (InvalidOperationException) + { + // Ignore invalid values. + } + } + + return false; + } + + /// + /// Get a boolean flag from an environment variable. The variable's value + /// is not examined; only its presence is checked. + /// + /// The name of the environment variable. + /// True if the environment variable is set; false otherwise. + private static bool GetEnvFlag(string name) + { + return Environment.GetEnvironmentVariable(name) is not null; + } + + /// + /// Get a string value from an environment variable. + /// + /// The name of the environment variable. + /// The value of the environment variable, or an empty string if not set. + private static string GetEnvVar(string name) + { + string? value = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(value)) + { + return string.Empty; + } + return value; + } + + #endregion +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs new file mode 100644 index 0000000000..50ae1c7c6e --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs @@ -0,0 +1,62 @@ +// 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.Test; + +public class DefaultAuthProviderTests +{ + // Verify that our auth provider has been installed for all AAD/Entra + // authentication methods, and not for any other methods. + // + // Note that this isn't testing anything in the Azure package. It actually + // tests the static constructor of the SqlAuthenticationProviderManager + // class in the MDS package and the static GetProvider() and SetProvider() + // methods of the SqlAuthenticationProvider class in the Abstractions + // package. We're testing this here because this test project uses both of + // those packages, and this is a convenient place to put such a test. + [Fact] + public void AuthProviderInstalled() + { + // Iterate over all authentication methods rather than specifying them + // via Theory data so that we detect any new methods that don't meet + // our expectations. + foreach (var method in + #if NET + Enum.GetValues() + #else + Enum.GetValues(typeof(SqlAuthenticationMethod)).Cast() + #endif + ) + { + SqlAuthenticationProvider? provider = + SqlAuthenticationProvider.GetProvider(method); + + switch (method) + { + #pragma warning disable 0618 // Type or member is obsolete + case SqlAuthenticationMethod.ActiveDirectoryPassword: + #pragma warning restore 0618 // Type or member is obsolete + case SqlAuthenticationMethod.ActiveDirectoryIntegrated: + case SqlAuthenticationMethod.ActiveDirectoryInteractive: + case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal: + case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: + case SqlAuthenticationMethod.ActiveDirectoryManagedIdentity: + case SqlAuthenticationMethod.ActiveDirectoryMSI: + case SqlAuthenticationMethod.ActiveDirectoryDefault: + case SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity: + Assert.NotNull(provider); + Assert.IsType(provider); + break; + + default: + // There is either no provider installed, or it is not ours. + if (provider is not null) + { + Assert.IsNotType(provider); + } + break; + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/StringExtensions.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/StringExtensions.cs new file mode 100644 index 0000000000..acc7f10310 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/StringExtensions.cs @@ -0,0 +1,13 @@ +// 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. + +// Adds the missing Empty() method to string that doesn't waste time on null +// checks like String.IsNullOrEmpty() does, and has a nice short name. +internal static class StringExtensions +{ + internal static bool Empty(this string str) + { + return str.Length == 0; + } +} diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/WorkloadIdentityFederationTests.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/WorkloadIdentityFederationTests.cs new file mode 100644 index 0000000000..053e543ee1 --- /dev/null +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/WorkloadIdentityFederationTests.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Azure.Identity; + +namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; + +// Verify that we're running in an environment that supports Azure Pipelines +// Workload Identity Federation authentication. +public class WorkloadIdentityFederationTests +{ + [ConditionalFact( + typeof(Config), + nameof(Config.HasSystemAccessToken), + nameof(Config.HasTenantId), + nameof(Config.HasUserManagedIdentityClientId), + nameof(Config.HasWorkloadIdentityFederationServiceConnectionId))] + public async void GetCredential() + { + AzurePipelinesCredential credential = new( + // The tenant ID of the managed identity associated to our workload + // identity federation service connection. See: + // + // https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/654fffd0-d02d-4894-b1b7-e2dfbc44a665/resourceGroups/aad-testlab-dl797892652000/providers/Microsoft.ManagedIdentity/userAssignedIdentities/dotnetMSI/properties + // + // Note that we need a service connection configured in each Azure DevOps project + // (Public and ADO.Net) that uses this tenant ID. + // + Config.TenantId, + + // The client ID of the managed identity associated to our workload + // identity federation service connection. See: + // + // https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/654fffd0-d02d-4894-b1b7-e2dfbc44a665/resourceGroups/aad-testlab-dl797892652000/providers/Microsoft.ManagedIdentity/userAssignedIdentities/dotnetMSI/overview + // + Config.UserManagedIdentityClientId, + + // The Azure Dev Ops service connection ID (resourceId found in the + // URL) of our workload identity federation setup. + // + // Note that we need a service connection configured in each Azure + // DevOps project (Public and ADO.Net). + // + // Public project: + // + // https://sqlclientdrivers.visualstudio.com/public/_settings/adminservices?resourceId=ec9623b2-829c-497f-ae1f-7461766f9a9c + // + // ADO.Net project: + // + // https://sqlclientdrivers.visualstudio.com/ADO.Net/_settings/adminservices?resourceId=c29947a8-df6a-4ceb-b2d4-1676c57c37b9 + // + Config.WorkloadIdentityFederationServiceConnectionId, + + // The system access token provided by Azure Pipelines. + Config.SystemAccessToken); + + // Acquire a token suitable for accessing Azure SQL databases. + var token = await credential.GetTokenAsync( + new(["https://database.windows.net/.default"]), + CancellationToken.None); + + Assert.NotEmpty(token.Token); + } +} 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 aaccec8c11..d7821b8a35 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -1,14 +1,22 @@  - false + + Microsoft.Data.SqlClient + $(AssemblyName).NetCore + net8.0;net9.0;netstandard2.0 + + false $(ObjFolder)$(Configuration)\$(AssemblyName)\ref\ netcoreapp $(BinFolder)$(Configuration)\$(AssemblyName)\ref\ $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName)\netstandard\ - $(OutputPath)\$(TargetFramework)\Microsoft.Data.SqlClient.xml + $(OutputPath)\$(TargetFramework)\$(AssemblyName).xml Core $(BaseProduct) Debug;Release; AnyCPU;x64;x86 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 8432a6427c..70cb79a8b0 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -1,9 +1,16 @@  + Microsoft.Data.SqlClient + $(AssemblyName).NetCore + net8.0;net9.0 - Microsoft.Data.SqlClient is not supported on this platform. + $(OS) + Microsoft.Data.SqlClient is not supported on this platform. false netcoreapp @@ -11,7 +18,7 @@ AnyCPU;x64;x86 $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName)\netcore\ $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName)\netcore\ - $(OutputPath)\$(TargetFramework)\Microsoft.Data.SqlClient.xml + $(OutputPath)\$(TargetFramework)\$(AssemblyName).xml true Core $(BaseProduct) true 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 29fbddc3b6..4cdfa44f73 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -1,10 +1,18 @@  - false + + Microsoft.Data.SqlClient + $(AssemblyName).NetFx + net462 + + false $(ObjFolder)$(Configuration)\$(AssemblyName)\ref\ $(BinFolder)$(Configuration)\$(AssemblyName)\ref\ - $(OutputPath)\Microsoft.Data.SqlClient.xml + $(OutputPath)\$(AssemblyName).xml Framework $(BaseProduct) Debug;Release 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 61c0d4274a..e3e1309607 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -1,16 +1,23 @@  + Microsoft.Data.SqlClient + $(AssemblyName).NetFx + net462 - $(OS) + {407890AC-9876-4FEF-A6F1-F36A876BAADE} + $(OS) true AnyCPU $(BinFolder)$(Configuration).$(OutputPlatform)\ $(ObjFolder)$(Configuration).$(OutputPlatform)\ $(BinPath)$(AssemblyName)\netfx\ - $(OutputPath)\Microsoft.Data.SqlClient.xml + $(OutputPath)\$(AssemblyName).xml $(ObjPath)$(AssemblyName)\netfx\ Framework $(BaseProduct) false @@ -898,7 +905,7 @@ Microsoft\Data\SqlClient\SqlUtil.cs - Microsoft\Data\SqlClient\SSPI\NativeSspiContextProvider.windows..cs + Microsoft\Data\SqlClient\SSPI\NativeSspiContextProvider.windows.cs Microsoft\Data\SqlClient\SSPI\NegotiateSspiContextProvider.cs diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj index ec728d6a98..1e827794ed 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj +++ b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj @@ -40,10 +40,10 @@ - + PreserveNewest xunit.runner.json - + @@ -51,12 +51,11 @@ - - PreserveNewest - %(Filename)%(Extension) - + + + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs index 32050a9716..d86b6684d1 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs @@ -68,34 +68,10 @@ public async Task IsDummySqlAuthenticationProviderSetByDefault() var provider = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive); Assert.NotNull(provider); - Assert.Equal(typeof(DummySqlAuthenticationProvider), provider.GetType()); + Assert.IsType(provider); var token = await provider.AcquireTokenAsync(null); Assert.Equal(token.AccessToken, DummySqlAuthenticationProvider.DUMMY_TOKEN_STR); } - - [Fact] - public void CustomActiveDirectoryProviderTest() - { - SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(static (result) => Task.CompletedTask); - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); - Assert.Same(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); - } - - [Fact] - public void CustomActiveDirectoryProviderTest_AppClientId() - { - SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(Guid.NewGuid().ToString()); - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); - Assert.Same(authProvider, SqlAuthenticationProvider.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.Same(authProvider, SqlAuthenticationProvider.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 7c54891ff4..bda156ce47 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 @@ -30,7 +30,7 @@ - + @@ -144,27 +144,22 @@ - - + + + - - PreserveNewest - %(Filename)%(Extension) - - - - + PreserveNewest xunit.runner.json - + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs new file mode 100644 index 0000000000..ecc9d8fa0f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs @@ -0,0 +1,35 @@ +// 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 Microsoft.Data.SqlClient.FunctionalTests.DataCommon; +using Xunit; + +namespace Microsoft.Data.SqlClient.Tests +{ + public class SqlAuthenticationProviderManagerTests + { + // The FunctionalTests project employs a .NET Framework app.config file + // that configures a dummy authentication provider for + // ActiveDirectoryInteractive authentication. Verify that this is + // respected. + [ConditionalFact(typeof(TestUtility), nameof(TestUtility.IsNetFramework))] + public void DefaultAuthenticationProviders_AppConfig() + { + // The provider for ActiveDirectoryInteractive should be our dummy + // provider. + Assert.IsType( + SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive)); + + // There should be no provider for other methods. Spot-check a few. + Assert.Null(SqlAuthenticationProvider.GetProvider( + #pragma warning disable CS0618 // Type or member is obsolete + SqlAuthenticationMethod.ActiveDirectoryPassword)); + #pragma warning restore CS0618 // Type or member is obsolete + + Assert.Null(SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryManagedIdentity)); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs deleted file mode 100644 index c0315ce2f0..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs +++ /dev/null @@ -1,36 +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 Microsoft.Data.SqlClient.FunctionalTests.DataCommon; -using Xunit; - -namespace Microsoft.Data.SqlClient.Tests -{ - public class SqlAuthenticationProviderTest - { - [Theory] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryIntegrated)] - #pragma warning disable 0618 // Type or member is obsolete - [InlineData(SqlAuthenticationMethod.ActiveDirectoryPassword)] - #pragma warning restore 0618 // Type or member is obsolete - [InlineData(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal)] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity)] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryMSI)] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryDefault)] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity)] - public void DefaultAuthenticationProviders(SqlAuthenticationMethod method) - { - Assert.IsType(SqlAuthenticationProvider.GetProvider(method)); - } - - // Overridden by app.config in this project - [ConditionalTheory(typeof(TestUtility), nameof(TestUtility.IsNetFramework))] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryInteractive)] - public void DefaultAuthenticationProviders_Interactive(SqlAuthenticationMethod method) - { - Assert.IsType(SqlAuthenticationProvider.GetProvider(method)); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 0bb9654efc..a62bb9787b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -239,6 +239,15 @@ static DataTestUtility() AEConnStringsSetup.Add(TCPConnectionString); } } + + // Many of our tests require a Managed Identity provider to be + // registered. + // + // TODO: Figure out which ones and install on-demand rather than + // globally. + SqlAuthenticationProvider.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, + new ManagedIdentityProvider()); } public static IEnumerable ConnectionStrings => GetConnectionStrings(withEnclave: true); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ManagedIdentityProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ManagedIdentityProvider.cs new file mode 100644 index 0000000000..0e590be0f9 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ManagedIdentityProvider.cs @@ -0,0 +1,97 @@ +// 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.Threading; +using System.Threading.Tasks; + +using Azure.Core; +using Azure.Identity; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests; + +internal class ManagedIdentityProvider : SqlAuthenticationProvider +{ + // Our cache of managed identity user Ids to credential instances. + private readonly ConcurrentDictionary + _credentialCache = new(); + + // The default suffix to apply to resource scopes. + private const string s_defaultScopeSuffix = "/.default"; + + // Acquire a token using Managed Identity. The UserId in the parameters is + // used as the managed identity client ID. Tokens are cached per UserId. + // + // GOTCHA: This assumes that the Resource and Authority in the parameters + // never change for a given UserId, which is probably a safe assumption for + // tests. + // + public override async Task AcquireTokenAsync( + SqlAuthenticationParameters parameters) + { + if (parameters.UserId is null) + { + throw new TokenException( + "Refusing to acquire token for ManagedIdentity with null UserId"); + } + + try + { + // Build an appropriate scope. + string scope = parameters.Resource.EndsWith( + s_defaultScopeSuffix, StringComparison.Ordinal) + ? parameters.Resource + : parameters.Resource + s_defaultScopeSuffix; + + TokenRequestContext context = new([scope]); + + TokenCredentialOptions options = new() + { + AuthorityHost = new Uri(parameters.Authority) + }; + + // Create or re-use the ManagedIdentityCredential for this UserId. + ManagedIdentityCredential credential = + _credentialCache.GetOrAdd( + parameters.UserId, + (_) => new(parameters.UserId, options)); + + // Set up a cancellation token based on the authentication timeout, + // ignoring overflow since this is just test code. + using CancellationTokenSource cancellor = new(); + cancellor.CancelAfter(parameters.ConnectionTimeout * 1000); + + // Acquire the token, which may be cached by the credential. + AccessToken token = + await credential.GetTokenAsync(context, cancellor.Token) + .ConfigureAwait(false); + + return new(token.Token, token.ExpiresOn); + } + catch (Exception ex) + { + throw new TokenException( + $"Failed to acquire token for ManagedIdentity " + + $"userId ={parameters.UserId} error={ex.Message}", ex); + } + } + + /// We support only the Managed Identity authentication method. + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) + { + return authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity; + } + + // The exception we throw on any errors acquiring tokens. + private sealed class TokenException : SqlAuthenticationProviderException + { + internal TokenException(string message, Exception? causedBy = null) + : base(message, causedBy) + { + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/UsernamePasswordProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/UsernamePasswordProvider.cs new file mode 100644 index 0000000000..5f9ca1eff0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/UsernamePasswordProvider.cs @@ -0,0 +1,72 @@ +// 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.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Identity.Client; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests; + +internal class UsernamePasswordProvider : SqlAuthenticationProvider +{ + readonly string _appClientId; + const string s_defaultScopeSuffix = "/.default"; + + internal UsernamePasswordProvider(string appClientId) + { + _appClientId = appClientId; + } + + public override async Task AcquireTokenAsync(SqlAuthenticationParameters parameters) + { + try + { + string scope = + parameters.Resource.EndsWith(s_defaultScopeSuffix, StringComparison.Ordinal) + ? parameters.Resource + : parameters.Resource + s_defaultScopeSuffix; + + using CancellationTokenSource cts = new(); + cts.CancelAfter(parameters.ConnectionTimeout * 1000); + + AuthenticationResult result = + #pragma warning disable CS0618 // Type or member is obsolete + await PublicClientApplicationBuilder.Create(_appClientId) + .WithAuthority(parameters.Authority) + .Build() + .AcquireTokenByUsernamePassword([scope], parameters.UserId, parameters.Password) + #pragma warning restore CS0618 // Type or member is obsolete + .WithCorrelationId(parameters.ConnectionId) + .ExecuteAsync(cancellationToken: cts.Token); + + return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); + } + catch (Exception ex) + { + throw new TokenException( + $"Failed to acquire token for username/password " + + $"userId ={parameters.UserId} error={ex.Message}", ex); + } + } + + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) + { + #pragma warning disable 0618 // Type or member is obsolete + return authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryPassword); + #pragma warning restore 0618 // Type or member is obsolete + } + + // The exception we throw on any errors acquiring tokens. + private sealed class TokenException : SqlAuthenticationProviderException + { + internal TokenException(string message, Exception? causedBy = null) + : base(message, causedBy) + { + } + } +} 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 a459a760c7..945270d65a 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 @@ -272,9 +272,11 @@ + + @@ -330,14 +332,12 @@ - - @@ -370,11 +370,11 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - PreserveNewest - %(Filename)%(Extension) - + + + + PreserveNewest @@ -388,9 +388,9 @@ Always - + PreserveNewest xunit.runner.json - + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs index 54900a4dd0..e99192732d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs @@ -22,10 +22,19 @@ public AADFedAuthTokenRefreshTest(ITestOutputHelper testOutputHelper) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAADPasswordConnStrSetup))] public void FedAuthTokenRefreshTest() { - string connectionString = DataTestUtility.AADPasswordConnectionString; + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider original = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword); + #pragma warning restore 0618 // Type or member is obsolete - using (SqlConnection connection = new SqlConnection(connectionString)) + try { + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new UsernamePasswordProvider(DataTestUtility.ApplicationClientId)); + #pragma warning restore 0618 // Type or member is obsolete + + string connectionString = DataTestUtility.AADPasswordConnectionString; + + using SqlConnection connection = new SqlConnection(connectionString); connection.Open(); string oldTokenHash = ""; @@ -65,6 +74,16 @@ public void FedAuthTokenRefreshTest() Assert.True(newLocalExpiryTime > oldLocalExpiryTime, "The refreshed token must have a new or later expiry time."); } } + finally + { + if (original is not null) + { + // Reset to driver internal provider. + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, original); + #pragma warning restore 0618 // Type or member is obsolete + } + } } [Conditional("DEBUG")] 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 2c46c2598d..f2c0128c46 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -5,59 +5,14 @@ using System; using System.Diagnostics; using System.Security; -using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Microsoft.Identity.Client; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { - public class AADConnectionsTest + public class AADConnectionTest { - class CustomSqlAuthenticationProvider : SqlAuthenticationProvider - { - string _appClientId; - - internal CustomSqlAuthenticationProvider(string appClientId) - { - _appClientId = appClientId; - } - - public override async Task AcquireTokenAsync(SqlAuthenticationParameters parameters) - { - string s_defaultScopeSuffix = "/.default"; - string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix, StringComparison.Ordinal) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; - - _ = parameters.ServerName; - _ = parameters.DatabaseName; - _ = parameters.ConnectionId; - - var cts = new CancellationTokenSource(); - cts.CancelAfter(parameters.ConnectionTimeout * 1000); - - string[] scopes = new string[] { scope }; - SecureString password = new SecureString(); - -#pragma warning disable CS0618 // Type or member is obsolete - AuthenticationResult result = await PublicClientApplicationBuilder.Create(_appClientId) - .WithAuthority(parameters.Authority) - .Build().AcquireTokenByUsernamePassword(scopes, parameters.UserId, parameters.Password) - .WithCorrelationId(parameters.ConnectionId) - .ExecuteAsync(cancellationToken: cts.Token); -#pragma warning restore CS0618 // Type or member is obsolete - - return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); - } - - public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) - { - #pragma warning disable 0618 // Type or member is obsolete - return authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryPassword); - #pragma warning restore 0618 // Type or member is obsolete - } - } - private static void ConnectAndDisconnect(string connectionString, SqlCredential credential = null) { using (SqlConnection conn = new SqlConnection(connectionString)) @@ -79,15 +34,6 @@ private static void ConnectAndDisconnect(string connectionString, SqlCredential private static bool IsManagedIdentitySetup() => DataTestUtility.ManagedIdentitySupported; private static bool SupportsSystemAssignedManagedIdentity() => DataTestUtility.SupportsSystemAssignedManagedIdentity; - [PlatformSpecific(TestPlatforms.Windows)] - [ConditionalFact(nameof(IsAccessTokenSetup), nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] - public static void KustoDatabaseTest() - { - // This is a sample Kusto database that can be connected by any AD account. - using SqlConnection connection = new SqlConnection($"Data Source=help.kusto.windows.net; Authentication=Active Directory Default;Trust Server Certificate=True;User ID = {DataTestUtility.UserManagedIdentityClientId};"); - connection.Open(); - Assert.True(connection.State == System.Data.ConnectionState.Open); - } [ConditionalFact(nameof(IsAccessTokenSetup), nameof(IsAADConnStringsSetup))] public static void AccessTokenTest() @@ -213,47 +159,33 @@ public static void AADPasswordWithIntegratedSecurityTrue() Assert.Contains(expectedMessage, e.Message); } - [ConditionalFact(nameof(IsAccessTokenSetup), nameof(IsAADConnStringsSetup))] - public static void AADPasswordWithWrongPassword() - { - string[] credKeys = { "Password", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + "Password=TestPassword;"; - - Assert.Throws(() => ConnectAndDisconnect(connStr)); - - // We cannot verify error message with certainity as driver may cache token from other tests for current user - // and error message may change accordingly. - } - [ConditionalFact(nameof(IsAADConnStringsSetup))] public static void GetAccessTokenByPasswordTest() { - // Clear token cache for code coverage. - ActiveDirectoryAuthenticationProvider.ClearUserTokenCache(); - using (SqlConnection connection = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider original = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword); + #pragma warning restore 0618 // Type or member is obsolete + + try { - connection.Open(); - Assert.True(connection.State == System.Data.ConnectionState.Open); - } - } + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new UsernamePasswordProvider(DataTestUtility.ApplicationClientId)); + #pragma warning restore 0618 // Type or member is obsolete - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void TestADPasswordAuthentication() - { - // Connect to Azure DB with password and retrieve user name. - using (SqlConnection conn = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) + using (SqlConnection connection = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) + { + connection.Open(); + Assert.True(connection.State == System.Data.ConnectionState.Open); + } + } + finally { - conn.Open(); - using (SqlCommand sqlCommand = new SqlCommand - ( - cmdText: $"SELECT SUSER_SNAME();", - connection: conn, - transaction: null - )) + if (original is not null) { - string customerId = (string)sqlCommand.ExecuteScalar(); - string expected = DataTestUtility.RetrieveValueFromConnStr(DataTestUtility.AADPasswordConnectionString, new string[] { "User ID", "UID" }); - Assert.Equal(expected, customerId); + // Reset to driver internal provider. + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, original); + #pragma warning restore 0618 // Type or member is obsolete } } } @@ -262,28 +194,41 @@ 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)); + SqlAuthenticationProvider original = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword); #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)) + + try { - conn.Open(); - using (SqlCommand sqlCommand = new SqlCommand - ( - cmdText: $"SELECT SUSER_SNAME();", - connection: conn, - transaction: null - )) + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new UsernamePasswordProvider(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)) { - string customerId = (string)sqlCommand.ExecuteScalar(); - string expected = DataTestUtility.RetrieveValueFromConnStr(DataTestUtility.AADPasswordConnectionString, new string[] { "User ID", "UID" }); - Assert.Equal(expected, customerId); + conn.Open(); + using (SqlCommand sqlCommand = new SqlCommand + ( + cmdText: $"SELECT SUSER_SNAME();", + connection: conn, + transaction: null + )) + { + string customerId = (string)sqlCommand.ExecuteScalar(); + string expected = DataTestUtility.RetrieveValueFromConnStr(DataTestUtility.AADPasswordConnectionString, new string[] { "User ID", "UID" }); + Assert.Equal(expected, customerId); + } + } + } + finally + { + if (original is not null) + { + // Reset to driver internal provider. + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, original); + #pragma warning restore 0618 // Type or member is obsolete } } - // Reset to driver internal provider. - #pragma warning disable 0618 // Type or member is obsolete - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new ActiveDirectoryAuthenticationProvider(DataTestUtility.ApplicationClientId)); - #pragma warning restore 0618 // Type or member is obsolete } [ConditionalFact(nameof(IsAADConnStringsSetup))] @@ -320,92 +265,6 @@ public static void MFAAuthWithPassword() Assert.Contains(expectedMessage, e.Message); } - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void EmptyPasswordInConnStrAADPassword() - { - // connection fails with expected error message. - string[] pwdKey = { "Password", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, pwdKey) + "Password=;"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string user = DataTestUtility.FetchKeyInConnStr(DataTestUtility.AADPasswordConnectionString, new string[] { "User Id", "UID" }); - string expectedMessage = string.Format("Failed to authenticate the user {0} in Active Directory (Authentication=ActiveDirectoryPassword).", user); - Assert.Contains(expectedMessage, e.Message); - } - - [PlatformSpecific(TestPlatforms.Windows)] - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void EmptyCredInConnStrAADPassword() - { - // connection fails with expected error message. - string[] removeKeys = { "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + "User ID=; Password=;"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string expectedMessage = "Failed to authenticate the user in Active Directory (Authentication=ActiveDirectoryPassword)."; - Assert.Contains(expectedMessage, e.Message); - } - - [PlatformSpecific(TestPlatforms.AnyUnix)] - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void EmptyCredInConnStrAADPasswordAnyUnix() - { - // connection fails with expected error message. - string[] removeKeys = { "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + "User ID=; Password=;"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string expectedMessage = "MSAL cannot determine the username (UPN) of the currently logged in user.For Integrated Windows Authentication and Username/Password flows, please use .WithUsername() before calling ExecuteAsync()."; - Assert.Contains(expectedMessage, e.Message); - } - - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void AADPasswordWithInvalidUser() - { - // connection fails with expected error message. - string[] removeKeys = { "User ID", "UID" }; - string user = "testdotnet@domain.com"; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + $"User ID={user}"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string expectedMessage = string.Format("Failed to authenticate the user {0} in Active Directory (Authentication=ActiveDirectoryPassword).", user); - Assert.Contains(expectedMessage, e.Message); - } - - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void NoCredentialsActiveDirectoryPassword() - { - // test Passes with correct connection string. - ConnectAndDisconnect(DataTestUtility.AADPasswordConnectionString); - - // connection fails with expected error message. - string[] credKeys = { "User ID", "Password", "UID", "PWD" }; - string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys); - InvalidOperationException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); - - string expectedMessage = "Either Credential or both 'User ID' and 'Password' (or 'UID' and 'PWD') connection string keywords must be specified, if 'Authentication=Active Directory Password'."; - Assert.Contains(expectedMessage, e.Message); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAADServicePrincipalSetup))] - public static void NoCredentialsActiveDirectoryServicePrincipal() - { - // test Passes with correct connection string. - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + - $"Authentication=Active Directory Service Principal; User ID={DataTestUtility.AADServicePrincipalId}; PWD={DataTestUtility.AADServicePrincipalSecret};"; - ConnectAndDisconnect(connStr); - - // connection fails with expected error message. - string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + - "Authentication=Active Directory Service Principal;"; - InvalidOperationException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); - - string expectedMessage = "Either Credential or both 'User ID' and 'Password' (or 'UID' and 'PWD') connection string keywords must be specified, if 'Authentication=Active Directory Service Principal'."; - Assert.Contains(expectedMessage, e.Message); - } - [ConditionalFact(nameof(IsAADConnStringsSetup))] public static void ActiveDirectoryDeviceCodeFlowWithUserIdMustFail() { @@ -496,22 +355,6 @@ public static void ActiveDirectoryManagedIdentityWithPasswordMustFail() Assert.Contains(expectedMessage, e.Message); } - [InlineData("2445343 2343253")] - [InlineData("2445343$#^@@%2343253")] - [ConditionalTheory(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] - public static void ActiveDirectoryManagedIdentityWithInvalidUserIdMustFail(string userId) - { - // connection fails with expected error message. - string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + - $"Authentication=Active Directory Managed Identity; User Id={userId}"; - - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); - - string expectedMessage = "[Managed Identity] Authentication unavailable"; - Assert.Contains(expectedMessage, e.GetBaseException().Message, StringComparison.OrdinalIgnoreCase); - } - [ConditionalFact(nameof(IsAADConnStringsSetup))] public static void ActiveDirectoryMSIWithCredentialsMustFail() { @@ -653,85 +496,66 @@ public static void AccessTokenCallbackReceivesUsernameAndPassword() } } - [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] - public static void ActiveDirectoryDefaultMustPass() - { - string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + - $"Authentication=ActiveDirectoryDefault;User ID={DataTestUtility.UserManagedIdentityClientId};"; - - // Connection should be established using Managed Identity by default. - ConnectAndDisconnect(connStr); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsIntegratedSecuritySetup), nameof(DataTestUtility.AreConnStringsSetup))] - public static void ADIntegratedUsingSSPI() - { - // test Passes with correct connection string. - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.TCPConnectionString, removeKeys) + - $"Authentication=Active Directory Integrated;"; - ConnectAndDisconnect(connStr); - } - // Test passes locally everytime, but in pieplines fails randomly with uncertainity. // e.g. Second AAD connection too slow (802ms)! (More than 30% of the first (576ms).) [ActiveIssue("16058")] [ConditionalFact(nameof(IsAADConnStringsSetup))] public static void ConnectionSpeed() { - var connString = DataTestUtility.AADPasswordConnectionString; + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider original = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword); + #pragma warning restore 0618 // Type or member is obsolete - //Ensure server endpoints are warm - using (var connectionDrill = new SqlConnection(connString)) + try { - connectionDrill.Open(); - } + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new UsernamePasswordProvider(DataTestUtility.ApplicationClientId)); + #pragma warning restore 0618 // Type or member is obsolete - SqlConnection.ClearAllPools(); - ActiveDirectoryAuthenticationProvider.ClearUserTokenCache(); + var connString = DataTestUtility.AADPasswordConnectionString; - Stopwatch firstConnectionTime = new Stopwatch(); - Stopwatch secondConnectionTime = new Stopwatch(); + //Ensure server endpoints are warm + using (var connectionDrill = new SqlConnection(connString)) + { + connectionDrill.Open(); + } + + SqlConnection.ClearAllPools(); - using (var connectionDrill = new SqlConnection(connString)) + Stopwatch firstConnectionTime = new Stopwatch(); + Stopwatch secondConnectionTime = new Stopwatch(); + + using (var connectionDrill = new SqlConnection(connString)) + { + firstConnectionTime.Start(); + connectionDrill.Open(); + firstConnectionTime.Stop(); + using (var connectionDrill2 = new SqlConnection(connString)) + { + secondConnectionTime.Start(); + connectionDrill2.Open(); + secondConnectionTime.Stop(); + } + } + + // Subsequent AAD connections within a short timeframe should use an auth token cached from the connection pool + // Second connection speed in tests was typically 10-15% of the first connection time. Using 30% since speeds may vary. + Assert.True(((double)secondConnectionTime.ElapsedMilliseconds / firstConnectionTime.ElapsedMilliseconds) < 0.30, $"Second AAD connection too slow ({secondConnectionTime.ElapsedMilliseconds}ms)! (More than 30% of the first ({firstConnectionTime.ElapsedMilliseconds}ms).)"); + } + finally { - firstConnectionTime.Start(); - connectionDrill.Open(); - firstConnectionTime.Stop(); - using (var connectionDrill2 = new SqlConnection(connString)) + if (original is not null) { - secondConnectionTime.Start(); - connectionDrill2.Open(); - secondConnectionTime.Stop(); + // Reset to driver internal provider. + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, original); + #pragma warning restore 0618 // Type or member is obsolete } } - - // Subsequent AAD connections within a short timeframe should use an auth token cached from the connection pool - // Second connection speed in tests was typically 10-15% of the first connection time. Using 30% since speeds may vary. - Assert.True(((double)secondConnectionTime.ElapsedMilliseconds / firstConnectionTime.ElapsedMilliseconds) < 0.30, $"Second AAD connection too slow ({secondConnectionTime.ElapsedMilliseconds}ms)! (More than 30% of the first ({firstConnectionTime.ElapsedMilliseconds}ms).)"); } #region Managed Identity Authentication tests - [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] - public static void SystemAssigned_ManagedIdentityTest() - { - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + - $"Authentication=Active Directory Managed Identity;"; - ConnectAndDisconnect(connStr); - } - - [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] - public static void UserAssigned_ManagedIdentityTest() - { - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + - $"Authentication=Active Directory Managed Identity; User Id={DataTestUtility.UserManagedIdentityClientId};"; - ConnectAndDisconnect(connStr); - } - [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] public static void AccessToken_SystemManagedIdentityTest() { @@ -760,36 +584,6 @@ public static void AccessToken_UserManagedIdentityTest() } } - [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsAzure), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] - public static void Azure_SystemManagedIdentityTest() - { - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; - string connectionString = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.TCPConnectionString, removeKeys) - + $"Authentication=Active Directory Managed Identity;"; - - using (SqlConnection conn = new SqlConnection(connectionString)) - { - conn.Open(); - - Assert.True(conn.State == System.Data.ConnectionState.Open); - } - } - - [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsAzure), nameof(IsManagedIdentitySetup))] - public static void Azure_UserManagedIdentityTest() - { - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; - string connectionString = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.TCPConnectionString, removeKeys) - + $"Authentication=Active Directory Managed Identity; User Id={DataTestUtility.UserManagedIdentityClientId}"; - - using (SqlConnection conn = new SqlConnection(connectionString)) - { - conn.Open(); - - Assert.True(conn.State == System.Data.ConnectionState.Open); - } - } - [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsAzure), nameof(IsAccessTokenSetup), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] public static void Azure_AccessToken_SystemManagedIdentityTest() { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Build.props index 26de699227..fb26623a2e 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;net8.0;net9.0 + net462;net8.0;net9.0;net10.0 + + + true true @@ -21,18 +27,30 @@ - - - - - - 6.1.0-preview2.25178.5 + + + 6.1.3 + + + + + + + + + $(AzurePackageVersion) + + + + + 1.0.0 + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj index 91fe0f81ee..ba086b58a8 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj index a105ccdf29..a60240475a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -43,17 +43,15 @@ + + + + - - PreserveNewest - %(Filename)%(Extension) - - - - + PreserveNewest xunit.runner.json - + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj index 45ba5c2cf9..2f802adc75 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj @@ -1,9 +1,6 @@  - netfx - netcoreapp - win - win-$(Platform) + netstandard2.0 $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) @@ -14,10 +11,6 @@ PreserveNewest - - - - diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Utils.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Utils.cs index 5d708e757d..bb1e2220a8 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Utils.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Utils.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; namespace Microsoft.Data.SqlClient.TestUtilities { diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json index 5ef2e9c99e..4362d07c75 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json @@ -35,5 +35,6 @@ "ManagedIdentitySupported": true, "UserManagedIdentityClientId": "", "PowerShellPath": "", - "AliasName": "" + "AliasName": "", + "WorkloadIdentityFederationServiceConnectionId": "" } 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 42755c93ec..38ccf678dd 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 @@ -1,9 +1,18 @@ { - "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", - "diagnosticMessages": true, - "parallelizeAssembly": true, - "shadowCopy": false, - "printMaxEnumerableLength": 0, - "printMaxStringLength": 0, - "showLiveOutput": false + "_comment1": "xUnit v2 doesn't allow proper JSON comments, so we put our comments in keys that are ignored.", + "_comment2": "Options for v3+ are prefixed with '_v3_'", + + "$schema": "https://xunit.net/schema/v2.8/xunit.runner.schema.json", + "_v3_$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + + "diagnosticMessages": true, + "parallelizeAssembly": true, + "shadowCopy": false, + + "_v3_culture": "invariant", + "_v3_printMaxEnumerableLength": 0, + "_v3_printMaxObjectDepth": 0, + "_v3_printMaxObjectMemberCount": 0, + "_v3_printMaxStringLength": 0, + "_v3_showLiveOutput": true } diff --git a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj index 48d163cab6..89d6d91b37 100644 --- a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj +++ b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj @@ -6,7 +6,7 @@ net46;netstandard2.0 $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName)\ $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName)\ - $(OutputPath)$(TargetFramework)\Microsoft.SqlServer.Server.xml + $(OutputPath)$(TargetFramework)\$(AssemblyName).xml portable false true diff --git a/src/Microsoft.SqlServer.Server/StringsHelper.cs b/src/Microsoft.SqlServer.Server/StringsHelper.cs index 610839adbf..8f3915e18c 100644 --- a/src/Microsoft.SqlServer.Server/StringsHelper.cs +++ b/src/Microsoft.SqlServer.Server/StringsHelper.cs @@ -48,13 +48,17 @@ public static string GetResourceString(string res) { StringsHelper sys = GetLoader(); if (sys == null) + { return null; + } // If "res" is a resource id, temp will not be null, "res" will contain the retrieved resource string. // If "res" is not a resource id, temp will be null. string temp = sys._resources.GetString(res, Culture); if (temp != null) + { res = temp; + } return res; } diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index 4792149ed2..1e8ea94d75 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -86,20 +86,20 @@ - - + + - - + + - - + + - - + + @@ -110,105 +110,110 @@ - - + + - - + + - - + + - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - + + - - - - + + + + - - - - + + + + diff --git a/tools/targets/CopySniDllsForNetFxProjectReferenceBuilds.targets b/tools/targets/CopySniDllsForNetFxProjectReferenceBuilds.targets new file mode 100644 index 0000000000..e24a40cb01 --- /dev/null +++ b/tools/targets/CopySniDllsForNetFxProjectReferenceBuilds.targets @@ -0,0 +1,51 @@ + + + + + + + + .NetFx + + + + + + + + + + + diff --git a/tools/targets/GenerateMdsPackage.targets b/tools/targets/GenerateMdsPackage.targets index 1c90310770..1b3418860a 100644 --- a/tools/targets/GenerateMdsPackage.targets +++ b/tools/targets/GenerateMdsPackage.targets @@ -1,5 +1,11 @@ + + + .NetFx + .NetCore + + @@ -8,6 +14,6 @@ - + From 0617c8e8603068793994c8360858284fea880261 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 16 Jan 2026 09:42:56 -0400 Subject: [PATCH 13/13] Azure Split - Use MDS Common Project (#3873) --- .../jobs/pack-abstractions-package-ci-job.yml | 1 - .../jobs/pack-azure-package-ci-job.yml | 1 - .../jobs/test-abstractions-package-ci-job.yml | 1 - .../jobs/test-azure-package-ci-job.yml | 50 ++++-- .../stages/build-azure-package-ci-stage.yml | 51 ++++-- src/Directory.Build.props | 39 ----- .../Abstractions/src/Abstractions.csproj | 8 +- .../src/SqlAuthenticationProviderInternal.cs | 15 +- .../test/Abstractions.Test.csproj | 10 +- .../test/SqlAuthenticationProviderTest.cs | 13 +- .../Azure/src/Azure.csproj | 6 +- .../Azure/test/AADConnectionTest.cs | 25 ++- .../Azure/test/Azure.Test.csproj | 16 +- .../Azure/test/Config.cs | 3 - .../Azure/test/StringExtensions.cs | 6 +- .../ref/Microsoft.Data.SqlClient.csproj | 5 - .../src/Microsoft.Data.SqlClient.csproj | 5 - .../netfx/ref/Microsoft.Data.SqlClient.csproj | 5 - .../netfx/src/Microsoft.Data.SqlClient.csproj | 5 - .../src/Microsoft.Data.SqlClient.csproj | 45 ++++- .../Data/Common/AdapterUtil.netfx.cs | 3 + .../DbConnectionPoolIdentity.cs | 2 +- .../SqlClient/Diagnostics/SqlClientMetrics.cs | 2 + .../Microsoft/Data/SqlClient/SqlDependency.cs | 2 + .../src/Microsoft/Data/SqlClient/SqlUtil.cs | 2 + .../src/Microsoft/Data/SqlClient/TdsParser.cs | 2 + tools/specs/Microsoft.Data.SqlClient.nuspec | 159 +++++++++--------- ...DllsForNetFxProjectReferenceBuilds.targets | 11 +- tools/targets/GenerateMdsPackage.targets | 7 +- 29 files changed, 243 insertions(+), 257 deletions(-) diff --git a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml index c928baa62d..4fd0d8d73d 100644 --- a/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-abstractions-package-ci-job.yml @@ -81,7 +81,6 @@ jobs: value: >- $(commonArguments) --configuration ${{ parameters.buildConfiguration }} - -p:ForceMdsAssemblyNameSuffix=true -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} # Explicitly unset the $PLATFORM environment variable that is set by the diff --git a/eng/pipelines/jobs/pack-azure-package-ci-job.yml b/eng/pipelines/jobs/pack-azure-package-ci-job.yml index c2f6e408fe..837fcc5409 100644 --- a/eng/pipelines/jobs/pack-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-azure-package-ci-job.yml @@ -100,7 +100,6 @@ jobs: value: >- --verbosity ${{ parameters.dotnetVerbosity }} -p:ReferenceType=${{ parameters.referenceType }} - -p:ForceMdsAssemblyNameSuffix=true -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} # dotnet CLI arguments for build/test/pack commands diff --git a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml index 965c2d7d0c..a4fea75efa 100644 --- a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml @@ -98,7 +98,6 @@ jobs: value: >- $(commonArguments) --configuration ${{ parameters.buildConfiguration }} - -p:ForceMdsAssemblyNameSuffix=true # Explicitly unset the $PLATFORM environment variable that is set by the # 'ADO Build properties' Library in the ADO SqlClientDrivers public project. diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index afba00dc03..288030f3b8 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -97,6 +97,18 @@ parameters: - Package - Project + # Steps to run, if any, to configure a local SQL Server instance on the agent + # VM. + - name: sqlServerSetupSteps + type: stepList + default: [] + + # True if the VM image includes a local SQL Server that supports connections + # via integrated security. + - name: supportsIntegratedSecurity + type: boolean + default: false + # The pool VM image to use. - name: vmImage type: string @@ -129,16 +141,24 @@ jobs: value: >- --verbosity ${{ parameters.dotnetVerbosity }} -p:ReferenceType=${{ parameters.referenceType }} - -p:ForceMdsAssemblyNameSuffix=true -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - # dotnet CLI arguments for build/test/pack commands + # dotnet CLI arguments for build/test/pack commands. - name: buildArguments value: >- $(commonArguments) --configuration ${{ parameters.buildConfiguration }} + # dotnet CLI arguments for test commands. + # + # Filter out tests annotated with the ActiveIssue attribute, which for + # some reason uses the category 'failing' rather than 'ActiveIssue'. + # + - name: testArguments + value: >- + --filter "category != failing" + # 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 @@ -232,19 +252,25 @@ jobs: AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) # macOS doesn't support managed identities. ManagedIdentitySupported: ${{ not(eq(parameters.vmImage, 'macos-latest')) }} - SupportsIntegratedSecurity: ${{ eq(variables['SupportsIntegratedSecurity'], 'true') }} + SupportsIntegratedSecurity: ${{ parameters.supportsIntegratedSecurity }} TCPConnectionString: $(AZURE_DB_TCP_CONN_STRING) UserManagedIdentityClientId: $(UserManagedIdentityClientId) WorkloadIdentityFederationServiceConnectionId: $(WorkloadIdentityFederationServiceConnectionId) - # Note: Using the isFork variable to determine if secrets are - # available is not ideal since it's an indirect association. But - # everything else (referencing secret variables various ways to detect - # if they were present) won't run consistently across forks and - # non-forks. + # Avoid exposing secrets to pipeline jobs triggered via forks. This + # prevents external contributors from creating PRs and running + # pipelines that could expose these secrets. + # + # Note that this isn't a perfect restriction since internal + # contributors may want to use forks, but this would prevent them from + # running the full test suite. We don't have a better way to detect + # external parties though. ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) AADServicePrincipalSecret: $(AADServicePrincipalSecret) + # Perform any local SQL Server setup. + - ${{ parameters.sqlServerSetupSteps }} + # We use the 'custom' command because the DotNetCoreCLI@2 task doesn't # support all of our argument combinations for the different build steps. @@ -282,7 +308,7 @@ jobs: # Many of our tests require access to Azure resources that are # currently only granted by agents running our custom ADO 1ES # images in our ADO pools. - ${{ if startsWith(parameters.poolName, 'ADO-') }}: + ${{ if ne(parameters.poolName, 'Azure Pipelines') }}: ADO_POOL: 1 # When using connectedServiceName below, the DotNetCoreCLI task # needs the system access token to be injected as this environment @@ -299,14 +325,14 @@ jobs: command: custom custom: test projects: $(project) - arguments: $(buildArguments) --no-build -f ${{ runtime }} + arguments: $(buildArguments) $(testArguments) --no-build -f ${{ runtime }} # Run the tests for each .NET Framework runtime. - ${{ each runtime in parameters.netFrameworkRuntimes }}: - task: DotNetCoreCLI@2 displayName: Test [${{ runtime }}] env: - ${{ if startsWith(parameters.poolName, 'ADO-CI') }}: + ${{ if ne(parameters.poolName, 'Azure Pipelines') }}: ADO_POOL: 1 SYSTEM_ACCESSTOKEN: $(System.AccessToken) ${{ if eq(parameters.debug, true) }}: @@ -316,4 +342,4 @@ jobs: command: custom custom: test projects: $(project) - arguments: $(buildArguments) --no-build -f ${{ runtime }} + arguments: $(buildArguments) $(testArguments) --no-build -f ${{ runtime }} diff --git a/eng/pipelines/stages/build-azure-package-ci-stage.yml b/eng/pipelines/stages/build-azure-package-ci-stage.yml index 26b1d0d1eb..35504b2c85 100644 --- a/eng/pipelines/stages/build-azure-package-ci-stage.yml +++ b/eng/pipelines/stages/build-azure-package-ci-stage.yml @@ -46,8 +46,8 @@ parameters: # # Any pool specified here must contain images with the following names: # + # - ADO-MMS22-SQL22 # - ADO-UB22-SQL22 - # - ADO-CI-Win11 # default: $(ci_var_defaultPoolName) @@ -139,7 +139,7 @@ stages: debug: ${{ parameters.debug }} displayNamePrefix: Linux dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - jobNameSuffix: linux_basic + jobNameSuffix: linux mdsArtifactName: MDS.Artifact mdsPackageVersion: ${{ parameters.mdsPackageVersion }} netFrameworkRuntimes: [] @@ -155,15 +155,25 @@ stages: abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} - displayNamePrefix: Linux + displayNamePrefix: Linux Integration dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - jobNameSuffix: linux_comprehensive + jobNameSuffix: linux_integration mdsArtifactName: MDS.Artifact mdsPackageVersion: ${{ parameters.mdsPackageVersion }} netFrameworkRuntimes: [] netRuntimes: [net8.0, net9.0, net10.0] poolName: ${{ parameters.adoPoolName }} referenceType: ${{ parameters.referenceType }} + # The image includes a SQL Server instance that we must configure. + sqlServerSetupSteps: + - template: /eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml@self + parameters: + # Override the template's default step condition. We always + # want this step to run. + condition: true + # Use the Azure DevOps Library variable "password" for the SA + # password. + password: $(password) vmImage: ADO-UB22-SQL22 # ------------------------------------------------------------------------ @@ -178,7 +188,7 @@ stages: debug: ${{ parameters.debug }} displayNamePrefix: Win dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - jobNameSuffix: windows_basic + jobNameSuffix: windows mdsArtifactName: MDS.Artifact mdsPackageVersion: ${{ parameters.mdsPackageVersion }} netFrameworkRuntimes: [net462] @@ -194,16 +204,33 @@ stages: abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} - displayNamePrefix: Win + displayNamePrefix: Win Integration dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - jobNameSuffix: windows_comprehensive + jobNameSuffix: windows_integration mdsArtifactName: MDS.Artifact mdsPackageVersion: ${{ parameters.mdsPackageVersion }} netFrameworkRuntimes: [net462] netRuntimes: [net8.0, net9.0, net10.0] poolName: ${{ parameters.adoPoolName }} referenceType: ${{ parameters.referenceType }} - vmImage: ADO-CI-Win11 + # The image includes a SQL Server instance that we must configure. + sqlServerSetupSteps: + - template: /eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml@self + # Use defaults for most parameters. + parameters: + # Override the template's default step condition. We always + # want this step to run. + condition: true + enableLocalDB: true + # These variables are from an Azure DevOps Library variable + # group. + fileStreamDirectory: $(FileStreamDirectory) + password: $(password) + sqlRootPath: $(SQL22RootPath) + # The ADO-MMS22-SQL22 image includes a local SQL Server that supports + # integrated security. + supportsIntegratedSecurity: true + vmImage: ADO-MMS22-SQL22 # ------------------------------------------------------------------------ # Build and test on macOS. @@ -242,10 +269,10 @@ stages: dependsOn: # We depend on all of the test jobs to ensure the tests pass before # producing the NuGet package. - - test_azure_package_job_linux_basic - - test_azure_package_job_linux_comprehensive - - test_azure_package_job_windows_basic - - test_azure_package_job_windows_comprehensive + - test_azure_package_job_linux + - test_azure_package_job_linux_integration + - test_azure_package_job_windows + - test_azure_package_job_windows_integration - test_azure_package_job_macos dotnetVerbosity: ${{ parameters.dotnetVerbosity }} referenceType: ${{ parameters.referenceType }} diff --git a/src/Directory.Build.props b/src/Directory.Build.props index f38c666907..7a3e277e2a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -153,43 +153,4 @@ 14 - - - - false - true - diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj index 53652e0d8b..ddb74f9244 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj @@ -42,12 +42,6 @@ $(AbstractionsPackageVersion) $(Artifacts)/doc/$(TargetFramework)/$(AssemblyName).xml - - - $(DefineConstants);APPLY_MDS_ASSEMBLY_NAME_SUFFIX @@ -86,7 +80,7 @@ dotnet.png - + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs index 309edf4998..a1903e9279 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProviderInternal.cs @@ -32,20 +32,7 @@ private static class Internal /// static Internal() { - // Choose the MDS assembly name based on compilation flags and the - // runtime environment. See the top-level Directory.Build.props for - // more information. - string assemblyName = "Microsoft.Data.SqlClient"; - #if (APPLY_MDS_ASSEMBLY_NAME_SUFFIX) - if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) - { - assemblyName += ".NetFx"; - } - else - { - assemblyName += ".NetCore"; - } - #endif + const string assemblyName = "Microsoft.Data.SqlClient"; // If the MDS package is present, load its // SqlAuthenticationProviderManager class and get/set methods. 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 23e4ea451f..aeb752d648 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/Abstractions.Test.csproj @@ -9,14 +9,6 @@ Microsoft.Data.SqlClient.Extensions.Abstractions.Test - - - $(DefineConstants);APPLY_MDS_ASSEMBLY_NAME_SUFFIX - - @@ -33,7 +25,7 @@ - + PreserveNewest xunit.runner.json diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs index 4c837332e5..07e2b40078 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/test/SqlAuthenticationProviderTest.cs @@ -8,23 +8,14 @@ namespace Microsoft.Data.SqlClient.Extensions.Abstractions.Test; public class SqlAuthenticationProviderTest { - // Choose the MDS assembly name based on compilation flags. See the - // top-level Directory.Build.props for more information. - #if (APPLY_MDS_ASSEMBLY_NAME_SUFFIX && NET) - const string assemblyName = "Microsoft.Data.SqlClient.NetCore"; - #elif (APPLY_MDS_ASSEMBLY_NAME_SUFFIX && NETFRAMEWORK) - const string assemblyName = "Microsoft.Data.SqlClient.NetFx"; - #else - const string assemblyName = "Microsoft.Data.SqlClient"; - #endif - /// /// Construct to confirm preconditions. /// public SqlAuthenticationProviderTest() { // Confirm that the MDS assembly is indeed not present. - Assert.Throws(() => Assembly.Load(assemblyName)); + Assert.Throws( + () => Assembly.Load("Microsoft.Data.SqlClient")); } #region Tests diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj index 74dba18d1f..9ebb0806ac 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj @@ -80,7 +80,7 @@ dotnet.png - + - + @@ -121,6 +121,6 @@ --> $(AssemblyName) - + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs index dda1afabef..dd6f3bcdf0 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs @@ -194,9 +194,22 @@ public static void ActiveDirectoryDefaultMustPass() ConnectAndDisconnect(connStr); } + // This test works on main in the existing jobs (like Win22_Sql22), but + // fails in the Azure project tests on a similar agent/image: + // + // Failed Microsoft.Data.SqlClient.Extensions.Azure.Test.AADConnectionTest.ADIntegratedUsingSSPI [59 ms] + // Error Message: + // Microsoft.Data.SqlClient.SqlException : Failed to authenticate the user NT Authority\Anonymous Logon in Active Directory (Authentication=ActiveDirectoryIntegrated). + // Error code 0xget_user_name_failed + // Failed to acquire access token for ActiveDirectoryIntegrated: Failed to get user name. + // + // ActiveIssue tests can be filtered out of test runs on the dotnet CLI + // using the filter "category != failing". + // + [ActiveIssue("https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/40107")] [ConditionalFact( typeof(Config), - nameof(Config.HasIntegratedSecurityConnectionString), + nameof(Config.SupportsIntegratedSecurity), nameof(Config.HasTcpConnectionString))] public static void ADIntegratedUsingSSPI() { @@ -209,9 +222,9 @@ public static void ADIntegratedUsingSSPI() [ConditionalFact( typeof(Config), - nameof(Config.HasPasswordConnectionString), nameof(Config.SupportsManagedIdentity), - nameof(Config.SupportsSystemAssignedManagedIdentity))] + nameof(Config.SupportsSystemAssignedManagedIdentity), + nameof(Config.HasPasswordConnectionString))] public static void SystemAssigned_ManagedIdentityTest() { string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; @@ -235,9 +248,9 @@ public static void UserAssigned_ManagedIdentityTest() [ConditionalFact( typeof(Config), - nameof(Config.HasTcpConnectionString), nameof(Config.SupportsManagedIdentity), nameof(Config.SupportsSystemAssignedManagedIdentity), + nameof(Config.HasTcpConnectionString), nameof(Config.IsAzureSqlServer))] public static void Azure_SystemManagedIdentityTest() { @@ -255,10 +268,10 @@ public static void Azure_SystemManagedIdentityTest() [ConditionalFact( typeof(Config), + nameof(Config.OnAdoPool), + nameof(Config.SupportsManagedIdentity), nameof(Config.HasTcpConnectionString), nameof(Config.HasUserManagedIdentityClientId), - nameof(Config.SupportsManagedIdentity), - nameof(Config.SupportsSystemAssignedManagedIdentity), nameof(Config.IsAzureSqlServer))] public static void Azure_UserManagedIdentityTest() { 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 592c4c3c56..bc4e5b14b1 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj @@ -27,7 +27,7 @@ - + PreserveNewest xunit.runner.json @@ -41,21 +41,12 @@ tests into the artifacts. We don't refer to any of its assemblies. --> - + - - - + @@ -63,5 +54,4 @@ - diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs index 1db51d1c87..2a01a12384 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs @@ -4,7 +4,6 @@ using System.Runtime.InteropServices; using System.Text.Json; - using Microsoft.Data.SqlClient.TestUtilities; namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; @@ -56,8 +55,6 @@ internal static class Config #region Conditional Fact/Theory Helpers - internal static bool HasIntegratedSecurityConnectionString() => - !TcpConnectionString.Empty() && IntegratedSecuritySupported; internal static bool HasPasswordConnectionString() => !PasswordConnectionString.Empty(); internal static bool HasServicePrincipal() => !ServicePrincipalId.Empty() && !ServicePrincipalSecret.Empty(); internal static bool HasSystemAccessToken() => !SystemAccessToken.Empty(); diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/StringExtensions.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/StringExtensions.cs index acc7f10310..def794cc21 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/StringExtensions.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/StringExtensions.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -// Adds the missing Empty() method to string that doesn't waste time on null -// checks like String.IsNullOrEmpty() does, and has a nice short name. +/// +/// Adds the missing Empty() method to string that doesn't waste time on null +/// checks like String.IsNullOrEmpty() does, and has a nice short name. +/// internal static class StringExtensions { internal static bool Empty(this string str) 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 d7821b8a35..8618e36fd7 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -1,11 +1,6 @@  - Microsoft.Data.SqlClient - $(AssemblyName).NetCore net8.0;net9.0;netstandard2.0 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 70cb79a8b0..b0465b4756 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -1,11 +1,6 @@  - Microsoft.Data.SqlClient - $(AssemblyName).NetCore net8.0;net9.0 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 4cdfa44f73..e3e46f2349 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -1,11 +1,6 @@  - Microsoft.Data.SqlClient - $(AssemblyName).NetFx net462 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 e3e1309607..1bcba9057c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -1,11 +1,6 @@  - Microsoft.Data.SqlClient - $(AssemblyName).NetFx net462 diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 9bc0137542..c17ce89279 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -1,6 +1,7 @@  + Microsoft.Data.SqlClient Debug;Release; true @@ -15,11 +16,26 @@ $(SigningKeyPath) + + + net462;net8.0;net9.0 + - + + Windows_NT $(OS) @@ -34,13 +50,6 @@ $(DefineConstants);_WINDOWS - - - - net8.0;net9.0 - $(TargetFrameworks);net462 - - @@ -60,6 +69,26 @@ + + + + Microsoft.Data.SqlClient.Resources.Strings.resources + ResXFileCodeGenerator + Strings.Designer.cs + System + + + + + Microsoft.Data.SqlClient.Resources.%(Filename).resources + + + + + True + True + Strings.resx + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs index c7427a6a8d..8b8d8b3c5b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.netfx.cs @@ -13,7 +13,10 @@ using System.Security.Permissions; using System.Text; using System.Threading.Tasks; + +#if _WINDOWS using Interop.Windows.Kernel32; +#endif namespace Microsoft.Data.Common { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs index 7eca7bd0f6..057433cf90 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs @@ -80,7 +80,7 @@ internal static DbConnectionPoolIdentity GetCurrent() #endif } - #if NETFRAMEWORK + #if NETFRAMEWORK && _WINDOWS internal static WindowsIdentity GetCurrentWindowsIdentity() => WindowsIdentity.GetCurrent(); #endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs index f6eb1c84c1..a813a03003 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs @@ -8,7 +8,9 @@ #if NET using System.Threading; #else +#if _WINDOWS using Interop.Windows.Kernel32; +#endif using Microsoft.Data.Common; using System.Diagnostics; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs index e237e0e3e7..9ce5c20342 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs @@ -20,7 +20,9 @@ using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security.Permissions; +#if _WINDOWS using Interop.Windows.Sni; +#endif using Microsoft.Data.SqlClient.LocalDb; #endif 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 7c7d3e8c87..9054f88ab7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -21,7 +21,9 @@ #if NETFRAMEWORK using System.Runtime.InteropServices; using System.Runtime.Versioning; +#if _WINDOWS using Interop.Windows.Kernel32; +#endif #else using System.Net.Sockets; #endif 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 23063704ae..3f06a04ac6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -30,7 +30,9 @@ #if NETFRAMEWORK using System.Runtime.CompilerServices; +#if _WINDOWS using Interop.Windows.Sni; +#endif using Microsoft.Data.SqlTypes; #endif diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index 1e8ea94d75..4792149ed2 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -86,20 +86,20 @@ - - + + - - + + - - + + - - + + @@ -110,110 +110,105 @@ - - + + - - + + - - + + - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - + + - - - - + + + + - - - - + + + + diff --git a/tools/targets/CopySniDllsForNetFxProjectReferenceBuilds.targets b/tools/targets/CopySniDllsForNetFxProjectReferenceBuilds.targets index e24a40cb01..47c45c41cd 100644 --- a/tools/targets/CopySniDllsForNetFxProjectReferenceBuilds.targets +++ b/tools/targets/CopySniDllsForNetFxProjectReferenceBuilds.targets @@ -34,15 +34,14 @@ Name="CopySniDllsForNetFx" AfterTargets="CopyFilesToOutputDirectory" Condition="'$(ReferenceType)' == 'Project' AND '$(TargetFramework)' == 'net462'"> - - - - .NetFx - - + + + + + diff --git a/tools/targets/GenerateMdsPackage.targets b/tools/targets/GenerateMdsPackage.targets index 1b3418860a..50f9b8fddd 100644 --- a/tools/targets/GenerateMdsPackage.targets +++ b/tools/targets/GenerateMdsPackage.targets @@ -1,11 +1,6 @@ - - .NetFx - .NetCore - - @@ -14,6 +9,6 @@ - +