diff --git a/src/code/ContainerRegistryServerAPICalls.cs b/src/code/ContainerRegistryServerAPICalls.cs
index 785c7aeae..c30dc391f 100644
--- a/src/code/ContainerRegistryServerAPICalls.cs
+++ b/src/code/ContainerRegistryServerAPICalls.cs
@@ -47,6 +47,10 @@ internal class ContainerRegistryServerAPICalls : ServerApiCall
const string containerRegistryStartUploadTemplate = "https://{0}/v2/{1}/blobs/uploads/"; // 0 - registry, 1 - packagename
const string containerRegistryEndUploadTemplate = "https://{0}{1}&digest=sha256:{2}"; // 0 - registry, 1 - location, 2 - digest
const string defaultScope = "&scope=repository:*:*&scope=registry:catalog:*";
+ const string catalogScope = "&scope=registry:catalog:*";
+ const string grantTypeTemplate = "grant_type=access_token&service={0}{1}"; // 0 - registry, 1 - scope
+ const string authUrlTemplate = "{0}?service={1}{2}"; // 0 - realm, 1 - service, 2 - scope
+
const string containerRegistryRepositoryListTemplate = "https://{0}/v2/_catalog"; // 0 - registry
#endregion
@@ -323,7 +327,7 @@ private Stream InstallVersion(
return null;
}
- string containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, out errRecord);
if (errRecord != null)
{
return null;
@@ -371,7 +375,7 @@ private Stream InstallVersion(
/// If no credential provided at registration then, check if the ACR endpoint can be accessed without a token. If not, try using Azure.Identity to get the az access token, then ACR refresh token and then ACR access token.
/// Note: Access token can be empty if the repository is unauthenticated
///
- internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
+ internal string GetContainerRegistryAccessToken(bool needCatalogAccess, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetContainerRegistryAccessToken()");
string accessToken = string.Empty;
@@ -393,7 +397,7 @@ internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
}
else
{
- bool isRepositoryUnauthenticated = IsContainerRegistryUnauthenticated(Repository.Uri.ToString(), out errRecord, out accessToken);
+ bool isRepositoryUnauthenticated = IsContainerRegistryUnauthenticated(Repository.Uri.ToString(), needCatalogAccess, out errRecord, out accessToken);
if (errRecord != null)
{
return null;
@@ -444,7 +448,7 @@ internal string GetContainerRegistryAccessToken(out ErrorRecord errRecord)
///
/// Checks if container registry repository is unauthenticated.
///
- internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, out ErrorRecord errRecord, out string anonymousAccessToken)
+ internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, bool needCatalogAccess, out ErrorRecord errRecord, out string anonymousAccessToken)
{
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::IsContainerRegistryUnauthenticated()");
errRecord = null;
@@ -482,18 +486,22 @@ internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, out
return false;
}
- string content = "grant_type=access_token&service=" + service + defaultScope;
+ string content = needCatalogAccess ? String.Format(grantTypeTemplate, service, catalogScope) : String.Format(grantTypeTemplate, service, defaultScope);
+
var contentHeaders = new Collection> { new KeyValuePair("Content-Type", "application/x-www-form-urlencoded") };
- // get the anonymous access token
- var url = $"{realm}?service={service}{defaultScope}";
+ string url = needCatalogAccess ? String.Format(authUrlTemplate, realm, service, catalogScope) : String.Format(authUrlTemplate, realm, service, defaultScope);
// we dont check the errorrecord here because we want to return false if we get a 401 and not throw an error
- var results = GetHttpResponseJObjectUsingContentHeaders(url, HttpMethod.Get, content, contentHeaders, out _);
+ _cmdletPassedIn.WriteDebug($"Getting anonymous access token from the realm: {url}");
+ ErrorRecord errRecordTemp = null;
+
+ var results = GetHttpResponseJObjectUsingContentHeaders(url, HttpMethod.Get, content, contentHeaders, out errRecordTemp);
if (results == null)
{
_cmdletPassedIn.WriteDebug("Failed to get access token from the realm. results is null.");
+ _cmdletPassedIn.WriteDebug($"ErrorRecord: {errRecordTemp}");
return false;
}
@@ -504,7 +512,6 @@ internal bool IsContainerRegistryUnauthenticated(string containerRegistyUrl, out
}
anonymousAccessToken = results["access_token"].ToString();
- _cmdletPassedIn.WriteDebug("Anonymous access token retrieved");
return true;
}
}
@@ -757,7 +764,7 @@ internal Hashtable GetContainerRegistryMetadata(string packageName, string exact
if (!NuGetVersion.TryParse(pkgVersionString, out NuGetVersion pkgVersion))
{
errRecord = new ErrorRecord(
- new ArgumentException($"Version {pkgVersionString} to be parsed from metadata is not a valid NuGet version."),
+ new ArgumentException($"Version {pkgVersionString} to be parsed from metadata is not a valid NuGet version for package '{packageName}'."),
"ParseMetadataFailure",
ErrorCategory.InvalidArgument,
this);
@@ -984,24 +991,29 @@ internal JObject GetHttpResponseJObjectUsingContentHeaders(string url, HttpMetho
{
HttpRequestMessage request = new HttpRequestMessage(method, url);
- if (string.IsNullOrEmpty(content))
+ // HTTP GET does not expect a body / content.
+ if (method != HttpMethod.Get)
{
- errRecord = new ErrorRecord(
- exception: new ArgumentNullException($"Content is null or empty and cannot be used to make a request as its content headers."),
- "RequestContentHeadersNullOrEmpty",
- ErrorCategory.InvalidData,
- _cmdletPassedIn);
- return null;
- }
+ if (string.IsNullOrEmpty(content))
+ {
+ errRecord = new ErrorRecord(
+ exception: new ArgumentNullException($"Content is null or empty and cannot be used to make a request as its content headers."),
+ "RequestContentHeadersNullOrEmpty",
+ ErrorCategory.InvalidData,
+ _cmdletPassedIn);
- request.Content = new StringContent(content);
- request.Content.Headers.Clear();
- if (contentHeaders != null)
- {
- foreach (var header in contentHeaders)
+ return null;
+ }
+
+ request.Content = new StringContent(content);
+ request.Content.Headers.Clear();
+ if (contentHeaders != null)
{
- request.Content.Headers.Add(header.Key, header.Value);
+ foreach (var header in contentHeaders)
+ {
+ request.Content.Headers.Add(header.Key, header.Value);
+ }
}
}
@@ -1230,7 +1242,7 @@ internal bool PushNupkgContainerRegistry(
// Get access token (includes refresh tokens)
_cmdletPassedIn.WriteVerbose($"Get access token for container registry server.");
- var containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
+ var containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, out errRecord);
if (errRecord != null)
{
return false;
@@ -1695,7 +1707,7 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
string packageNameLowercase = packageName.ToLower();
string packageNameForFind = PrependMARPrefix(packageNameLowercase);
- string containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, out errRecord);
if (errRecord != null)
{
return emptyHashResponses;
@@ -1711,8 +1723,9 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
List allVersionsList = foundTags["tags"].ToList();
SortedDictionary sortedQualifyingPkgs = GetPackagesWithRequiredVersion(allVersionsList, versionType, versionRange, requiredVersion, packageNameForFind, includePrerelease, out errRecord);
- if (errRecord != null)
+ if (errRecord != null && sortedQualifyingPkgs?.Count == 0)
{
+ _cmdletPassedIn.WriteDebug("No qualifying packages found for the specified criteria.");
return emptyHashResponses;
}
@@ -1756,12 +1769,14 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
if (!NuGetVersion.TryParse(pkgVersionString, out NuGetVersion pkgVersion))
{
errRecord = new ErrorRecord(
- new ArgumentException($"Version {pkgVersionString} to be parsed from metadata is not a valid NuGet version."),
+ new ArgumentException($"Version {pkgVersionString} to be parsed from metadata is not a valid NuGet version for package '{packageName}'."),
"FindNameFailure",
ErrorCategory.InvalidArgument,
this);
- return null;
+ _cmdletPassedIn.WriteError(errRecord);
+ _cmdletPassedIn.WriteDebug($"Skipping package '{packageName}' with version '{pkgVersionString}' as it is not a valid NuGet version.");
+ continue; // skip this version and continue with the next one
}
_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");
@@ -1804,7 +1819,7 @@ private FindResults FindPackages(string packageName, bool includePrerelease, out
{
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::FindPackages()");
errRecord = null;
- string containerRegistryAccessToken = GetContainerRegistryAccessToken(out errRecord);
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: true, out errRecord);
if (errRecord != null)
{
return emptyResponseResults;
diff --git a/src/code/PSRepositoryInfo.cs b/src/code/PSRepositoryInfo.cs
index b74d52cff..07dd06762 100644
--- a/src/code/PSRepositoryInfo.cs
+++ b/src/code/PSRepositoryInfo.cs
@@ -104,7 +104,7 @@ public enum RepositoryProviderType
internal bool IsMARRepository()
{
- return (ApiVersion == APIVersion.ContainerRegistry && Uri.Host.Contains("mcr.microsoft.com"));
+ return (ApiVersion == APIVersion.ContainerRegistry && Uri.Host.StartsWith("mcr.microsoft") );
}
#endregion