|
20 | 20 | using System.Text; |
21 | 21 | using System.Security.Cryptography; |
22 | 22 | using System.Text.Json; |
| 23 | +using System.Xml; |
23 | 24 |
|
24 | 25 | namespace Microsoft.PowerShell.PSResourceGet |
25 | 26 | { |
@@ -1115,23 +1116,144 @@ private static Collection<KeyValuePair<string, string>> GetDefaultHeaders(string |
1115 | 1116 | #endregion |
1116 | 1117 |
|
1117 | 1118 | #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 | + } |
1118 | 1230 |
|
1119 | 1231 | /// <summary> |
1120 | 1232 | /// Helper method that publishes a package to the container registry. |
1121 | 1233 | /// This gets called from Publish-PSResource. |
1122 | 1234 | /// </summary> |
1123 | | - internal bool PushNupkgContainerRegistry(string psd1OrPs1File, |
| 1235 | + internal bool PushNupkgContainerRegistry( |
1124 | 1236 | string outputNupkgDir, |
1125 | 1237 | string packageName, |
1126 | 1238 | string modulePrefix, |
1127 | 1239 | NuGetVersion packageVersion, |
1128 | 1240 | ResourceType resourceType, |
1129 | 1241 | Hashtable parsedMetadataHash, |
1130 | 1242 | Hashtable dependencies, |
| 1243 | + bool isNupkgPathSpecified, |
| 1244 | + string originalNupkgPath, |
1131 | 1245 | out ErrorRecord errRecord) |
1132 | 1246 | { |
1133 | 1247 | _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; |
1135 | 1257 |
|
1136 | 1258 | string pkgNameForUpload = string.IsNullOrEmpty(modulePrefix) ? packageName : modulePrefix + "/" + packageName; |
1137 | 1259 | string packageNameLowercase = pkgNameForUpload.ToLower(); |
@@ -1708,6 +1830,72 @@ private string PrependMARPrefix(string packageName) |
1708 | 1830 | return updatedPackageName; |
1709 | 1831 | } |
1710 | 1832 |
|
| 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 | + |
1711 | 1899 | #endregion |
1712 | 1900 | } |
1713 | 1901 | } |
0 commit comments