From 20cd30365da339a1e9f1a79277ab4c9cd633278e Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Fri, 17 Jul 2026 15:31:51 -0700 Subject: [PATCH 1/7] Enforce CFSClean for Network Isolation --- eng/common/pipelines/templates/1es-redirect.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/common/pipelines/templates/1es-redirect.yml b/eng/common/pipelines/templates/1es-redirect.yml index 7711cb6d7a1..00be16b2438 100644 --- a/eng/common/pipelines/templates/1es-redirect.yml +++ b/eng/common/pipelines/templates/1es-redirect.yml @@ -27,6 +27,7 @@ extends: parameters: settings: skipBuildTagsForGitHubPullRequests: true + networkIsolationPolicy: Permissive, CFSClean sdl: codeql: compiled: From 21ef6097ed06a0037c36581065860be6b771c1f6 Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Fri, 17 Jul 2026 15:59:58 -0700 Subject: [PATCH 2/7] Add Nuget config targeting devOps feed --- NuGet.Config | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 NuGet.Config diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 00000000000..c679a5601f0 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,11 @@ + + + + + + + + + + + From 2174406c50aba8a04edd803de6c6e06cd8540f7a Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Fri, 17 Jul 2026 16:34:48 -0700 Subject: [PATCH 3/7] Authenticate to Dev Feed for Java and Python --- .../templates/steps/auth-dev-feed.yml | 49 +++++++++++++++++++ .../templates/steps/maven-authenticate.yml | 17 +++++++ .../pipelines/templates/steps/build-step.yml | 4 ++ 3 files changed, 70 insertions(+) create mode 100644 eng/common/pipelines/templates/steps/auth-dev-feed.yml create mode 100644 eng/common/pipelines/templates/steps/maven-authenticate.yml diff --git a/eng/common/pipelines/templates/steps/auth-dev-feed.yml b/eng/common/pipelines/templates/steps/auth-dev-feed.yml new file mode 100644 index 00000000000..464136b3dc7 --- /dev/null +++ b/eng/common/pipelines/templates/steps/auth-dev-feed.yml @@ -0,0 +1,49 @@ +parameters: + DevFeedName: 'public/azure-sdk-for-python' + EnableTwineAuth: true + EnablePipAuth: true + EnableUvAuth: true + +steps: + - pwsh: | + # For safety default to publishing to the private feed. + # Publish to https://dev.azure.com/azure-sdk/internal/_packaging?_a=feed&feed=azure-sdk-for-python-pr + $devopsFeedName = 'internal/azure-sdk-for-python-pr' + if ('$(Build.Repository.Name)' -eq 'Azure/azure-sdk-for-python') { + # Publish to https://dev.azure.com/azure-sdk/public/_packaging?_a=feed&feed=azure-sdk-for-python + $devopsFeedName = '${{ parameters.DevFeedName }}' + } + echo "##vso[task.setvariable variable=DevFeedName]$devopsFeedName" + echo "Using DevopsFeed = $devopsFeedName" + displayName: Setup DevOpsFeedName + + - ${{ if eq(parameters.EnableTwineAuth, true) }}: + - task: TwineAuthenticate@0 + displayName: 'Twine Authenticate to feed' + inputs: + artifactFeeds: $(DevFeedName) + + - ${{ if eq(parameters.EnablePipAuth, true) }}: + - task: PipAuthenticate@1 + displayName: 'Pip Authenticate to feed' + inputs: + artifactFeeds: $(DevFeedName) + onlyAddExtraIndex: false + + - ${{ if eq(parameters.EnableUvAuth, true) }}: + - pwsh: | + if ($env:PIP_INDEX_URL) { + Write-Host "Found pip index URL: $($env:PIP_INDEX_URL)" + # UV_DEFAULT_INDEX is the canonical replacement for the deprecated UV_INDEX_URL (uv 0.4.23+). + # PIP_INDEX_URL is set by PipAuthenticate@1 and contains embedded credentials, which uv + # will use for Basic auth against the ADO feed (and its PyPI upstream) per astral-sh/uv#12651. + Write-Host "##vso[task.setvariable variable=UV_DEFAULT_INDEX]$($env:PIP_INDEX_URL)" + # Disable keyring so uv uses the URL-embedded credentials directly. + Write-Host "##vso[task.setvariable variable=UV_KEYRING_PROVIDER]disabled" + } else { + Write-Host "##[warning]PIP_INDEX_URL not set - uv will fall back to public PyPI." + } + # Force any managed Python downloads to go directly to GitHub releases + # rather than the default CDN (releases.astral.sh). + Write-Host "##vso[task.setvariable variable=UV_PYTHON_INSTALL_MIRROR]https://github.com/astral-sh/python-build-standalone/releases/download" + displayName: 'Configure UV Authentication' diff --git a/eng/common/pipelines/templates/steps/maven-authenticate.yml b/eng/common/pipelines/templates/steps/maven-authenticate.yml new file mode 100644 index 00000000000..f03caee204f --- /dev/null +++ b/eng/common/pipelines/templates/steps/maven-authenticate.yml @@ -0,0 +1,17 @@ +parameters: + SourceDirectory: $(Build.SourcesDirectory) + +steps: + # Copy mirror settings to default Maven location so all requests go through CFS + - pwsh: | + $m2Dir = if ($env:USERPROFILE) { "$env:USERPROFILE\.m2" } else { "$HOME/.m2" } + New-Item -ItemType Directory -Force -Path $m2Dir | Out-Null + Copy-Item -Path "${{ parameters.SourceDirectory }}/eng/settings.xml" -Destination "$m2Dir/settings.xml" + displayName: 'Setup Maven mirror settings' + + # Authenticate with Azure Artifacts feeds + # MavenAuthenticate adds entries to ~/.m2/settings.xml matching mirror id 'azure-sdk-for-java' + - task: MavenAuthenticate@0 + displayName: 'Maven Authenticate' + inputs: + artifactsFeeds: 'azure-sdk-for-java' diff --git a/eng/emitters/pipelines/templates/steps/build-step.yml b/eng/emitters/pipelines/templates/steps/build-step.yml index d723de94d4b..a0888cd13cb 100644 --- a/eng/emitters/pipelines/templates/steps/build-step.yml +++ b/eng/emitters/pipelines/templates/steps/build-step.yml @@ -61,6 +61,10 @@ steps: - template: /eng/common/pipelines/templates/steps/create-authenticated-npmrc.yml + - template: /eng/common/pipelines/templates/steps/auth-dev-feed.yml + + - template: /eng/common/pipelines/templates/steps/maven-authenticate.yml + - task: NodeTool@0 displayName: Install Node.js retryCountOnTaskFailure: 3 From 94fe555b155cb2962121a63213a9cfedabb2e17a Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Mon, 20 Jul 2026 11:55:31 -0700 Subject: [PATCH 4/7] Add maven-authenticate settings.xml --- .../pipelines/templates/steps/auth-dev-feed.yml | 2 +- eng/settings.xml | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 eng/settings.xml diff --git a/eng/common/pipelines/templates/steps/auth-dev-feed.yml b/eng/common/pipelines/templates/steps/auth-dev-feed.yml index 464136b3dc7..cd8fa23e67e 100644 --- a/eng/common/pipelines/templates/steps/auth-dev-feed.yml +++ b/eng/common/pipelines/templates/steps/auth-dev-feed.yml @@ -9,7 +9,7 @@ steps: # For safety default to publishing to the private feed. # Publish to https://dev.azure.com/azure-sdk/internal/_packaging?_a=feed&feed=azure-sdk-for-python-pr $devopsFeedName = 'internal/azure-sdk-for-python-pr' - if ('$(Build.Repository.Name)' -eq 'Azure/azure-sdk-for-python') { + if (-not ('$(Build.Repository.Name)').EndsWith('-pr')) { # Publish to https://dev.azure.com/azure-sdk/public/_packaging?_a=feed&feed=azure-sdk-for-python $devopsFeedName = '${{ parameters.DevFeedName }}' } diff --git a/eng/settings.xml b/eng/settings.xml new file mode 100644 index 00000000000..b1941716f65 --- /dev/null +++ b/eng/settings.xml @@ -0,0 +1,13 @@ + + + + azure-sdk-for-java + Azure Artifacts Maven Mirror + https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-java/maven/v1 + external:* + + + From 530c1b9ed756634e16156dfe5fabcb8d53a435bc Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Mon, 20 Jul 2026 14:41:35 -0700 Subject: [PATCH 5/7] Update ExternalTypeReferenceResolverTests does not probe external feeds --- .../ExternalTypeReferenceResolverTests.cs | 61 ++++++++++++++----- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/ExternalTypeReferenceResolverTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/ExternalTypeReferenceResolverTests.cs index a507018bace..50056b82802 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/ExternalTypeReferenceResolverTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Utilities/ExternalTypeReferenceResolverTests.cs @@ -20,6 +20,7 @@ public class ExternalTypeReferenceResolverTests { private string? _tempDirectory; private string? _projectDir; + private string? _nugetCacheDir; private string? _originalNugetPackageDir; [SetUp] @@ -27,14 +28,18 @@ public void Setup() { _tempDirectory = Path.Combine(Path.GetTempPath(), "TestArtifacts", Guid.NewGuid().ToString()); _projectDir = Path.Combine(_tempDirectory, "ProjectDir"); - var nugetCacheDir = Path.Combine(_tempDirectory, "NuGetCache"); + _nugetCacheDir = Path.Combine(_tempDirectory, "NuGetCache"); Directory.CreateDirectory(Path.Combine(_projectDir, "src")); - Directory.CreateDirectory(nugetCacheDir); + Directory.CreateDirectory(_nugetCacheDir); + + // Ensure these tests are fully isolated from machine/user NuGet settings and never probe + // external feeds when package lookup falls through to source probing. + WriteLocalNuGetConfig(_projectDir, _nugetCacheDir); _originalNugetPackageDir = Environment.GetEnvironmentVariable("NUGET_PACKAGES", EnvironmentVariableTarget.Process); - Environment.SetEnvironmentVariable("NUGET_PACKAGES", nugetCacheDir, EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable("NUGET_PACKAGES", _nugetCacheDir, EnvironmentVariableTarget.Process); - ExternalTypeReferenceResolver.Reset(); + ResetResolverIfInitialized(); MockHelpers.LoadMockGenerator( outputPath: _projectDir, configuration: "{}"); @@ -43,7 +48,7 @@ public void Setup() [TearDown] public void Cleanup() { - ExternalTypeReferenceResolver.Reset(); + ResetResolverIfInitialized(); Directory.Delete(_tempDirectory!, true); Environment.SetEnvironmentVariable("NUGET_PACKAGES", _originalNugetPackageDir, EnvironmentVariableTarget.Process); } @@ -64,10 +69,9 @@ public void TryResolve_ReturnsNullWhenPackageMissing() [Test] public void TryResolve_LoadsTypeFromNuGetCache() { - var nugetCacheDir = Path.Combine(_tempDirectory!, "NuGetCache"); const string pkgName = "Test.External.Loadable"; const string typeName = "Test.External.Loadable.LoadableType"; - CreateFakeNuGetPackage(nugetCacheDir, pkgName, "1.2.3"); + CreateFakeNuGetPackage(_nugetCacheDir!, pkgName, "1.2.3"); var external = new InputExternalTypeMetadata(typeName, pkgName, null); @@ -80,14 +84,13 @@ public void TryResolve_LoadsTypeFromNuGetCache() [Test] public void TryResolve_PrefersHighestCachedVersionAtOrAboveMinVersion() { - var nugetCacheDir = Path.Combine(_tempDirectory!, "NuGetCache"); const string pkgName = "Test.MultiVersion.Package"; const string typeName = "Test.MultiVersion.Package.SomeType"; // Create three cached versions; MinVersion=2.0.0 must skip 1.0.0 and pick 3.0.0 (highest >= MinVersion). - CreateFakeNuGetPackage(nugetCacheDir, pkgName, "1.0.0"); - CreateFakeNuGetPackage(nugetCacheDir, pkgName, "2.5.0"); - CreateFakeNuGetPackage(nugetCacheDir, pkgName, "3.0.0"); + CreateFakeNuGetPackage(_nugetCacheDir!, pkgName, "1.0.0"); + CreateFakeNuGetPackage(_nugetCacheDir!, pkgName, "2.5.0"); + CreateFakeNuGetPackage(_nugetCacheDir!, pkgName, "3.0.0"); var external = new InputExternalTypeMetadata(typeName, pkgName, "2.0.0"); var resolved = ExternalTypeReferenceResolver.TryResolve(external); @@ -104,10 +107,9 @@ public void TryResolve_PrefersHighestCachedVersionAtOrAboveMinVersion() [Test] public void TryResolve_AddsMetadataReferenceOnce() { - var nugetCacheDir = Path.Combine(_tempDirectory!, "NuGetCache"); const string pkgName = "Test.MetadataRef.Package"; const string typeName = "Test.MetadataRef.Package.RefType"; - CreateFakeNuGetPackage(nugetCacheDir, pkgName, "1.0.0"); + CreateFakeNuGetPackage(_nugetCacheDir!, pkgName, "1.0.0"); var external = new InputExternalTypeMetadata(typeName, pkgName, null); @@ -141,10 +143,9 @@ public void TryResolve_ReturnsNullForUnknownPackage() [Test] public async Task ResolveAllAsync_ResolvesExternalTypesFromInputLibrary() { - var nugetCacheDir = Path.Combine(_tempDirectory!, "NuGetCache"); const string pkgName = "Test.PreWalk.Package"; const string typeName = "Test.PreWalk.Package.PreWalkType"; - CreateFakeNuGetPackage(nugetCacheDir, pkgName, "1.0.0"); + CreateFakeNuGetPackage(_nugetCacheDir!, pkgName, "1.0.0"); var external = new InputExternalTypeMetadata(typeName, pkgName, null); var unionWithExternal = InputFactory.Union( @@ -188,5 +189,35 @@ private static string CreateFakeNuGetPackage(string nugetCacheDir, string packag .Replace("$VERSION$", version); return FakeNuGetPackage.Create(nugetCacheDir, packageName, version, source); } + + private static void WriteLocalNuGetConfig(string projectDir, string nugetCacheDir) + { + var nugetConfigPath = Path.Combine(projectDir, "NuGet.Config"); + var normalizedCachePath = nugetCacheDir.Replace("\\", "/"); + var config = $@" + + + + + + + + +"; + + File.WriteAllText(nugetConfigPath, config); + } + + private static void ResetResolverIfInitialized() + { + try + { + ExternalTypeReferenceResolver.Reset(); + } + catch (InvalidOperationException) + { + // CodeModelGenerator may not be initialized yet when this fixture runs in isolation. + } + } } } From 8a1ec447884d621fe11f10509fa8b25b8b1a2489 Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Mon, 20 Jul 2026 14:53:41 -0700 Subject: [PATCH 6/7] Authenticate to DevOps feed in test-step --- eng/emitters/pipelines/templates/steps/test-step.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/eng/emitters/pipelines/templates/steps/test-step.yml b/eng/emitters/pipelines/templates/steps/test-step.yml index 59a2beb5689..1e35605d82a 100644 --- a/eng/emitters/pipelines/templates/steps/test-step.yml +++ b/eng/emitters/pipelines/templates/steps/test-step.yml @@ -53,6 +53,12 @@ steps: - template: /eng/common/pipelines/templates/steps/create-authenticated-npmrc.yml + - ${{ if eq(parameters.LanguageShortName, 'python') }}: + - template: /eng/common/pipelines/templates/steps/auth-dev-feed.yml + + - ${{ if eq(parameters.LanguageShortName, 'java') }}: + - template: /eng/common/pipelines/templates/steps/maven-authenticate.yml + - download: current artifact: ${{ parameters.BuildArtifactName }} displayName: Download build artifacts From 75fb7a16de4e20541f2fe3a0774a431241b0b194 Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Mon, 20 Jul 2026 16:14:39 -0700 Subject: [PATCH 7/7] Ensure nuget restore runs before tests and nuget config is used --- eng/tsp-core/pipelines/templates/install.yml | 2 +- packages/http-client-csharp/eng/scripts/Test-Packages.ps1 | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/eng/tsp-core/pipelines/templates/install.yml b/eng/tsp-core/pipelines/templates/install.yml index 7820c3a411a..0f5d4d05ec0 100644 --- a/eng/tsp-core/pipelines/templates/install.yml +++ b/eng/tsp-core/pipelines/templates/install.yml @@ -37,6 +37,6 @@ steps: retryCountOnTaskFailure: 3 - ${{ if parameters.useDotNet }}: - - script: dotnet restore + - script: dotnet restore --configfile "$(Build.SourcesDirectory)/NuGet.Config" displayName: Restore .NET Dependencies workingDirectory: packages/typespec-vs diff --git a/packages/http-client-csharp/eng/scripts/Test-Packages.ps1 b/packages/http-client-csharp/eng/scripts/Test-Packages.ps1 index bb0c24c43fe..be831bc6870 100644 --- a/packages/http-client-csharp/eng/scripts/Test-Packages.ps1 +++ b/packages/http-client-csharp/eng/scripts/Test-Packages.ps1 @@ -24,8 +24,14 @@ try { # enforce cop static-analysis rules on the generator sources Invoke-LoggedCommand "./eng/scripts/Invoke-Cop.ps1" -GroupOutput + # Use the repo NuGet.Config explicitly so restore always targets the Azure DevOps feed. + $repoRoot = (Resolve-Path "$packageRoot/../..").Path + $nugetConfigPath = (Join-Path $repoRoot "NuGet.Config") + Invoke-LoggedCommand "dotnet nuget list source --configfile `"$nugetConfigPath`"" -GroupOutput + Invoke-LoggedCommand "dotnet restore ./generator --configfile `"$nugetConfigPath`"" -GroupOutput + # test the generator - Invoke-LoggedCommand "dotnet test ./generator" -GroupOutput + Invoke-LoggedCommand "dotnet test ./generator --no-restore" -GroupOutput Invoke-LoggedCommand "./eng/scripts/Get-Spector-Coverage.ps1" -GroupOutput }