From 643cd7cf0888a6e979a594bd63c0d120aae5a56c Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 29 Oct 2024 16:44:34 -0700 Subject: [PATCH 1/8] Prepend prefix for MAR operations --- src/code/ContainerRegistryServerAPICalls.cs | 20 ++++++++++++++++---- src/code/PSRepositoryInfo.cs | 13 +++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/code/ContainerRegistryServerAPICalls.cs b/src/code/ContainerRegistryServerAPICalls.cs index f9dc060a9..39738f3a1 100644 --- a/src/code/ContainerRegistryServerAPICalls.cs +++ b/src/code/ContainerRegistryServerAPICalls.cs @@ -286,7 +286,8 @@ public override Stream InstallPackage(string packageName, string packageVersion, return results; } - results = InstallVersion(packageName, packageVersion, out errRecord); + string packageNameForInstall = PrependMARPrefix(packageName); + results = InstallVersion(packageNameForInstall, packageVersion, out errRecord); return results; } @@ -1601,13 +1602,14 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp string registryUrl = Repository.Uri.ToString(); string packageNameLowercase = packageName.ToLower(); + string packageNameForFind = PrependMARPrefix(packageNameLowercase); string containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord); if (errRecord != null) { return emptyHashResponses; } - var foundTags = FindContainerRegistryImageTags(packageNameLowercase, "*", containerRegistryAccessToken, out errRecord); + var foundTags = FindContainerRegistryImageTags(packageNameForFind, "*", containerRegistryAccessToken, out errRecord); if (errRecord != null || foundTags == null) { return emptyHashResponses; @@ -1616,7 +1618,7 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp List latestVersionResponse = new List(); List allVersionsList = foundTags["tags"].ToList(); - SortedDictionary sortedQualifyingPkgs = GetPackagesWithRequiredVersion(allVersionsList, versionType, versionRange, requiredVersion, packageNameLowercase, includePrerelease, out errRecord); + SortedDictionary sortedQualifyingPkgs = GetPackagesWithRequiredVersion(allVersionsList, versionType, versionRange, requiredVersion, packageNameForFind, includePrerelease, out errRecord); if (errRecord != null) { return emptyHashResponses; @@ -1627,7 +1629,7 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp foreach (var pkgVersionTag in pkgsInDescendingOrder) { string exactTagVersion = pkgVersionTag.Value.ToString(); - Hashtable metadata = GetContainerRegistryMetadata(packageNameLowercase, exactTagVersion, containerRegistryAccessToken, out errRecord); + Hashtable metadata = GetContainerRegistryMetadata(packageNameForFind, exactTagVersion, containerRegistryAccessToken, out errRecord); if (errRecord != null || metadata.Count == 0) { return emptyHashResponses; @@ -1694,6 +1696,16 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp return sortedPkgs; } + private string PrependMARPrefix(string packageName) + { + // If the repostitory is MAR and its not a wildcard search, we need to prefix the package name with MAR prefix. + string updatedPackageName = Repository.IsMARRepository() && packageName.Trim() != "*" + ? string.Concat(PSRepositoryInfo.MARPrefix, packageName) + : packageName; + + return updatedPackageName; + } + #endregion } } diff --git a/src/code/PSRepositoryInfo.cs b/src/code/PSRepositoryInfo.cs index b660c6690..cba9f81a4 100644 --- a/src/code/PSRepositoryInfo.cs +++ b/src/code/PSRepositoryInfo.cs @@ -11,6 +11,10 @@ namespace Microsoft.PowerShell.PSResourceGet.UtilClasses /// public sealed class PSRepositoryInfo { + #region constants + internal const string MARPrefix = "azure-powershell/"; + #endregion + #region Enums public enum APIVersion @@ -95,5 +99,14 @@ public enum RepositoryProviderType public bool IsAllowedByPolicy { get; set; } #endregion + + #region Methods + + internal bool IsMARRepository() + { + return (ApiVersion == APIVersion.ContainerRegistry && Uri.Host.Contains("mcr.microsoft.com")); + } + + #endregion } } From 955389d395ecd1ffd86f1b2909359db7a9f57719 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 29 Oct 2024 16:45:59 -0700 Subject: [PATCH 2/8] Block publishing to MAR endpoint --- src/code/PublishHelper.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/code/PublishHelper.cs b/src/code/PublishHelper.cs index 5470da611..0eec8e0d9 100644 --- a/src/code/PublishHelper.cs +++ b/src/code/PublishHelper.cs @@ -368,6 +368,17 @@ internal void PushResource(string Repository, string modulePrefix, bool SkipDepe return; } + if (repository.IsMARRepository()) + { + _cmdletPassedIn.WriteError(new ErrorRecord( + new PSInvalidOperationException($"Repository '{repository.Name}' is a MAR repository and cannot be published to."), + "MARRepositoryPublishError", + ErrorCategory.PermissionDenied, + this)); + + return; + } + _networkCredential = Utils.SetNetworkCredential(repository, _networkCredential, _cmdletPassedIn); // Check if dependencies already exist within the repo if: From ac9e6caab9dc4dce906f0840bb509742beb20b0c Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 29 Oct 2024 16:47:29 -0700 Subject: [PATCH 3/8] Fix expected prefix --- src/code/PSRepositoryInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/PSRepositoryInfo.cs b/src/code/PSRepositoryInfo.cs index cba9f81a4..b74d52cff 100644 --- a/src/code/PSRepositoryInfo.cs +++ b/src/code/PSRepositoryInfo.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerShell.PSResourceGet.UtilClasses public sealed class PSRepositoryInfo { #region constants - internal const string MARPrefix = "azure-powershell/"; + internal const string MARPrefix = "psresource/"; #endregion #region Enums From 8d4cb6dced4e1c887c33cc7b540b37800b2a92da Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 30 Oct 2024 10:29:33 -0700 Subject: [PATCH 4/8] Detect MAR endpoint as Container Registry repository --- src/code/RepositorySettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/code/RepositorySettings.cs b/src/code/RepositorySettings.cs index 97e9f80b7..d77ad9187 100644 --- a/src/code/RepositorySettings.cs +++ b/src/code/RepositorySettings.cs @@ -862,7 +862,7 @@ private static PSRepositoryInfo.APIVersion GetRepoAPIVersion(Uri repoUri) // repositories with Uri Scheme "temp" may have PSPath Uri's like: "Temp:\repo" and we should consider them as local repositories. return PSRepositoryInfo.APIVersion.Local; } - else if (repoUri.AbsoluteUri.EndsWith(".azurecr.io") || repoUri.AbsoluteUri.EndsWith(".azurecr.io/")) + else if (repoUri.AbsoluteUri.EndsWith(".azurecr.io") || repoUri.AbsoluteUri.EndsWith(".azurecr.io/") || repoUri.AbsoluteUri.Contains("mcr.microsoft.com") || repoUri.AbsoluteUri.Contains("mcr.microsoft.com/") { return PSRepositoryInfo.APIVersion.ContainerRegistry; } @@ -876,7 +876,7 @@ private static RepositoryProviderType GetRepositoryProviderType(Uri repoUri) { string absoluteUri = repoUri.AbsoluteUri; // We want to use contains instead of EndsWith to accomodate for trailing '/' - if (absoluteUri.Contains("azurecr.io")){ + if (absoluteUri.Contains("azurecr.io") || absoluteUri.Contains("mcr.microsoft.com")){ return RepositoryProviderType.ACR; } // TODO: add a regex for this match From a823cade837c707e8acbaafbe1af90519df91ef0 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 30 Oct 2024 10:34:30 -0700 Subject: [PATCH 5/8] Fix typo --- src/code/RepositorySettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/RepositorySettings.cs b/src/code/RepositorySettings.cs index d77ad9187..c37b19d0a 100644 --- a/src/code/RepositorySettings.cs +++ b/src/code/RepositorySettings.cs @@ -862,7 +862,7 @@ private static PSRepositoryInfo.APIVersion GetRepoAPIVersion(Uri repoUri) // repositories with Uri Scheme "temp" may have PSPath Uri's like: "Temp:\repo" and we should consider them as local repositories. return PSRepositoryInfo.APIVersion.Local; } - else if (repoUri.AbsoluteUri.EndsWith(".azurecr.io") || repoUri.AbsoluteUri.EndsWith(".azurecr.io/") || repoUri.AbsoluteUri.Contains("mcr.microsoft.com") || repoUri.AbsoluteUri.Contains("mcr.microsoft.com/") + else if (repoUri.AbsoluteUri.EndsWith(".azurecr.io") || repoUri.AbsoluteUri.EndsWith(".azurecr.io/") || repoUri.AbsoluteUri.Contains("mcr.microsoft.com") || repoUri.AbsoluteUri.Contains("mcr.microsoft.com/")) { return PSRepositoryInfo.APIVersion.ContainerRegistry; } From 352bf9e7f7ab7bab7102986dfbb76ae2b6bb060d Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 30 Oct 2024 12:02:29 -0700 Subject: [PATCH 6/8] Add tests --- src/code/ContainerRegistryServerAPICalls.cs | 4 ++- src/code/InternalHooks.cs | 3 +++ ...SResourceContainerRegistryServer.Tests.ps1 | 18 +++++++++++++ ...SResourceContainerRegistryServer.Tests.ps1 | 27 ++++++++++++++++++- ...SResourceContainerRegistryServer.Tests.ps1 | 21 +++++++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/code/ContainerRegistryServerAPICalls.cs b/src/code/ContainerRegistryServerAPICalls.cs index 39738f3a1..d32b2300f 100644 --- a/src/code/ContainerRegistryServerAPICalls.cs +++ b/src/code/ContainerRegistryServerAPICalls.cs @@ -1698,9 +1698,11 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp private string PrependMARPrefix(string packageName) { + string prefix = string.IsNullOrEmpty(InternalHooks.MARPrefix) ? PSRepositoryInfo.MARPrefix : InternalHooks.MARPrefix; + // If the repostitory is MAR and its not a wildcard search, we need to prefix the package name with MAR prefix. string updatedPackageName = Repository.IsMARRepository() && packageName.Trim() != "*" - ? string.Concat(PSRepositoryInfo.MARPrefix, packageName) + ? string.Concat(prefix, packageName) : packageName; return updatedPackageName; diff --git a/src/code/InternalHooks.cs b/src/code/InternalHooks.cs index 2078d1d41..20837b0ff 100644 --- a/src/code/InternalHooks.cs +++ b/src/code/InternalHooks.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Reflection; +using Azure.Core; namespace Microsoft.PowerShell.PSResourceGet.UtilClasses { @@ -15,6 +16,8 @@ public class InternalHooks internal static string AllowedUri; + internal static string MARPrefix; + public static void SetTestHook(string property, object value) { var fieldInfo = typeof(InternalHooks).GetField(property, BindingFlags.Static | BindingFlags.NonPublic); diff --git a/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 b/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 index 5de4d7c46..b38163578 100644 --- a/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 +++ b/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 @@ -228,3 +228,21 @@ Describe 'Test HTTP Find-PSResource for ACR Server Protocol' -tags 'CI' { $res.Type.ToString() | Should -Be "Script" } } + +Describe 'Test Find-PSResource for MAR Repository' -tags 'CI' { + BeforeAll { + [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::SetTestHook("MARPrefix", "azure-powershell/"); + Register-PSResourceRepository -Name "MAR" -Uri "https://mcr.microsoft.com" -ApiVersion "ContainerRegistry" + } + + AfterAll { + [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::SetTestHook("MARPrefix", $null); + Unregister-PSResourceRepository -Name "MAR" + } + + It "Should find resource given specific Name, Version null" { + $res = Find-PSResource -Name "Az.Accounts" -Repository "MAR" + $res.Name | Should -Be "Az.Accounts" + $res.Version | Should -Be "3.0.4" + } +} \ No newline at end of file diff --git a/test/InstallPSResourceTests/InstallPSResourceContainerRegistryServer.Tests.ps1 b/test/InstallPSResourceTests/InstallPSResourceContainerRegistryServer.Tests.ps1 index 835043da7..2ade007f2 100644 --- a/test/InstallPSResourceTests/InstallPSResourceContainerRegistryServer.Tests.ps1 +++ b/test/InstallPSResourceTests/InstallPSResourceContainerRegistryServer.Tests.ps1 @@ -137,7 +137,7 @@ Describe 'Test Install-PSResource for ACR scenarios' -tags 'CI' { It "Install resource with a dependency (should install both parent and dependency)" { Install-PSResource -Name $testModuleParentName -Repository $ACRRepoName -TrustRepository - + $parentPkg = Get-InstalledPSResource $testModuleParentName $parentPkg.Name | Should -Be $testModuleParentName $parentPkg.Version | Should -Be "1.0.0" @@ -307,3 +307,28 @@ Describe 'Test Install-PSResource for V3Server scenarios' -tags 'ManualValidatio Set-PSResourceRepository PoshTestGallery -Trusted } } + +Describe 'Test Install-PSResource for MAR Repository' -tags 'CI' { + BeforeAll { + [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::SetTestHook("MARPrefix", "azure-powershell/"); + Register-PSResourceRepository -Name "MAR" -Uri "https://mcr.microsoft.com" -ApiVersion "ContainerRegistry" + } + + AfterAll { + [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::SetTestHook("MARPrefix", $null); + Unregister-PSResourceRepository -Name "MAR" + } + + It "Should find resource given specific Name, Version null" { + try { + $pkg = Install-PSResource -Name "Az.Accounts" -Repository "MAR" -PassThru -TrustRepository -Reinstall + $pkg.Name | Should -Be "Az.Accounts" + $pkg.Version | Should -Be "3.0.4" + } + finally { + if ($pkg) { + Uninstall-PSResource -Name "Az.Accounts" -Version "3.0.4" + } + } + } +} diff --git a/test/PublishPSResourceTests/PublishPSResourceContainerRegistryServer.Tests.ps1 b/test/PublishPSResourceTests/PublishPSResourceContainerRegistryServer.Tests.ps1 index 6c4150dac..1df7916e9 100644 --- a/test/PublishPSResourceTests/PublishPSResourceContainerRegistryServer.Tests.ps1 +++ b/test/PublishPSResourceTests/PublishPSResourceContainerRegistryServer.Tests.ps1 @@ -512,3 +512,24 @@ Describe "Test Publish-PSResource" -tags 'CI' { $results[0].Version | Should -Be $version } } + +Describe 'Test Publish-PSResource for MAR Repository' -tags 'CI' { + BeforeAll { + [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::SetTestHook("MARPrefix", "azure-powershell/"); + Register-PSResourceRepository -Name "MAR" -Uri "https://mcr.microsoft.com" -ApiVersion "ContainerRegistry" + } + + AfterAll { + [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::SetTestHook("MARPrefix", $null); + Unregister-PSResourceRepository -Name "MAR" + } + + It "Should find resource given specific Name, Version null" { + $fileName = "NonExistent.psd1" + $modulePath = New-Item -Path "$TestDrive\NonExistent" -ItemType Directory -Force + $psd1Path = Join-Path -Path $modulePath -ChildPath $fileName + New-ModuleManifest -Path $psd1Path -ModuleVersion "1.0.0" -Description "NonExistent module" + + { Publish-PSResource -Path $modulePath -Repository "MAR" -ErrorAction Stop } | Should -Throw + } +} From 618aff6ba142db6bb9013e3b6e8ffd8b6b57e106 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 30 Oct 2024 12:19:16 -0700 Subject: [PATCH 7/8] fixes --- src/code/InternalHooks.cs | 1 - .../FindPSResourceContainerRegistryServer.Tests.ps1 | 2 +- .../PublishPSResourceContainerRegistryServer.Tests.ps1 | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/code/InternalHooks.cs b/src/code/InternalHooks.cs index 20837b0ff..0578485ca 100644 --- a/src/code/InternalHooks.cs +++ b/src/code/InternalHooks.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Reflection; -using Azure.Core; namespace Microsoft.PowerShell.PSResourceGet.UtilClasses { diff --git a/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 b/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 index b38163578..b15833980 100644 --- a/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 +++ b/test/FindPSResourceTests/FindPSResourceContainerRegistryServer.Tests.ps1 @@ -245,4 +245,4 @@ Describe 'Test Find-PSResource for MAR Repository' -tags 'CI' { $res.Name | Should -Be "Az.Accounts" $res.Version | Should -Be "3.0.4" } -} \ No newline at end of file +} diff --git a/test/PublishPSResourceTests/PublishPSResourceContainerRegistryServer.Tests.ps1 b/test/PublishPSResourceTests/PublishPSResourceContainerRegistryServer.Tests.ps1 index 1df7916e9..1426efe11 100644 --- a/test/PublishPSResourceTests/PublishPSResourceContainerRegistryServer.Tests.ps1 +++ b/test/PublishPSResourceTests/PublishPSResourceContainerRegistryServer.Tests.ps1 @@ -530,6 +530,6 @@ Describe 'Test Publish-PSResource for MAR Repository' -tags 'CI' { $psd1Path = Join-Path -Path $modulePath -ChildPath $fileName New-ModuleManifest -Path $psd1Path -ModuleVersion "1.0.0" -Description "NonExistent module" - { Publish-PSResource -Path $modulePath -Repository "MAR" -ErrorAction Stop } | Should -Throw + { Publish-PSResource -Path $modulePath -Repository "MAR" -ErrorAction Stop } | Should -Throw -ErrorId "MARRepositoryPublishError,Microsoft.PowerShell.PSResourceGet.Cmdlets.PublishPSResource" } } From f2b9c148503b5b69d25543b6871a1f327b4af489 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Wed, 30 Oct 2024 12:27:05 -0700 Subject: [PATCH 8/8] CR feedback --- src/code/RepositorySettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code/RepositorySettings.cs b/src/code/RepositorySettings.cs index c37b19d0a..e9f2693e2 100644 --- a/src/code/RepositorySettings.cs +++ b/src/code/RepositorySettings.cs @@ -862,7 +862,7 @@ private static PSRepositoryInfo.APIVersion GetRepoAPIVersion(Uri repoUri) // repositories with Uri Scheme "temp" may have PSPath Uri's like: "Temp:\repo" and we should consider them as local repositories. return PSRepositoryInfo.APIVersion.Local; } - else if (repoUri.AbsoluteUri.EndsWith(".azurecr.io") || repoUri.AbsoluteUri.EndsWith(".azurecr.io/") || repoUri.AbsoluteUri.Contains("mcr.microsoft.com") || repoUri.AbsoluteUri.Contains("mcr.microsoft.com/")) + else if (repoUri.AbsoluteUri.EndsWith(".azurecr.io") || repoUri.AbsoluteUri.EndsWith(".azurecr.io/") || repoUri.AbsoluteUri.Contains("mcr.microsoft.com")) { return PSRepositoryInfo.APIVersion.ContainerRegistry; }