Skip to content
89 changes: 87 additions & 2 deletions src/code/RegisterPSResourceRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,19 @@ class RegisterPSResourceRepository : PSCmdlet, IDynamicParameters
#region Members

private readonly string PSGalleryRepoName = "PSGallery";
private readonly string MicrosoftArtifactRegistryRepoName = "MicrosoftArtifactRegistry";
private readonly string MicrosoftArtifactRegistryRepoUri = "https://mcr.microsoft.com";
private readonly string PSGalleryRepoUri = "https://www.powershellgallery.com/api/v2";
private const int DefaultPriority = 50;
private const bool DefaultTrusted = false;
private const bool MARDefaultTrusted = true;
private const int MARDefaultPriority = 40;
private const string NameParameterSet = "NameParameterSet";
private const string PSGalleryParameterSet = "PSGalleryParameterSet";
private const string RepositoriesParameterSet = "RepositoriesParameterSet";
private Uri _uri;
private CredentialProviderDynamicParameters _credentialProvider;
private const string MARParameterSet = "MARParameterSet";

#endregion

Expand All @@ -62,6 +67,13 @@ class RegisterPSResourceRepository : PSCmdlet, IDynamicParameters
[Parameter(Mandatory = true, ParameterSetName = PSGalleryParameterSet, HelpMessage = "PSGallery switch used to indicate registering PSGallery repository.")]
public SwitchParameter PSGallery { get; set; }

/// <summary>
/// When specified, registers Microsoft Artifact Registry repository.
/// </summary>
[Parameter(Mandatory = true, ParameterSetName = MARParameterSet, HelpMessage = "Switch used to indicate registering Microsoft Artifact Registry repository.")]
[Alias("MAR")]
public SwitchParameter MicrosoftArtifactRegistry { get; set; }

/// <summary>
/// Specifies a hashtable of repositories and is used to register multiple repositories at once.
/// </summary>
Expand All @@ -74,6 +86,7 @@ class RegisterPSResourceRepository : PSCmdlet, IDynamicParameters
/// </summary>
[Parameter(ParameterSetName = NameParameterSet)]
[Parameter(ParameterSetName = PSGalleryParameterSet)]
[Parameter(ParameterSetName = MARParameterSet)]
public SwitchParameter Trusted { get; set; }

/// <summary>
Expand All @@ -84,8 +97,9 @@ class RegisterPSResourceRepository : PSCmdlet, IDynamicParameters
/// </summary>
[Parameter(ParameterSetName = NameParameterSet)]
[Parameter(ParameterSetName = PSGalleryParameterSet)]
[Parameter(ParameterSetName = MARParameterSet)]
[ValidateRange(0, 100)]
public int Priority { get; set; } = DefaultPriority;
public int Priority { get; set; }

/// <summary>
/// Specifies the Api version of the repository to be set.
Expand Down Expand Up @@ -122,6 +136,7 @@ public object GetDynamicParameters()
// It should also not appear when using the 'Repositories' parameter set.
if (ParameterSetName.Equals(PSGalleryParameterSet) ||
ParameterSetName.Equals(RepositoriesParameterSet) ||
ParameterSetName.Equals(MARParameterSet) ||
PSRepositoryInfo.IsValidContainerRegistryURL(Uri))
{
return null;
Expand Down Expand Up @@ -149,6 +164,18 @@ protected override void ProcessRecord()
repoApiVersion = ApiVersion;
}

if (!MyInvocation.BoundParameters.ContainsKey(nameof(Priority)))
{
if (ParameterSetName.Equals(MARParameterSet, StringComparison.OrdinalIgnoreCase))
{
Priority = MARDefaultPriority;
}
else
{
Priority = DefaultPriority;
}
}

PSRepositoryInfo.CredentialProviderType? credentialProvider = _credentialProvider?.CredentialProvider;

switch (ParameterSetName)
Expand Down Expand Up @@ -218,6 +245,25 @@ protected override void ProcessRecord()
}
break;

case MARParameterSet:
if (MicrosoftArtifactRegistry)
{
try
{
bool trustedValue = Trusted.IsPresent ? Trusted : MARDefaultTrusted;
items.Add(MicrosoftArtifactRegistryParameterSetHelper(Priority, trustedValue));
}
catch (Exception e)
{
ThrowTerminatingError(new ErrorRecord(
new PSInvalidOperationException(e.Message),
"ErrorInMARParameterSet",
ErrorCategory.InvalidArgument,
this));
}
}
break;

default:
Dbg.Assert(false, "Invalid parameter set");
break;
Expand Down Expand Up @@ -262,6 +308,34 @@ private PSRepositoryInfo PSGalleryParameterSetHelper(int repoPriority, bool repo
return addedRepo;
}

private PSRepositoryInfo MicrosoftArtifactRegistryParameterSetHelper(int repoPriority, bool repoTrusted)
{
WriteDebug("In RegisterPSResourceRepository::MicrosoftArtifactRegistryParameterSetHelper()");
Uri marUri = new Uri(MicrosoftArtifactRegistryRepoUri);
WriteDebug("Internal name and uri values for Microsoft Artifact Registry are hardcoded and validated. Priority and trusted values, if passed in, also validated");
var addedRepo = RepositorySettings.AddToRepositoryStore(MicrosoftArtifactRegistryRepoName,
marUri,
repoPriority,
repoTrusted,
apiVersion: null,
repoCredentialInfo: null,
credentialProvider: null,
Force,
this,
out string errorMsg);

if (!string.IsNullOrEmpty(errorMsg))
{
ThrowTerminatingError(new ErrorRecord(
new PSInvalidOperationException(errorMsg),
"RepositoryCredentialSecretManagementUnavailableModule",
ErrorCategory.ResourceUnavailable,
this));
}

return addedRepo;
}

private List<PSRepositoryInfo> RepositoriesParameterSetHelper()
{
WriteDebug("In RegisterPSResourceRepository::RepositoriesParameterSetHelper()");
Expand Down Expand Up @@ -323,7 +397,7 @@ private PSRepositoryInfo RepoValidationHelper(Hashtable repo)
return null;
}

if (repo["Name"].ToString().Equals("PSGallery"))
if (repo["Name"].ToString().Equals("PSGallery", StringComparison.OrdinalIgnoreCase))
{
WriteError(new ErrorRecord(
new PSInvalidOperationException("Cannot register PSGallery with -Name parameter. Try: Register-PSResourceRepository -PSGallery"),
Expand All @@ -334,6 +408,17 @@ private PSRepositoryInfo RepoValidationHelper(Hashtable repo)
return null;
}

if (repo["Name"].ToString().Equals("MAR", StringComparison.OrdinalIgnoreCase))
Comment thread
adityapatwardhan marked this conversation as resolved.
{
WriteError(new ErrorRecord(
new PSInvalidOperationException("Cannot register MAR with -Name parameter. The MAR repository is automatically registered. Try: Reset-PSResourceRepository to restore default repositories."),
"MARProvidedAsNameRepoPSet",
ErrorCategory.InvalidArgument,
this));

return null;
}

if (!repo.ContainsKey("Uri") || repo["Uri"] == null || String.IsNullOrEmpty(repo["Uri"].ToString()))
{
WriteError(new ErrorRecord(
Expand Down
32 changes: 32 additions & 0 deletions src/code/RepositorySettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ internal static class RepositorySettings
// The repository store file's location is currently only at '%LOCALAPPDATA%\PSResourceGet' for the user account.
private const string PSGalleryRepoName = "PSGallery";
private const string PSGalleryRepoUri = "https://www.powershellgallery.com/api/v2";
private const string MARRepoName = "MicrosoftArtifactRegistry";
private const string MARRepoUri = "https://mcr.microsoft.com";
private const int DefaultPriority = 50;
private const int MARDefaultPriority = 40;
private const bool DefaultTrusted = false;
private const bool MARDefaultTrusted = true;
private const string RepositoryFileName = "PSResourceRepository.xml";
private static readonly string RepositoryPath = Path.Combine(Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData), "PSResourceGet");
Expand Down Expand Up @@ -63,6 +67,10 @@ public static void CheckRepositoryStore()
// Add PSGallery to the newly created store
Uri psGalleryUri = new Uri(PSGalleryRepoUri);
Add(PSGalleryRepoName, psGalleryUri, DefaultPriority, DefaultTrusted, repoCredentialInfo: null, repoCredentialProvider: CredentialProviderType.None, APIVersion.V2, force: false);

// Add MAR to the newly created store
Uri marUri = new Uri(MARRepoUri);
Add(MARRepoName, marUri, MARDefaultPriority, MARDefaultTrusted, repoCredentialInfo: null, repoCredentialProvider: CredentialProviderType.None, APIVersion.ContainerRegistry, force: false);
}

// Open file (which should exist now), if cannot/is corrupted then throw error
Expand All @@ -85,6 +93,12 @@ public static PSRepositoryInfo AddRepository(string repoName, Uri repoUri, int r
return null;
}

if (repoName.Equals("MicrosoftArtifactRegistry", StringComparison.OrdinalIgnoreCase))
{
errorMsg = "Cannot register MAR with -Name parameter. Try: Register-PSResourceRepository -MicrosoftArtifactRegistry.";
return null;
}

return AddToRepositoryStore(repoName, repoUri, repoPriority, repoTrusted, apiVersion, repoCredentialInfo, repoCredentialProvider, force, cmdletPassedIn, out errorMsg);
}

Expand Down Expand Up @@ -187,6 +201,20 @@ public static PSRepositoryInfo UpdateRepositoryStore(string repoName, Uri repoUr
return null;
}

// check MAR Uri is not trying to be set
if (repoName.Equals("MicrosoftArtifactRegistry", StringComparison.OrdinalIgnoreCase) && repoUri != null)
{
errorMsg = "The MAR repository has a predefined Uri. Setting the -Uri parameter for this repository is not allowed. Please run 'Reset-PSResourceRepository' to restore default repositories.";
return null;
}

// check MAR CredentialInfo is not trying to be set
if (repoName.Equals("MicrosoftArtifactRegistry", StringComparison.OrdinalIgnoreCase) && repoCredentialInfo != null)
{
errorMsg = "Setting the -CredentialInfo parameter for MAR is not allowed. Run 'Reset-PSResourceRepository' to restore default repositories.";
return null;
}

// determine trusted value to pass in (true/false if set, null otherwise, hence the nullable bool variable)
bool? _trustedNullable = isSet ? new bool?(repoTrusted) : new bool?();

Expand Down Expand Up @@ -908,6 +936,10 @@ public static PSRepositoryInfo Reset(out string errorMsg)
Uri psGalleryUri = new Uri(PSGalleryRepoUri);
PSRepositoryInfo psGalleryRepo = Add(PSGalleryRepoName, psGalleryUri, DefaultPriority, DefaultTrusted, repoCredentialInfo: null, repoCredentialProvider: CredentialProviderType.None, APIVersion.V2, force: false);

// Add MAR to the newly created store
Uri marUri = new Uri(MARRepoUri);
Add(MARRepoName, marUri, MARDefaultPriority, MARDefaultTrusted, repoCredentialInfo: null, repoCredentialProvider: CredentialProviderType.None, APIVersion.ContainerRegistry, force: false);

// Clean up backup file on success
if (!string.IsNullOrEmpty(backupFilePath) && File.Exists(backupFilePath))
{
Expand Down
8 changes: 4 additions & 4 deletions src/code/ResetPSResourceRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell.PSResourceGet.Cmdlets
/// <summary>
/// The Reset-PSResourceRepository cmdlet resets the repository store by creating a new PSRepositories.xml file.
/// This is useful when the repository store becomes corrupted.
/// It will create a new repository store with only the PSGallery repository registered.
/// It will create a new repository store with PSGallery and MAR repositories registered.
/// </summary>
[Cmdlet(VerbsCommon.Reset,
"PSResourceRepository",
Expand Down Expand Up @@ -39,8 +39,8 @@ protected override void ProcessRecord()
"PSResourceRepository.xml");

WriteVerbose($"Resetting repository store at: {repositoryStorePath}");
if (!ShouldProcess(repositoryStorePath, "Reset repository store and create new PSRepositories.xml file with PSGallery registered"))

if (!ShouldProcess(repositoryStorePath, "Reset repository store and create new PSRepositories.xml file with PSGallery and MAR registered"))
{
return;
}
Expand All @@ -57,7 +57,7 @@ protected override void ProcessRecord()
return;
}

WriteVerbose("Repository store reset successfully. PSGallery has been registered.");
WriteVerbose("Repository store reset successfully. PSGallery and MAR have been registered.");

if (PassThru)
{
Expand Down
3 changes: 2 additions & 1 deletion src/code/SetPSResourceRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,11 @@ public SwitchParameter Trusted
public object GetDynamicParameters()
{
PSRepositoryInfo repository = RepositorySettings.Read(new[] { Name }, out string[] _).FirstOrDefault();
// Dynamic parameter '-CredentialProvider' should not appear for PSGallery, or any container registry repository.
// Dynamic parameter '-CredentialProvider' should not appear for PSGallery, MAR, or any container registry repository.
// It should also not appear when using the 'Repositories' parameter set.
if (repository is not null &&
(repository.Name.Equals("PSGallery", StringComparison.OrdinalIgnoreCase) ||
repository.Name.Equals("MicrosoftArtifactRegistry", StringComparison.OrdinalIgnoreCase) ||
ParameterSetName.Equals(RepositoriesParameterSet) ||
repository.IsContainerRegistry()))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Import-Module $modPath -Force -Verbose
Describe 'Test HTTP Find-PSResource for ACR Server Protocol' -tags 'CI' {

BeforeAll {
$MARName = Get-MarName
$testModuleName = "test-module"
$testModuleWith2DigitVersion = "test-2DigitPkg"
$testModuleParentName = "test_parent_mod"
Expand Down Expand Up @@ -273,50 +274,51 @@ Describe 'Test HTTP Find-PSResource for ACR Server Protocol' -tags 'CI' {
}

Describe 'Test Find-PSResource for MAR Repository' -tags 'CI' {

BeforeAll {
Register-PSResourceRepository -Name "MAR" -Uri "https://mcr.microsoft.com" -ApiVersion "ContainerRegistry"
Get-NewPSResourceRepositoryFile
}

AfterAll {
Unregister-PSResourceRepository -Name "MAR"
Get-RevertPSResourceRepositoryFile
}

It "Should find resource given specific Name, Version null" {
$res = Find-PSResource -Name "Az.Accounts" -Repository "MAR"
$res = Find-PSResource -Name "Az.Accounts" -Repository $MarName
$res.Name | Should -Be "Az.Accounts"
$res.Version | Should -BeGreaterThan ([Version]"4.0.0")
}

It "Should find resource and its dependency given specific Name and Version" {
$res = Find-PSResource -Name "Az.Storage" -Version "8.0.0" -Repository "MAR"
$res = Find-PSResource -Name "Az.Storage" -Version "8.0.0" -Repository $MarName
$res.Dependencies.Length | Should -Be 1
$res.Dependencies[0].Name | Should -Be "Az.Accounts"
}

It "Should find Azpreview resource and it's dependency given specific Name and Version" {
$res = Find-PSResource -Name "Azpreview" -Version "13.2.0" -Repository "MAR"
$res = Find-PSResource -Name "Azpreview" -Version "13.2.0" -Repository $MarName
$res.Dependencies.Length | Should -Not -Be 0
}

It "Should find resource with wildcard in Name" {
$res = Find-PSResource -Name "Az.App*" -Repository "MAR"
$res = Find-PSResource -Name "Az.App*" -Repository $MarName
$res | Should -Not -BeNullOrEmpty
$res.Count | Should -BeGreaterThan 1
}

It "Should find all resource with wildcard in Name" {
$res = Find-PSResource -Name "*" -Repository "MAR"
$res = Find-PSResource -Name "*" -Repository $MarName
$res | Should -Not -BeNullOrEmpty
$res.Count | Should -BeGreaterThan 1
}

It "Should find version range for Az dependencies" {
# Target known version to know the output from the API won't change
$res = Find-PSResource -Repository 'MAR' -Name 'Az' -Version '14.4.0'
$res = Find-PSResource -Repository $MarName -Name 'Az' -Version '14.4.0'

# Version defined by "ModuleVersion"
$res.Dependencies.Where{$_.'Name' -eq 'Az.Accounts'}.'VersionRange'.ToString() | Should -Be '[5.3.0, )'

# Version defined by "RequiredVersion"
$res.Dependencies.Where{$_.'Name' -eq 'Az.Resources'}.'VersionRange'.ToString() | Should -Be '[8.1.0, 8.1.0]'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,25 +351,24 @@ Describe 'Test Install-PSResource for Container Registry scenarios - Manual Vali
}

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"
Get-NewPSResourceRepositoryFile
}

AfterAll {
[Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::SetTestHook("MARPrefix", $null);
Unregister-PSResourceRepository -Name "MAR"
Get-RevertPSResourceRepositoryFile
}

It "Should find resource given specific Name, Version null" {
It "Should install resource given specific Name, Version null" {
try {
$pkg = Install-PSResource -Name "Az.Accounts" -Repository "MAR" -PassThru -TrustRepository -Reinstall
$pkg = Install-PSResource -Name "Az.Accounts" -Repository 'MicrosoftArtifactRegistry' -PassThru -TrustRepository -Reinstall
$pkg.Name | Should -Be "Az.Accounts"
$pkg.Version | Should -Be "3.0.4"
$pkg.Version.Major | Should -BeGreaterOrEqual 5

}
finally {
if ($pkg) {
Uninstall-PSResource -Name "Az.Accounts" -Version "3.0.4"
Uninstall-PSResource -Name "Az.Accounts" -Version $pkg.Version
}
}
}
Expand Down
Loading
Loading