Skip to content

Commit 50ebac2

Browse files
authored
Add support for publishing .nupkg file to ACR (#1763)
1 parent c796914 commit 50ebac2

8 files changed

+338
-18
lines changed

src/code/ContainerRegistryServerAPICalls.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -649,9 +649,9 @@ internal Hashtable GetContainerRegistryMetadata(string packageName, string exact
649649
pkgVersionString += $"-{pkgPrereleaseLabelElement.ToString()}";
650650
}
651651
}
652-
else if (rootDom.TryGetProperty("Version", out pkgVersionElement))
652+
else if (rootDom.TryGetProperty("Version", out pkgVersionElement) || rootDom.TryGetProperty("version", out pkgVersionElement))
653653
{
654-
// script metadata will have "Version" property
654+
// script metadata will have "Version" property, but nupkg only based .nuspec will have lowercase "version" property and JsonElement.TryGetProperty() is case sensitive
655655
pkgVersionString = pkgVersionElement.ToString();
656656
}
657657
else
@@ -1115,23 +1115,26 @@ private static Collection<KeyValuePair<string, string>> GetDefaultHeaders(string
11151115
#endregion
11161116

11171117
#region Publish Methods
1118-
11191118
/// <summary>
11201119
/// Helper method that publishes a package to the container registry.
11211120
/// This gets called from Publish-PSResource.
11221121
/// </summary>
1123-
internal bool PushNupkgContainerRegistry(string psd1OrPs1File,
1122+
internal bool PushNupkgContainerRegistry(
11241123
string outputNupkgDir,
11251124
string packageName,
11261125
string modulePrefix,
11271126
NuGetVersion packageVersion,
11281127
ResourceType resourceType,
11291128
Hashtable parsedMetadataHash,
11301129
Hashtable dependencies,
1130+
bool isNupkgPathSpecified,
1131+
string originalNupkgPath,
11311132
out ErrorRecord errRecord)
11321133
{
11331134
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::PushNupkgContainerRegistry()");
1134-
string fullNupkgFile = System.IO.Path.Combine(outputNupkgDir, packageName + "." + packageVersion.ToNormalizedString() + ".nupkg");
1135+
1136+
// if isNupkgPathSpecified, then we need to publish the original .nupkg file, as it may be signed
1137+
string fullNupkgFile = isNupkgPathSpecified ? originalNupkgPath : System.IO.Path.Combine(outputNupkgDir, packageName + "." + packageVersion.ToNormalizedString() + ".nupkg");
11351138

11361139
string pkgNameForUpload = string.IsNullOrEmpty(modulePrefix) ? packageName : modulePrefix + "/" + packageName;
11371140
string packageNameLowercase = pkgNameForUpload.ToLower();

src/code/PSResourceInfo.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,8 @@ public static bool TryConvertFromContainerRegistryJson(
836836

837837
// Version
838838
// For scripts (i.e with "Version" property) the version can contain prerelease label
839-
if (rootDom.TryGetProperty("Version", out JsonElement scriptVersionElement))
839+
// For nupkg only based packages the .nuspec's metadata attributes will be lowercase
840+
if (rootDom.TryGetProperty("Version", out JsonElement scriptVersionElement) || rootDom.TryGetProperty("version", out scriptVersionElement))
840841
{
841842
versionValue = scriptVersionElement.ToString();
842843
pkgVersion = ParseHttpVersion(versionValue, out string prereleaseLabel);
@@ -883,25 +884,25 @@ public static bool TryConvertFromContainerRegistryJson(
883884
metadata["NormalizedVersion"] = parsedNormalizedVersion.ToNormalizedString();
884885

885886
// License Url
886-
if (rootDom.TryGetProperty("LicenseUrl", out JsonElement licenseUrlElement))
887+
if (rootDom.TryGetProperty("LicenseUrl", out JsonElement licenseUrlElement) || rootDom.TryGetProperty("licenseUrl", out licenseUrlElement))
887888
{
888889
metadata["LicenseUrl"] = ParseHttpUrl(licenseUrlElement.ToString()) as Uri;
889890
}
890891

891892
// Project Url
892-
if (rootDom.TryGetProperty("ProjectUrl", out JsonElement projectUrlElement))
893+
if (rootDom.TryGetProperty("ProjectUrl", out JsonElement projectUrlElement) || rootDom.TryGetProperty("projectUrl", out projectUrlElement))
893894
{
894895
metadata["ProjectUrl"] = ParseHttpUrl(projectUrlElement.ToString()) as Uri;
895896
}
896897

897898
// Icon Url
898-
if (rootDom.TryGetProperty("IconUrl", out JsonElement iconUrlElement))
899+
if (rootDom.TryGetProperty("IconUrl", out JsonElement iconUrlElement) || rootDom.TryGetProperty("iconUrl", out iconUrlElement))
899900
{
900901
metadata["IconUrl"] = ParseHttpUrl(iconUrlElement.ToString()) as Uri;
901902
}
902903

903904
// Tags
904-
if (rootDom.TryGetProperty("Tags", out JsonElement tagsElement))
905+
if (rootDom.TryGetProperty("Tags", out JsonElement tagsElement) || rootDom.TryGetProperty("tags", out tagsElement))
905906
{
906907
string[] pkgTags = Utils.EmptyStrArray;
907908
if (tagsElement.ValueKind == JsonValueKind.Array)
@@ -937,7 +938,7 @@ public static bool TryConvertFromContainerRegistryJson(
937938
}
938939

939940
// Author
940-
if (rootDom.TryGetProperty("Authors", out JsonElement authorsElement))
941+
if (rootDom.TryGetProperty("Authors", out JsonElement authorsElement) || rootDom.TryGetProperty("authors", out authorsElement))
941942
{
942943
metadata["Authors"] = authorsElement.ToString();
943944

@@ -948,19 +949,19 @@ public static bool TryConvertFromContainerRegistryJson(
948949
}
949950

950951
// Copyright
951-
if (rootDom.TryGetProperty("Copyright", out JsonElement copyrightElement))
952+
if (rootDom.TryGetProperty("Copyright", out JsonElement copyrightElement) || rootDom.TryGetProperty("copyright", out copyrightElement))
952953
{
953954
metadata["Copyright"] = copyrightElement.ToString();
954955
}
955956

956957
// Description
957-
if (rootDom.TryGetProperty("Description", out JsonElement descriptiontElement))
958+
if (rootDom.TryGetProperty("Description", out JsonElement descriptiontElement) || rootDom.TryGetProperty("description", out descriptiontElement))
958959
{
959960
metadata["Description"] = descriptiontElement.ToString();
960961
}
961962

962963
// ReleaseNotes
963-
if (rootDom.TryGetProperty("ReleaseNotes", out JsonElement releaseNotesElement))
964+
if (rootDom.TryGetProperty("ReleaseNotes", out JsonElement releaseNotesElement) || rootDom.TryGetProperty("releaseNotes", out releaseNotesElement))
964965
{
965966
metadata["ReleaseNotes"] = releaseNotesElement.ToString();
966967
}

src/code/PublishHelper.cs

Lines changed: 208 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Collections.Generic;
1010
using System.IO;
1111
using System.Linq;
12+
using System.Text.RegularExpressions;
1213
using System.Management.Automation;
1314
using System.Net;
1415
using System.Net.Http;
@@ -440,12 +441,28 @@ internal void PushResource(string Repository, string modulePrefix, bool SkipDepe
440441
{
441442
ContainerRegistryServerAPICalls containerRegistryServer = new ContainerRegistryServerAPICalls(repository, _cmdletPassedIn, _networkCredential, userAgentString);
442443

443-
var pkgMetadataFile = (resourceType == ResourceType.Script) ? pathToScriptFileToPublish : pathToModuleManifestToPublish;
444+
if (_isNupkgPathSpecified)
445+
{
446+
// copy the .nupkg to a temp path (outputNupkgDir field) as we don't want to tamper with the original, possibly signed, .nupkg file
447+
string copiedNupkgFilePath = CopyNupkgFileToTempPath(nupkgFilePath: Path, errRecord: out ErrorRecord copyErrRecord);
448+
if (copyErrRecord != null)
449+
{
450+
_cmdletPassedIn.WriteError(copyErrRecord);
451+
return;
452+
}
453+
454+
// get package info (name, version, metadata hashtable) from the copied .nupkg package and then populate appropriate fields (_pkgName, _pkgVersion, parsedMetadata)
455+
GetPackageInfoFromNupkg(nupkgFilePath: copiedNupkgFilePath, errRecord: out ErrorRecord pkgInfoErrRecord);
456+
if (pkgInfoErrRecord != null)
457+
{
458+
_cmdletPassedIn.WriteError(pkgInfoErrRecord);
459+
return;
460+
}
461+
}
444462

445-
if (!containerRegistryServer.PushNupkgContainerRegistry(pkgMetadataFile, outputNupkgDir, _pkgName, modulePrefix, _pkgVersion, resourceType, parsedMetadata, dependencies, out ErrorRecord pushNupkgContainerRegistryError))
463+
if (!containerRegistryServer.PushNupkgContainerRegistry(outputNupkgDir, _pkgName, modulePrefix, _pkgVersion, resourceType, parsedMetadata, dependencies, _isNupkgPathSpecified, Path, out ErrorRecord pushNupkgContainerRegistryError))
446464
{
447465
_cmdletPassedIn.WriteError(pushNupkgContainerRegistryError);
448-
// exit out of processing
449466
return;
450467
}
451468
}
@@ -455,6 +472,7 @@ internal void PushResource(string Repository, string modulePrefix, bool SkipDepe
455472
{
456473
outputNupkgDir = pathToNupkgToPublish;
457474
}
475+
458476
// This call does not throw any exceptions, but it will write unsuccessful responses to the console
459477
if (!PushNupkg(outputNupkgDir, repository.Name, repository.Uri.ToString(), out ErrorRecord pushNupkgError))
460478
{
@@ -474,7 +492,8 @@ internal void PushResource(string Repository, string modulePrefix, bool SkipDepe
474492
}
475493
finally
476494
{
477-
if (!_isNupkgPathSpecified)
495+
// For scenarios such as Publish-PSResource -NupkgPath -Repository <non-container registry repository>, the outputNupkgDir will be set to NupkgPath path, and a temp outputDir folder will not have been created and thus doesn't need to attempt to be deleted
496+
if (Directory.Exists(outputDir))
478497
{
479498
_cmdletPassedIn.WriteVerbose(string.Format("Deleting temporary directory '{0}'", outputDir));
480499
Utils.DeleteDirectory(outputDir);
@@ -1243,6 +1262,191 @@ private bool CheckDependenciesExist(Hashtable dependencies, string repositoryNam
12431262
return true;
12441263
}
12451264

1265+
/// <summary>
1266+
/// This method is called by Publish-PSResource when the -NupkgPath parameter is specified
1267+
/// The method copies the .nupkg file to a temp path (populated at outputNupkgDir field) as we dont' want to extract and read original .nupkg file
1268+
/// </summary>
1269+
private string CopyNupkgFileToTempPath(string nupkgFilePath, out ErrorRecord errRecord)
1270+
{
1271+
errRecord = null;
1272+
string destinationFilePath = String.Empty;
1273+
var packageFullName = System.IO.Path.GetFileName(nupkgFilePath);
1274+
try
1275+
{
1276+
if (!Directory.Exists(outputDir))
1277+
{
1278+
Directory.CreateDirectory(outputDir);
1279+
if (!Directory.Exists(outputNupkgDir))
1280+
{
1281+
Directory.CreateDirectory(outputNupkgDir);
1282+
}
1283+
}
1284+
1285+
destinationFilePath = System.IO.Path.Combine(outputNupkgDir, packageFullName);
1286+
File.Copy(Path, destinationFilePath);
1287+
}
1288+
catch (Exception e)
1289+
{
1290+
errRecord = new ErrorRecord(
1291+
new ArgumentException($"Error moving .nupkg at -NupkgPath to temp nupkg dir path '{outputNupkgDir}' due to: '{e.Message}'."),
1292+
"ErrorMovingNupkg",
1293+
ErrorCategory.NotSpecified,
1294+
this);
1295+
1296+
// exit process record
1297+
return destinationFilePath;
1298+
}
1299+
1300+
return destinationFilePath;
1301+
}
1302+
1303+
/// <summary>
1304+
/// Get package info from the .nupkg file provided, inluding package name (_pkgName), package version (_pkgVersion), and metadata parsed into a hashtable (parsedMetadata)
1305+
/// </summary>
1306+
private void GetPackageInfoFromNupkg(string nupkgFilePath, out ErrorRecord errRecord)
1307+
{
1308+
errRecord = null;
1309+
Regex rx = new Regex(@"\.\d+\.", RegexOptions.Compiled | RegexOptions.IgnoreCase);
1310+
var packageFullName = System.IO.Path.GetFileName(nupkgFilePath);
1311+
MatchCollection matches = rx.Matches(packageFullName);
1312+
if (matches.Count == 0)
1313+
{
1314+
return;
1315+
}
1316+
1317+
Match match = matches[0];
1318+
1319+
GroupCollection groups = match.Groups;
1320+
if (groups.Count == 0)
1321+
{
1322+
return;
1323+
}
1324+
1325+
Capture group = groups[0];
1326+
1327+
string pkgFoundName = packageFullName.Substring(0, group.Index);
1328+
1329+
string version = packageFullName.Substring(group.Index + 1, packageFullName.LastIndexOf('.') - group.Index - 1);
1330+
_cmdletPassedIn.WriteDebug($"Found package '{pkgFoundName}', version '{version}', from packageFullName '{packageFullName}' at path '{Path}'");
1331+
1332+
if (!NuGetVersion.TryParse(version, out NuGetVersion nugetVersion))
1333+
{
1334+
errRecord = new ErrorRecord(
1335+
new ArgumentException($"Error parsing version '{version}' into NuGetVersion instance."),
1336+
"ErrorParsingNuGetVersion",
1337+
ErrorCategory.NotSpecified,
1338+
this);
1339+
1340+
return;
1341+
}
1342+
1343+
_pkgName = pkgFoundName;
1344+
_pkgVersion = nugetVersion;
1345+
parsedMetadata = GetMetadataFromNupkg(nupkgFilePath, _pkgName, out errRecord);
1346+
}
1347+
1348+
/// <summary>
1349+
/// Extract copied .nupkg, find metadata file (either .ps1, .psd1, or .nuspec) and read metadata into a hashtable
1350+
/// </summary>
1351+
internal Hashtable GetMetadataFromNupkg(string copiedNupkgPath, string packageName, out ErrorRecord errRecord)
1352+
{
1353+
Hashtable pkgMetadata = new Hashtable(StringComparer.OrdinalIgnoreCase);
1354+
errRecord = null;
1355+
1356+
// in temp directory create an "extract" folder to which we'll copy .nupkg to, extract contents, etc.
1357+
string nupkgDirPath = Directory.GetParent(copiedNupkgPath).FullName; //someGuid/nupkg/myPkg.nupkg -> /someGuid/nupkg
1358+
string tempPath = Directory.GetParent(nupkgDirPath).FullName; // someGuid
1359+
var extractPath = System.IO.Path.Combine(tempPath, "extract"); // someGuid/extract
1360+
1361+
try
1362+
{
1363+
var dir = Directory.CreateDirectory(extractPath);
1364+
dir.Attributes &= ~FileAttributes.ReadOnly;
1365+
1366+
// change extension to .zip
1367+
string zipFilePath = System.IO.Path.ChangeExtension(copiedNupkgPath, ".zip");
1368+
File.Move(copiedNupkgPath, zipFilePath);
1369+
1370+
// extract from .zip
1371+
_cmdletPassedIn.WriteDebug($"Extracting '{zipFilePath}' to '{extractPath}'");
1372+
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, extractPath);
1373+
1374+
string psd1FilePath = String.Empty;
1375+
string ps1FilePath = String.Empty;
1376+
string nuspecFilePath = String.Empty;
1377+
Utils.GetMetadataFilesFromPath(extractPath, packageName, out psd1FilePath, out ps1FilePath, out nuspecFilePath, out string properCasingPkgName);
1378+
1379+
List<string> pkgTags = new List<string>();
1380+
1381+
if (File.Exists(psd1FilePath))
1382+
{
1383+
_cmdletPassedIn.WriteDebug($"Attempting to read module manifest file '{psd1FilePath}'");
1384+
if (!Utils.TryReadManifestFile(psd1FilePath, out pkgMetadata, out Exception readManifestError))
1385+
{
1386+
errRecord = new ErrorRecord(
1387+
readManifestError,
1388+
"GetMetadataFromNupkgFailure",
1389+
ErrorCategory.ParserError,
1390+
this);
1391+
1392+
return pkgMetadata;
1393+
}
1394+
}
1395+
else if (File.Exists(ps1FilePath))
1396+
{
1397+
_cmdletPassedIn.WriteDebug($"Attempting to read script file '{ps1FilePath}'");
1398+
if (!PSScriptFileInfo.TryTestPSScriptFileInfo(ps1FilePath, out PSScriptFileInfo parsedScript, out ErrorRecord[] errors, out string[] verboseMsgs))
1399+
{
1400+
errRecord = new ErrorRecord(
1401+
new InvalidDataException($"PSScriptFile could not be read properly"),
1402+
"GetMetadataFromNupkgFailure",
1403+
ErrorCategory.ParserError,
1404+
this);
1405+
1406+
return pkgMetadata;
1407+
}
1408+
1409+
pkgMetadata = parsedScript.ToHashtable();
1410+
}
1411+
else if (File.Exists(nuspecFilePath))
1412+
{
1413+
_cmdletPassedIn.WriteDebug($"Attempting to read nuspec file '{nuspecFilePath}'");
1414+
pkgMetadata = Utils.GetMetadataFromNuspec(nuspecFilePath, _cmdletPassedIn, out errRecord);
1415+
if (errRecord != null)
1416+
{
1417+
return pkgMetadata;
1418+
}
1419+
}
1420+
else
1421+
{
1422+
errRecord = new ErrorRecord(
1423+
new InvalidDataException($".nupkg package must contain either .psd1, .ps1, or .nuspec file and none were found"),
1424+
"GetMetadataFromNupkgFailure",
1425+
ErrorCategory.InvalidData,
1426+
this);
1427+
1428+
return pkgMetadata;
1429+
}
1430+
}
1431+
catch (Exception e)
1432+
{
1433+
errRecord = new ErrorRecord(
1434+
new InvalidOperationException($"Temporary folder for installation could not be created or set due to: {e.Message}"),
1435+
"GetMetadataFromNupkgFailure",
1436+
ErrorCategory.InvalidOperation,
1437+
this);
1438+
}
1439+
finally
1440+
{
1441+
if (Directory.Exists(extractPath))
1442+
{
1443+
Utils.DeleteDirectory(extractPath);
1444+
}
1445+
}
1446+
1447+
return pkgMetadata;
1448+
}
1449+
12461450
#endregion
12471451
}
12481452
}

0 commit comments

Comments
 (0)