Skip to content

Add support for ACR when publishing scripts #1573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 74 additions & 56 deletions src/code/ACRServerAPICalls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public override FindResults FindName(string packageName, bool includePrerelease,
_cmdletPassedIn.WriteDebug("In ACRServerAPICalls::FindName()");
string accessToken = string.Empty;
string tenantID = string.Empty;
string packageNameLowercase = packageName.ToLower();

// Need to set up secret management vault before hand
var repositoryCredentialInfo = Repository.CredentialInfo;
Expand Down Expand Up @@ -167,7 +168,7 @@ public override FindResults FindName(string packageName, bool includePrerelease,
}

_cmdletPassedIn.WriteVerbose("Getting tags");
var foundTags = FindAcrImageTags(registry, packageName, "*", acrAccessToken, out errRecord);
var foundTags = FindAcrImageTags(registry, packageNameLowercase, "*", acrAccessToken, out errRecord);
if (errRecord != null || foundTags == null)
{
return new FindResults(stringResponse: new string[] { }, hashtableResponse: emptyHashResponses, responseType: acrFindResponseType);
Expand All @@ -193,20 +194,29 @@ public override FindResults FindName(string packageName, bool includePrerelease,
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: acrFindResponseType);
}

if (NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion))
if (!NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion))
{
_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");
if (!pkgVersion.IsPrerelease || includePrerelease)
{
// Versions are always in descending order i.e 5.0.0, 3.0.0, 1.0.0 so grabbing the first match suffices
latestVersionResponse.Add(GetACRMetadata(registry, packageName, pkgVersion, acrAccessToken, out errRecord));
if (errRecord != null)
{
return new FindResults(stringResponse: new string[] { }, hashtableResponse: latestVersionResponse.ToArray(), responseType: acrFindResponseType);
}
errRecord = new ErrorRecord(
new ArgumentException($"Version {pkgVersionElement.ToString()} to be parsed from metadata is not a valid NuGet version."),
"FindNameFailure",
ErrorCategory.InvalidArgument,
this);

break;
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: acrFindResponseType);
}

_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");
if (!pkgVersion.IsPrerelease || includePrerelease)
{
// TODO: ensure versions are in order, fix bug https://github.com/PowerShell/PSResourceGet/issues/1581
Hashtable metadata = GetACRMetadata(registry, packageNameLowercase, pkgVersion, acrAccessToken, out errRecord);
if (errRecord != null || metadata.Count == 0)
{
return new FindResults(stringResponse: new string[] { }, hashtableResponse: emptyHashResponses, responseType: acrFindResponseType);
}

latestVersionResponse.Add(metadata);
break;
}
}
}
Expand Down Expand Up @@ -285,6 +295,7 @@ public override FindResults FindVersionGlobbing(string packageName, VersionRange
_cmdletPassedIn.WriteDebug("In ACRServerAPICalls::FindVersionGlobbing()");
string accessToken = string.Empty;
string tenantID = string.Empty;
string packageNameLowercase = packageName.ToLower();

// Need to set up secret management vault beforehand
var repositoryCredentialInfo = Repository.CredentialInfo;
Expand Down Expand Up @@ -318,7 +329,7 @@ public override FindResults FindVersionGlobbing(string packageName, VersionRange
}

_cmdletPassedIn.WriteVerbose("Getting tags");
var foundTags = FindAcrImageTags(registry, packageName, "*", acrAccessToken, out errRecord);
var foundTags = FindAcrImageTags(registry, packageNameLowercase, "*", acrAccessToken, out errRecord);
if (errRecord != null || foundTags == null)
{
return new FindResults(stringResponse: new string[] { }, hashtableResponse: emptyHashResponses, responseType: acrFindResponseType);
Expand Down Expand Up @@ -355,7 +366,7 @@ public override FindResults FindVersionGlobbing(string packageName, VersionRange
continue;
}

latestVersionResponse.Add(GetACRMetadata(registry, packageName, pkgVersion, acrAccessToken, out errRecord));
latestVersionResponse.Add(GetACRMetadata(registry, packageNameLowercase, pkgVersion, acrAccessToken, out errRecord));
if (errRecord != null)
{
return new FindResults(stringResponse: new string[] { }, hashtableResponse: latestVersionResponse.ToArray(), responseType: acrFindResponseType);
Expand Down Expand Up @@ -706,7 +717,7 @@ internal Hashtable GetACRMetadata(string registry, string packageName, NuGetVers
if (exception != null)
{
errRecord = new ErrorRecord(exception, "FindNameFailure", ErrorCategory.InvalidResult, this);

return requiredVersionResponse;
}

Expand All @@ -727,14 +738,22 @@ internal Hashtable GetACRMetadata(string registry, string packageName, NuGetVers
return requiredVersionResponse;
}

if (NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion))
if (!NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion))
{
_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");
errRecord = new ErrorRecord(
new ArgumentException($"Version {pkgVersionElement.ToString()} to be parsed from metadata is not a valid NuGet version."),
"FindNameFailure",
ErrorCategory.InvalidArgument,
this);

if (pkgVersion == requiredVersion)
{
requiredVersionResponse.Add(metadataPkgName, metadata);
}
return requiredVersionResponse;
}

_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");

if (pkgVersion == requiredVersion)
{
requiredVersionResponse.Add(metadataPkgName, metadata);
}
}

Expand Down Expand Up @@ -1102,7 +1121,7 @@ private static Collection<KeyValuePair<string, string>> GetDefaultHeaders(string
};
}

internal bool PushNupkgACR(string psd1OrPs1File, string outputNupkgDir, string pkgName, NuGetVersion pkgVersion, PSRepositoryInfo repository, Hashtable parsedMetadataHash, out ErrorRecord errRecord)
internal bool PushNupkgACR(string psd1OrPs1File, string outputNupkgDir, string pkgName, NuGetVersion pkgVersion, PSRepositoryInfo repository, ResourceType resourceType, Hashtable parsedMetadataHash, out ErrorRecord errRecord)
{
errRecord = null;
// Push the nupkg to the appropriate repository
Expand Down Expand Up @@ -1188,7 +1207,7 @@ internal bool PushNupkgACR(string psd1OrPs1File, string outputNupkgDir, string p

/* Create manifest layer */
_cmdletPassedIn.WriteVerbose("Create package version metadata as JSON string");
string jsonString = CreateMetadataContent(psd1OrPs1File, parsedMetadataHash, out ErrorRecord metadataCreationError);
string jsonString = CreateMetadataContent(psd1OrPs1File, resourceType, parsedMetadataHash, out ErrorRecord metadataCreationError);
if (metadataCreationError != null)
{
_cmdletPassedIn.ThrowTerminatingError(metadataCreationError);
Expand All @@ -1197,7 +1216,7 @@ internal bool PushNupkgACR(string psd1OrPs1File, string outputNupkgDir, string p
FileInfo nupkgFile = new FileInfo(fullNupkgFile);
var fileSize = nupkgFile.Length;
var fileName = System.IO.Path.GetFileName(fullNupkgFile);
string fileContent = CreateJsonContent(nupkgDigest, configDigest, fileSize, fileName, pkgName, jsonString);
string fileContent = CreateJsonContent(nupkgDigest, configDigest, fileSize, fileName, pkgName, resourceType, jsonString);
File.WriteAllText(configFilePath, fileContent);

_cmdletPassedIn.WriteVerbose("Create the manifest layer");
Expand All @@ -1207,6 +1226,7 @@ internal bool PushNupkgACR(string psd1OrPs1File, string outputNupkgDir, string p
{
return true;
}

return false;
}

Expand All @@ -1216,6 +1236,7 @@ private string CreateJsonContent(
long nupkgFileSize,
string fileName,
string packageName,
ResourceType resourceType,
string jsonString)
{
StringBuilder stringBuilder = new StringBuilder();
Expand All @@ -1224,6 +1245,7 @@ private string CreateJsonContent(

jsonWriter.Formatting = Newtonsoft.Json.Formatting.Indented;

// start of manifest JSON object
jsonWriter.WriteStartObject();

jsonWriter.WritePropertyName("schemaVersion");
Expand Down Expand Up @@ -1257,17 +1279,18 @@ private string CreateJsonContent(
jsonWriter.WriteValue(fileName);
jsonWriter.WritePropertyName("metadata");
jsonWriter.WriteValue(jsonString);
jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
jsonWriter.WritePropertyName("artifactType");
jsonWriter.WriteValue(resourceType.ToString());
jsonWriter.WriteEndObject(); // end of annotations object

jsonWriter.WriteEndArray();
jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject(); // end of 'layers' entry object

jsonWriter.WriteEndArray(); // end of 'layers' array
jsonWriter.WriteEndObject(); // end of manifest JSON object

return stringWriter.ToString();
}



// ACR method
private bool CreateDigest(string fileName, out string digest, out ErrorRecord error)
{
Expand Down Expand Up @@ -1310,45 +1333,40 @@ private bool CreateDigest(string fileName, out string digest, out ErrorRecord er
return true;
}

private string CreateMetadataContent(string manifestFilePath, Hashtable parsedMetadata, out ErrorRecord metadataCreationError)
private string CreateMetadataContent(string manifestFilePath, ResourceType resourceType, Hashtable parsedMetadata, out ErrorRecord metadataCreationError)
{
metadataCreationError = null;
Hashtable parsedMetadataHash = null;
string jsonString = string.Empty;

// A script will already have the metadata parsed into the parsedMetadatahash,
// a module will still need the module manifest to be parsed.
if (parsedMetadata == null || parsedMetadata.Count == 0)
{
// Use the parsed module manifest data as 'parsedMetadataHash' instead of the passed-in data.
if (!Utils.TryReadManifestFile(
manifestFilePath: manifestFilePath,
manifestInfo: out parsedMetadataHash,
error: out Exception manifestReadError))
{
metadataCreationError = new ErrorRecord(
manifestReadError,
"ManifestFileReadParseForACRPublishError",
ErrorCategory.ReadError,
_cmdletPassedIn);

return jsonString;
}
}

if (parsedMetadataHash == null)
{
metadataCreationError = new ErrorRecord(
new InvalidOperationException("Error parsing package metadata into hashtable."),
"PackageMetadataHashEmptyError",
ErrorCategory.InvalidData,
new ArgumentException("Hashtable created from .ps1 or .psd1 containing package metadata was null or empty"),
"MetadataHashtableEmptyError",
ErrorCategory.InvalidArgument,
_cmdletPassedIn);

return jsonString;
}

_cmdletPassedIn.WriteVerbose("Serialize JSON into string.");
jsonString = System.Text.Json.JsonSerializer.Serialize(parsedMetadataHash);

if (parsedMetadata.ContainsKey("Version") && parsedMetadata["Version"] is NuGetVersion pkgNuGetVersion)
{
// do not serialize NuGetVersion, this will populate more metadata than is needed and makes it harder to deserialize later
parsedMetadata.Remove("Version");
parsedMetadata["Version"] = pkgNuGetVersion.ToString();
}

try
{
jsonString = System.Text.Json.JsonSerializer.Serialize(parsedMetadata);
}
catch (Exception ex)
{
metadataCreationError = new ErrorRecord(ex, "JsonSerializationError", ErrorCategory.InvalidResult, _cmdletPassedIn);
return jsonString;
}

return jsonString;
}
Expand Down
24 changes: 17 additions & 7 deletions src/code/PSResourceInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ public static bool TryConvertFromACRJson(

if (packageMetadata == null)
{
errorMsg = "TryConvertJsonToPSResourceInfo: Invalid json object. Object cannot be null.";
errorMsg = "TryConvertFromACRJson: Invalid json object. Object cannot be null.";
return false;
}

Expand All @@ -829,24 +829,34 @@ public static bool TryConvertFromACRJson(
JsonElement rootDom = packageMetadata.RootElement;

// Version
if (rootDom.TryGetProperty("ModuleVersion", out JsonElement versionElement))
if (rootDom.TryGetProperty("ModuleVersion", out JsonElement versionElement) || rootDom.TryGetProperty("Version", out versionElement))
{
string versionValue = versionElement.ToString();
metadata["Version"] = ParseHttpVersion(versionValue, out string prereleaseLabel);

Version pkgVersion = ParseHttpVersion(versionValue, out string prereleaseLabel);
metadata["Version"] = pkgVersion;
metadata["Prerelease"] = prereleaseLabel;
metadata["IsPrerelease"] = !String.IsNullOrEmpty(prereleaseLabel);

if (!NuGetVersion.TryParse(versionValue, out NuGetVersion parsedNormalizedVersion))
if (!NuGetVersion.TryParse(versionValue, out NuGetVersion parsedNormalizedVersion) && pkgVersion == null)
{
errorMsg = string.Format(
CultureInfo.InvariantCulture,
@"TryReadPSGetInfo: Cannot parse NormalizedVersion");
@"TryConvertFromACRJson: Cannot parse NormalizedVersion or System.Version from version in metadata.");

parsedNormalizedVersion = new NuGetVersion("1.0.0.0");
return false;
}

metadata["NormalizedVersion"] = parsedNormalizedVersion;
}
else
{
errorMsg = string.Format(
CultureInfo.InvariantCulture,
@"TryConvertFromACRJson: Neither 'ModuleVersion' nor 'Version' could be found in package metadata");

return false;
}

// License Url
if (rootDom.TryGetProperty("LicenseUrl", out JsonElement licenseUrlElement))
Expand Down Expand Up @@ -972,7 +982,7 @@ public static bool TryConvertFromACRJson(
{
errorMsg = string.Format(
CultureInfo.InvariantCulture,
@"TryConvertFromJson: Cannot parse PSResourceInfo from json object with error: {0}",
@"TryConvertFromACRJson: Cannot parse PSResourceInfo from json object with error: {0}",
ex.Message);

return false;
Expand Down
Loading