Skip to content

Commit 0774602

Browse files
committed
add code needed to publish .nupkg directly to ACR
1 parent 3e23ecb commit 0774602

File tree

2 files changed

+255
-4
lines changed

2 files changed

+255
-4
lines changed

src/code/ContainerRegistryServerAPICalls.cs

Lines changed: 190 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Text;
2121
using System.Security.Cryptography;
2222
using System.Text.Json;
23+
using System.Xml;
2324

2425
namespace Microsoft.PowerShell.PSResourceGet
2526
{
@@ -1115,23 +1116,144 @@ private static Collection<KeyValuePair<string, string>> GetDefaultHeaders(string
11151116
#endregion
11161117

11171118
#region Publish Methods
1119+
1120+
/// <summary>
1121+
/// This method is called if Publish-PSResource is called with -NupkgPath specified for a ContainerRegistry
1122+
/// Extracts metadata from the .nupkg
1123+
/// </summary>
1124+
internal Hashtable GetMetadataFromNupkg(string copiedNupkgPath, string packageName, out ErrorRecord errRecord)
1125+
{
1126+
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetMetadataFromNupkg()");
1127+
1128+
Hashtable pkgMetadata = new Hashtable(StringComparer.OrdinalIgnoreCase);
1129+
errRecord = null;
1130+
1131+
// create temp directory where we will copy .nupkg to, extract contents, etc.
1132+
string nupkgDirPath = Directory.GetParent(copiedNupkgPath).FullName; //someGuid/nupkg.myPkg.nupkg -> /someGuid/nupkg
1133+
string tempPath = Directory.GetParent(nupkgDirPath).FullName; // someGuid
1134+
var extractPath = Path.Combine(tempPath, "extract");
1135+
string packageFullName = Path.GetFileName(copiedNupkgPath);
1136+
// string packageName = Path.GetFileNameWithoutExtension(packageFullName);
1137+
1138+
try
1139+
{
1140+
var dir = Directory.CreateDirectory(extractPath);
1141+
dir.Attributes &= ~FileAttributes.ReadOnly;
1142+
1143+
// copy .nupkg
1144+
// string destNupkgPath = Path.Combine(tempDiscoveryPath, packageFullName);
1145+
// File.Copy(packagePath, destNupkgPath);
1146+
1147+
// change extension to .zip
1148+
string zipFilePath = Path.ChangeExtension(copiedNupkgPath, ".zip");
1149+
File.Move(copiedNupkgPath, zipFilePath);
1150+
1151+
// extract from .zip
1152+
_cmdletPassedIn.WriteDebug($"Extracting '{zipFilePath}' to '{extractPath}'");
1153+
System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, extractPath);
1154+
1155+
string psd1FilePath = String.Empty;
1156+
string ps1FilePath = String.Empty;
1157+
string nuspecFilePath = String.Empty;
1158+
Utils.GetMetadataFilesFromPath(extractPath, packageName, out psd1FilePath, out ps1FilePath, out nuspecFilePath, out string properCasingPkgName);
1159+
1160+
List<string> pkgTags = new List<string>();
1161+
1162+
if (File.Exists(psd1FilePath))
1163+
{
1164+
_cmdletPassedIn.WriteDebug($"Attempting to read module manifest file '{psd1FilePath}'");
1165+
if (!Utils.TryReadManifestFile(psd1FilePath, out pkgMetadata, out Exception readManifestError))
1166+
{
1167+
errRecord = new ErrorRecord(
1168+
readManifestError,
1169+
"GetMetadataFromNupkgFailure",
1170+
ErrorCategory.ParserError,
1171+
this);
1172+
1173+
return pkgMetadata;
1174+
}
1175+
}
1176+
else if (File.Exists(ps1FilePath))
1177+
{
1178+
_cmdletPassedIn.WriteDebug($"Attempting to read script file '{ps1FilePath}'");
1179+
if (!PSScriptFileInfo.TryTestPSScriptFileInfo(ps1FilePath, out PSScriptFileInfo parsedScript, out ErrorRecord[] errors, out string[] verboseMsgs))
1180+
{
1181+
errRecord = new ErrorRecord(
1182+
new InvalidDataException($"PSScriptFile could not be read properly"),
1183+
"GetMetadataFromNupkgFailure",
1184+
ErrorCategory.ParserError,
1185+
this);
1186+
1187+
return pkgMetadata;
1188+
}
1189+
1190+
pkgMetadata = parsedScript.ToHashtable();
1191+
}
1192+
else if (File.Exists(nuspecFilePath))
1193+
{
1194+
_cmdletPassedIn.WriteDebug($"Attempting to read nuspec file '{nuspecFilePath}'");
1195+
pkgMetadata = GetHashtableForNuspec(nuspecFilePath, out errRecord);
1196+
if (errRecord != null)
1197+
{
1198+
return pkgMetadata;
1199+
}
1200+
}
1201+
else
1202+
{
1203+
errRecord = new ErrorRecord(
1204+
new InvalidDataException($".nupkg package must contain either .psd1, .ps1, or .nuspec file and none were found"),
1205+
"GetMetadataFromNupkgFailure",
1206+
ErrorCategory.InvalidData,
1207+
this);
1208+
1209+
return pkgMetadata;
1210+
}
1211+
}
1212+
catch (Exception e)
1213+
{
1214+
errRecord = new ErrorRecord(
1215+
new InvalidOperationException($"Temporary folder for installation could not be created or set due to: {e.Message}"),
1216+
"GetMetadataFromNupkgFailure",
1217+
ErrorCategory.InvalidOperation,
1218+
this);
1219+
}
1220+
finally
1221+
{
1222+
if (Directory.Exists(extractPath))
1223+
{
1224+
Utils.DeleteDirectory(extractPath);
1225+
}
1226+
}
1227+
1228+
return pkgMetadata;
1229+
}
11181230

11191231
/// <summary>
11201232
/// Helper method that publishes a package to the container registry.
11211233
/// This gets called from Publish-PSResource.
11221234
/// </summary>
1123-
internal bool PushNupkgContainerRegistry(string psd1OrPs1File,
1235+
internal bool PushNupkgContainerRegistry(
11241236
string outputNupkgDir,
11251237
string packageName,
11261238
string modulePrefix,
11271239
NuGetVersion packageVersion,
11281240
ResourceType resourceType,
11291241
Hashtable parsedMetadataHash,
11301242
Hashtable dependencies,
1243+
bool isNupkgPathSpecified,
1244+
string originalNupkgPath,
11311245
out ErrorRecord errRecord)
11321246
{
11331247
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::PushNupkgContainerRegistry()");
1134-
string fullNupkgFile = System.IO.Path.Combine(outputNupkgDir, packageName + "." + packageVersion.ToNormalizedString() + ".nupkg");
1248+
1249+
if (isNupkgPathSpecified)
1250+
{
1251+
var copiedNupkgPath = System.IO.Path.Combine(outputNupkgDir, packageName + "." + packageVersion.ToNormalizedString() + ".nupkg");
1252+
parsedMetadataHash = GetMetadataFromNupkg(copiedNupkgPath, packageName, out errRecord);
1253+
}
1254+
1255+
// if isNupkgPathSpecified, then we need to publish the original .nupkg file, as it may be signed
1256+
string fullNupkgFile = !isNupkgPathSpecified ? System.IO.Path.Combine(outputNupkgDir, packageName + "." + packageVersion.ToNormalizedString() + ".nupkg") : originalNupkgPath;
11351257

11361258
string pkgNameForUpload = string.IsNullOrEmpty(modulePrefix) ? packageName : modulePrefix + "/" + packageName;
11371259
string packageNameLowercase = pkgNameForUpload.ToLower();
@@ -1708,6 +1830,72 @@ private string PrependMARPrefix(string packageName)
17081830
return updatedPackageName;
17091831
}
17101832

1833+
/// <summary>
1834+
/// Method that loads file content into XMLDocument. Used when reading .nuspec file.
1835+
/// </summary>
1836+
private XmlDocument LoadXmlDocument(string filePath, out ErrorRecord errRecord)
1837+
{
1838+
errRecord = null;
1839+
XmlDocument doc = new XmlDocument();
1840+
doc.PreserveWhitespace = true;
1841+
try { doc.Load(filePath); }
1842+
catch (Exception e)
1843+
{
1844+
errRecord = new ErrorRecord(
1845+
exception: e,
1846+
"LoadXmlDocumentFailure",
1847+
ErrorCategory.ReadError,
1848+
this);
1849+
}
1850+
1851+
return doc;
1852+
}
1853+
1854+
/// <summary>
1855+
/// Method that reads .nuspec file and parses out metadata information into Hashtable.
1856+
/// </summary>
1857+
private Hashtable GetHashtableForNuspec(string filePath, out ErrorRecord errRecord)
1858+
{
1859+
Hashtable nuspecHashtable = new Hashtable(StringComparer.InvariantCultureIgnoreCase);
1860+
1861+
XmlDocument nuspecXmlDocument = LoadXmlDocument(filePath, out errRecord);
1862+
if (errRecord != null)
1863+
{
1864+
return nuspecHashtable;
1865+
}
1866+
1867+
try
1868+
{
1869+
XmlNodeList elemList = nuspecXmlDocument.GetElementsByTagName("metadata");
1870+
for(int i = 0; i < elemList.Count; i++)
1871+
{
1872+
XmlNode metadataInnerXml = elemList[i];
1873+
1874+
for(int j= 0; j<metadataInnerXml.ChildNodes.Count; j++)
1875+
{
1876+
string key = metadataInnerXml.ChildNodes[j].LocalName;
1877+
string value = metadataInnerXml.ChildNodes[j].InnerText;
1878+
1879+
if (!nuspecHashtable.ContainsKey(key))
1880+
{
1881+
nuspecHashtable.Add(key, value);
1882+
}
1883+
}
1884+
1885+
}
1886+
}
1887+
catch (Exception e)
1888+
{
1889+
errRecord = new ErrorRecord(
1890+
exception: e,
1891+
"GetHashtableForNuspecFailure",
1892+
ErrorCategory.ReadError,
1893+
this);
1894+
}
1895+
1896+
return nuspecHashtable;
1897+
}
1898+
17111899
#endregion
17121900
}
17131901
}

src/code/PublishHelper.cs

Lines changed: 65 additions & 2 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;
@@ -432,6 +433,7 @@ internal void PushResource(string Repository, string modulePrefix, bool SkipDepe
432433
return;
433434
}
434435
}
436+
// TODO: do we not want to additionally publish to DesintationPath if NupkgPath is specified?
435437
}
436438

437439
string repositoryUri = repository.Uri.AbsoluteUri;
@@ -440,9 +442,70 @@ internal void PushResource(string Repository, string modulePrefix, bool SkipDepe
440442
{
441443
ContainerRegistryServerAPICalls containerRegistryServer = new ContainerRegistryServerAPICalls(repository, _cmdletPassedIn, _networkCredential, userAgentString);
442444

443-
var pkgMetadataFile = (resourceType == ResourceType.Script) ? pathToScriptFileToPublish : pathToModuleManifestToPublish;
445+
// var pkgMetadataFile = (resourceType == ResourceType.Script) ? pathToScriptFileToPublish : pathToModuleManifestToPublish;
446+
if (_isNupkgPathSpecified)
447+
{
448+
// Path will really be NupkgPath
449+
var packageFullName = System.IO.Path.GetFileName(Path);
450+
try
451+
{
452+
if (!Directory.Exists(outputDir))
453+
{
454+
Directory.CreateDirectory(outputDir);
455+
if (!Directory.Exists(outputNupkgDir))
456+
{
457+
Directory.CreateDirectory(outputNupkgDir);
458+
}
459+
}
460+
461+
var destinationFilePath = System.IO.Path.Combine(outputNupkgDir, packageFullName);
462+
File.Copy(Path, destinationFilePath);
463+
}
464+
catch (Exception e)
465+
{
466+
_cmdletPassedIn.WriteError(new ErrorRecord(
467+
new ArgumentException($"Error moving .nupkg at -NupkgPath to temp nupkg dir path '{outputNupkgDir}' due to: '{e.Message}'."),
468+
"ErrorMovingNupkg",
469+
ErrorCategory.NotSpecified,
470+
this));
471+
472+
// exit process record
473+
return;
474+
}
475+
476+
// extract _pkgName and _pkgVersion because these will be null/not yet set
477+
Regex rx = new Regex(@"\.\d+\.", RegexOptions.Compiled | RegexOptions.IgnoreCase);
478+
MatchCollection matches = rx.Matches(packageFullName);
479+
if (matches.Count == 0)
480+
{
481+
return;
482+
}
483+
484+
Match match = matches[0];
485+
486+
GroupCollection groups = match.Groups;
487+
if (groups.Count == 0)
488+
{
489+
return;
490+
}
491+
492+
Capture group = groups[0];
493+
494+
string pkgFoundName = packageFullName.Substring(0, group.Index);
495+
496+
string version = packageFullName.Substring(group.Index + 1, packageFullName.LastIndexOf('.') - group.Index - 1);
497+
_cmdletPassedIn.WriteDebug($"Found package '{pkgFoundName}', version '{version}', from packageFullName '{packageFullName}' at path '{Path}'");
498+
499+
if (!NuGetVersion.TryParse(version, out NuGetVersion nugetVersion))
500+
{
501+
return;
502+
}
503+
504+
_pkgName = pkgFoundName;
505+
_pkgVersion = nugetVersion;
506+
}
444507

445-
if (!containerRegistryServer.PushNupkgContainerRegistry(pkgMetadataFile, outputNupkgDir, _pkgName, modulePrefix, _pkgVersion, resourceType, parsedMetadata, dependencies, out ErrorRecord pushNupkgContainerRegistryError))
508+
if (!containerRegistryServer.PushNupkgContainerRegistry(outputNupkgDir, _pkgName, modulePrefix, _pkgVersion, resourceType, parsedMetadata, dependencies, _isNupkgPathSpecified, Path, out ErrorRecord pushNupkgContainerRegistryError))
446509
{
447510
_cmdletPassedIn.WriteError(pushNupkgContainerRegistryError);
448511
// exit out of processing

0 commit comments

Comments
 (0)