diff --git a/src/code/ContainerRegistryServerAPICalls.cs b/src/code/ContainerRegistryServerAPICalls.cs index b7af3b98d..cd8c4c8be 100644 --- a/src/code/ContainerRegistryServerAPICalls.cs +++ b/src/code/ContainerRegistryServerAPICalls.cs @@ -82,14 +82,40 @@ public ContainerRegistryServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmd #region Overridden Methods + /// + /// Async find method which allows for searching for single name with specific version. + /// Name: no wildcard support + /// Version: no wildcard support + /// This is the concurrent (parallel) counterpart of FindVersion(). + /// public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("FindVersionAsync is not implemented for ContainerRegistryServerAPICalls."); + debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::FindVersionAsync()"); + FindResults findResponse = FindVersion(packageName, version, type, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } + /// + /// Async find method which allows for searching for single name with version range. + /// Name: no wildcard support + /// Version: supports wildcards + /// This is the concurrent (parallel) counterpart of FindVersionGlobbing(). + /// public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("FindVersionGlobbingAsync is not implemented for ContainerRegistryServerAPICalls."); + debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::FindVersionGlobbingAsync()"); + FindResults findResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } /// @@ -158,9 +184,21 @@ public override FindResults FindName(string packageName, bool includePrerelease, } + /// + /// Async find method which allows for searching for single name and returns latest version. + /// Name: no wildcard support + /// This is the concurrent (parallel) counterpart of FindName(). + /// public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("FindNameAsync is not implemented for ContainerRegistryServerAPICalls."); + debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::FindNameAsync()"); + FindResults findResponse = FindName(packageName, includePrerelease, type, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } /// @@ -329,7 +367,22 @@ public override Stream InstallPackage(string packageName, string packageVersion, /// public override Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("FindNameAsync is not implemented for ContainerRegistryServerAPICalls."); + debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::InstallPackageAsync()"); + Stream results = new MemoryStream(); + if (string.IsNullOrEmpty(packageVersion)) + { + errorMsgs.Enqueue(new ErrorRecord( + exception: new ArgumentNullException($"Package version could not be found for {packageName}"), + "PackageVersionNullOrEmptyError", + ErrorCategory.InvalidArgument, + _cmdletPassedIn)); + + return Task.FromResult(results); + } + + string packageNameForInstall = PrependMARPrefix(packageName); + results = InstallVersionAsync(packageNameForInstall, packageVersion, errorMsgs, debugMsgs, verboseMsgs); + return Task.FromResult(results); } /// @@ -400,6 +453,76 @@ private Stream InstallVersion( return responseContent.ReadAsStreamAsync().Result; } + /// + /// Installs a package with version specified using concurrent queues for output instead of cmdlet streams. + /// Used by the async install path to avoid cross-thread cmdlet stream writes. + /// + private Stream InstallVersionAsync( + string packageName, + string packageVersion, + ConcurrentQueue errorMsgs, + ConcurrentQueue debugMsgs, + ConcurrentQueue verboseMsgs) + { + debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::InstallVersionAsync()"); + string packageNameLowercase = packageName.ToLower(); + string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + try + { + Directory.CreateDirectory(tempPath); + } + catch (Exception e) + { + errorMsgs.Enqueue(new ErrorRecord( + exception: e, + "InstallVersionTempDirCreationError", + ErrorCategory.InvalidResult, + _cmdletPassedIn)); + + return null; + } + + string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, isPushOperation: false, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + return null; + } + + verboseMsgs.Enqueue($"Getting manifest for {packageNameLowercase} - {packageVersion}"); + var manifest = GetContainerRegistryRepositoryManifest(packageNameLowercase, packageVersion, containerRegistryAccessToken, out errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + return null; + } + string digest = GetDigestFromManifest(manifest, out errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + return null; + } + + verboseMsgs.Enqueue($"Downloading blob for {packageNameLowercase} - {packageVersion}"); + HttpContent responseContent; + try + { + responseContent = GetContainerRegistryBlobAsync(packageNameLowercase, digest, containerRegistryAccessToken).Result; + } + catch (Exception e) + { + errorMsgs.Enqueue(new ErrorRecord( + exception: e, + "InstallVersionGetContainerRegistryBlobAsyncError", + ErrorCategory.InvalidResult, + _cmdletPassedIn)); + + return null; + } + + return responseContent.ReadAsStreamAsync().Result; + } + #endregion #region Authentication and Token Methods diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs index 1a074b67e..3d149ab02 100644 --- a/src/code/FindHelper.cs +++ b/src/code/FindHelper.cs @@ -904,25 +904,18 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R // Example: Find-PSResource -Name "Az" -Version "3.0.0.0" // Example: Find-PSResource -Name "Az" -Version "3.0.0.0" -Tag "Windows" _cmdletPassedIn.WriteDebug("Exact version and package name are specified"); - + string key = string.Empty; FindResults responses = null; if (_tag.Length == 0) { - - ConcurrentDictionary> cachedNetworkCalls = new ConcurrentDictionary>(); Task response = null; - if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2) { - string key = $"{pkgName}|{_nugetVersion.ToNormalizedString()}|{_type}"; - response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionAsync(pkgName, _nugetVersion.ToNormalizedString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs)); - - responses = response.GetAwaiter().GetResult(); + key = $"{pkgName}|{_nugetVersion.ToNormalizedString()}|{_type}"; + response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionAsync(pkgName, _nugetVersion.ToNormalizedString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs)); + + responses = response.GetAwaiter().GetResult(); - Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); - } - else { - responses = currentServer.FindVersion(pkgName, _nugetVersion.ToNormalizedString(), _type, out errRecord); - } + Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); } else { @@ -991,20 +984,18 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R // Example: Find-PSResource -Name "Az" -Version "[1.0.0.0, 3.0.0.0]" _cmdletPassedIn.WriteDebug("Version range and package name are specified"); + errRecord = null; FindResults responses = null; if (_tag.Length == 0) { ConcurrentDictionary> cachedNetworkCalls = new ConcurrentDictionary>(); Task response = null; - if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2) { - string key = $"{pkgName}|{_versionRange.ToString()}|{_type}"; - response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionGlobbingAsync(pkgName, _versionRange, _prerelease, _type, getOnlyLatest: false, errorMsgs, warningMsgs, debugMsgs, verboseMsgs)); - - responses = response.GetAwaiter().GetResult(); - } - else { - responses = currentServer.FindVersionGlobbing(pkgName, _versionRange, _prerelease, _type, getOnlyLatest: false, out errRecord); - } + string key = $"{pkgName}|{_versionRange.ToString()}|{_type}"; + response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionGlobbingAsync(pkgName, _versionRange, _prerelease, _type, getOnlyLatest: false, errorMsgs, warningMsgs, debugMsgs, verboseMsgs)); + + responses = response.GetAwaiter().GetResult(); + + Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); } else { @@ -1189,7 +1180,7 @@ internal void FindDependencyPackagesHelper(ServerApiCall currentServer, Response //const int PARALLEL_THRESHOLD = 5; // TODO: Trottle limit from user, defaults to 5; int processorCount = Environment.ProcessorCount; int maxDegreeOfParallelism = processorCount * 4; - if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2 && currentPkg.Dependencies.Length > processorCount) + if (currentPkg.Dependencies.Length > processorCount) { Parallel.ForEach(currentPkg.Dependencies, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, dep => { @@ -1290,23 +1281,41 @@ private PSResourceInfo FindDependencyWithSpecificVersion( PSResourceInfo depPkg = null; ErrorRecord errRecord = null; FindResults responses = null; - Task response = null; debugMsgs.Enqueue("In FindHelper::FindDependencyWithSpecificVersion()"); - if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2) + ConcurrentQueue operationErrorMsgs = new ConcurrentQueue(); + ConcurrentQueue operationWarningMsgs = new ConcurrentQueue(); + ConcurrentQueue operationDebugMsgs = new ConcurrentQueue(); + ConcurrentQueue operationVerboseMsgs = new ConcurrentQueue(); + + // Call FindVersionAsync() for dependency with specific version. + string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}"; + responses = currentServer.FindVersionAsync(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, operationErrorMsgs, operationWarningMsgs, operationDebugMsgs, operationVerboseMsgs).GetAwaiter().GetResult(); + + while (operationErrorMsgs.TryDequeue(out ErrorRecord queuedError)) { - // See if the network call we're making is already cached, if not, call FindNameAsync() and cache results - string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}"; - debugMsgs.Enqueue("Checking if network call is cached."); - response = _cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionAsync(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs)); - - responses = response.GetAwaiter().GetResult(); + if (errRecord == null) + { + errRecord = queuedError; + } + + errorMsgs.Enqueue(queuedError); } - else + + while (operationWarningMsgs.TryDequeue(out string queuedWarning)) { - responses = currentServer.FindVersion(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, out errRecord); + warningMsgs.Enqueue(queuedWarning); } + while (operationDebugMsgs.TryDequeue(out string queuedDebug)) + { + debugMsgs.Enqueue(queuedDebug); + } + + while (operationVerboseMsgs.TryDequeue(out string queuedVerbose)) + { + verboseMsgs.Enqueue(queuedVerbose); + } // Error handling and Convert to PSResource object if (errRecord != null) @@ -1335,7 +1344,7 @@ private PSResourceInfo FindDependencyWithSpecificVersion( string pkgVersion = FormatPkgVersionString(depPkg); debugMsgs.Enqueue($"Found dependency '{depPkg.Name}' version '{pkgVersion}'"); - string key = $"{depPkg.Name}{pkgVersion}"; + key = $"{depPkg.Name}{pkgVersion}"; if (!depPkgsFound.ContainsKey(key)) { // Add pkg to collection of packages found then find dependencies @@ -1369,19 +1378,12 @@ private PSResourceInfo FindDependencyWithLowerBound( Task response = null; debugMsgs.Enqueue("In FindHelper::FindDependencyWithLowerBound()"); - if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2) - { - // See if the network call we're making is already cached, if not, call FindNameAsync() and cache results - string key = $"{dep.Name}|*|{_type}"; - debugMsgs.Enqueue("Checking if network call is cached."); - response = _cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindNameAsync(dep.Name, includePrerelease: true, _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs)); - - responses = response.GetAwaiter().GetResult(); - } - else - { - responses = currentServer.FindName(dep.Name, includePrerelease: true, _type, out errRecord); - } + // See if the network call we're making is already cached, if not, call FindNameAsync() and cache results + string key = $"{dep.Name}|*|{_type}"; + debugMsgs.Enqueue("Checking if network call is cached."); + response = _cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindNameAsync(dep.Name, includePrerelease: true, _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs)); + + responses = response.GetAwaiter().GetResult(); // Error handling and Convert to PSResource object if (errRecord != null) @@ -1410,7 +1412,7 @@ private PSResourceInfo FindDependencyWithLowerBound( string pkgVersion = FormatPkgVersionString(depPkg); debugMsgs.Enqueue($"Found dependency '{depPkg.Name}' version '{pkgVersion}'"); - string key = $"{depPkg.Name}{pkgVersion}"; + key = $"{depPkg.Name}{pkgVersion}"; if (!depPkgsFound.ContainsKey(key)) { // Add pkg to collection of packages found then find dependencies @@ -1445,21 +1447,13 @@ private PSResourceInfo FindDependencyWithUpperBound( ConcurrentDictionary> cachedNetworkCalls = new ConcurrentDictionary>(); debugMsgs.Enqueue("In FindHelper::FindDependencyWithUpperBound()"); + // See if the network call we're making is already cached, if not, call FindNameAsync() and cache results + string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}"; + debugMsgs.Enqueue("Checking if network call is cached."); + response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionGlobbingAsync(dep.Name, dep.VersionRange, includePrerelease: true, ResourceType.None, getOnlyLatest: true, errorMsgs, warningMsgs, debugMsgs, verboseMsgs)); - if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2) - { - // See if the network call we're making is already caced, if not, call FindNameAsync() and cache results - string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}"; - debugMsgs.Enqueue("Checking if network call is cached."); - response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionGlobbingAsync(dep.Name, dep.VersionRange, includePrerelease: true, ResourceType.None, getOnlyLatest: true, errorMsgs, warningMsgs, debugMsgs, verboseMsgs)); - - responses = response.GetAwaiter().GetResult(); + responses = response.GetAwaiter().GetResult(); - } - else - { - responses = currentServer.FindVersionGlobbing(dep.Name, dep.VersionRange, includePrerelease: true, ResourceType.None, getOnlyLatest: true, out errRecord); - } // Error handling and Convert to PSResource object if (errRecord != null) @@ -1489,7 +1483,7 @@ private PSResourceInfo FindDependencyWithUpperBound( string pkgVersion = FormatPkgVersionString(depPkg); debugMsgs.Enqueue($"Found dependency '{depPkg.Name}' version '{pkgVersion}'"); - string key = $"{depPkg.Name}{pkgVersion}"; + key = $"{depPkg.Name}{pkgVersion}"; if (!depPkgsFound.ContainsKey(key)) { // Add pkg to collection of packages found then find dependencies diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index c89ef0ee9..e3f95b616 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -801,7 +801,7 @@ private ConcurrentDictionary BeginPackageInstall( } else { - // Concurrent updates, currently only implemented for v2 server repositories + // Concurrent updates // Find all dependencies if (!skipDependencyCheck) { @@ -853,7 +853,7 @@ private ConcurrentDictionary InstallParentAndDependencyPackag // TODO: figure out a good threshold and parallel count int processorCount = Environment.ProcessorCount; _cmdletPassedIn.WriteDebug($"parentAndDeps.Count is {parentAndDeps.Count}, processor count is: {processorCount}"); - if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2 && parentAndDeps.Count > processorCount) + if (parentAndDeps.Count > processorCount) { _cmdletPassedIn.WriteDebug($"parentAndDeps.Count is greater than processor count"); // Set the maximum degree of parallelism to 32? (Invoke-Command has default of 32, that's where we got this number from) diff --git a/src/code/LocalServerApiCalls.cs b/src/code/LocalServerApiCalls.cs index a8e505acb..1ebb72dcb 100644 --- a/src/code/LocalServerApiCalls.cs +++ b/src/code/LocalServerApiCalls.cs @@ -41,14 +41,40 @@ public LocalServerAPICalls (PSRepositoryInfo repository, PSCmdlet cmdletPassedIn #region Overridden Methods + /// + /// Async find method which allows for searching for single name with specific version. + /// Name: no wildcard support + /// Version: no wildcard support + /// This is the concurrent (parallel) counterpart of FindVersion(). + /// public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException(); + debugMsgs.Enqueue("In LocalServerApiCalls::FindVersionAsync()"); + FindResults findResponse = FindVersionHelper(packageName, version, tags: Utils.EmptyStrArray, type, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } + /// + /// Async find method which allows for searching for single name with version range. + /// Name: no wildcard support + /// Version: supports wildcards + /// This is the concurrent (parallel) counterpart of FindVersionGlobbing(). + /// public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException(); + debugMsgs.Enqueue("In LocalServerApiCalls::FindVersionGlobbingAsync()"); + FindResults findResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } /// /// Find method which allows for searching for all packages from a repository and returns latest version for each. @@ -124,9 +150,21 @@ public override FindResults FindName(string packageName, bool includePrerelease, return FindNameHelper(packageName, Utils.EmptyStrArray, includePrerelease, type, out errRecord); } + /// + /// Async find method which allows for searching for single name and returns latest version. + /// Name: no wildcard support + /// This is the concurrent (parallel) counterpart of FindName(). + /// public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException(); + debugMsgs.Enqueue("In LocalServerApiCalls::FindNameAsync()"); + FindResults findResponse = FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } /// @@ -278,7 +316,26 @@ public override Stream InstallPackage(string packageName, string packageVersion, /// public override Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("InstallPackageAsync is not implemented for LocalServerAPICalls."); + debugMsgs.Enqueue("In LocalServerApiCalls::InstallPackageAsync()"); + Stream results = new MemoryStream(); + if (string.IsNullOrEmpty(packageVersion)) + { + errorMsgs.Enqueue(new ErrorRecord( + exception: new ArgumentNullException($"Package version could not be found for {packageName}"), + "PackageVersionNullOrEmptyError", + ErrorCategory.InvalidArgument, + _cmdletPassedIn)); + + return Task.FromResult(results); + } + + results = InstallVersion(packageName, packageVersion, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(results); } #endregion diff --git a/src/code/NuGetServerAPICalls.cs b/src/code/NuGetServerAPICalls.cs index 1c6bb2828..d8c6b5ffe 100644 --- a/src/code/NuGetServerAPICalls.cs +++ b/src/code/NuGetServerAPICalls.cs @@ -49,14 +49,51 @@ public NuGetServerAPICalls (PSRepositoryInfo repository, PSCmdlet cmdletPassedIn #region Overridden Methods + /// + /// Async find method which allows for searching for single name with specific version. + /// Name: no wildcard support + /// Version: no wildcard support + /// This is the concurrent (parallel) counterpart of FindVersion(). + /// public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("FindVersionAsync is not implemented for NuGetServerAPICalls."); + debugMsgs.Enqueue("In NuGetServerAPICalls::FindVersionAsync()"); + var queryBuilder = new NuGetV2QueryBuilder(new Dictionary{ + { "id", $"'{packageName}'" }, + }); + var filterBuilder = queryBuilder.FilterBuilder; + + // We need to explicitly add 'Id eq ' whenever $filter is used, otherwise arbitrary results are returned. + filterBuilder.AddCriterion($"Id eq '{packageName}'"); + filterBuilder.AddCriterion($"NormalizedVersion eq '{packageName}'"); + + var requestUrl = $"{Repository.Uri}/FindPackagesById()?{queryBuilder.BuildQueryString()}"; + string response = HttpRequestCallAsync(requestUrl, debugMsgs, out ErrorRecord errRecord); + FindResults findResponse = new FindResults(stringResponse: new string[] { response }, hashtableResponse: emptyHashResponses, responseType: FindResponseType); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } + /// + /// Async find method which allows for searching for single name with version range. + /// Name: no wildcard support + /// Version: supports wildcards + /// This is the concurrent (parallel) counterpart of FindVersionGlobbing(). + /// public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("FindVersionGlobbingAsync is not implemented for NuGetServerAPICalls."); + debugMsgs.Enqueue("In NuGetServerAPICalls::FindVersionGlobbingAsync()"); + FindResults findResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } /// /// Find method which allows for searching for all packages from a repository and returns latest version for each. @@ -193,9 +230,21 @@ public override FindResults FindName(string packageName, bool includePrerelease, return new FindResults(stringResponse: new string[]{ response }, hashtableResponse: emptyHashResponses, responseType: FindResponseType); } + /// + /// Async find method which allows for searching for single name and returns latest version. + /// Name: no wildcard support + /// This is the concurrent (parallel) counterpart of FindName(). + /// public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("FindNameAsync is not implemented for NuGetServerAPICalls."); + debugMsgs.Enqueue("In NuGetServerAPICalls::FindNameAsync()"); + FindResults findResponse = FindName(packageName, includePrerelease, type, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } /// @@ -471,7 +520,14 @@ public override Stream InstallPackage(string packageName, string packageVersion, /// public override Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("InstallPackageAsync is not implemented for NuGetServerAPICalls."); + debugMsgs.Enqueue("In NuGetServerAPICalls::InstallPackageAsync()"); + Stream results = InstallPackage(packageName, packageVersion, includePrerelease, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(results); } /// @@ -572,6 +628,55 @@ private HttpContent HttpRequestCallForContent(string requestUrl, out ErrorRecord return content; } + /// + /// Helper method that makes the HTTP request for the NuGet server protocol url passed in for async find APIs. + /// This helper writes diagnostics to the provided debug queue and avoids cmdlet stream writes. + /// + private string HttpRequestCallAsync(string requestUrl, ConcurrentQueue debugMsgs, out ErrorRecord errRecord) + { + debugMsgs.Enqueue("In NuGetServerAPICalls::HttpRequestCallAsync()"); + errRecord = null; + string response = string.Empty; + + try + { + debugMsgs.Enqueue($"Request url is: '{requestUrl}'"); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl); + response = SendRequestAsync(request, _sessionClient).GetAwaiter().GetResult(); + } + catch (HttpRequestException e) + { + errRecord = new ErrorRecord( + exception: e, + "HttpRequestFallFailure", + ErrorCategory.ConnectionError, + this); + } + catch (ArgumentNullException e) + { + errRecord = new ErrorRecord( + exception: e, + "HttpRequestFallFailure", + ErrorCategory.ConnectionError, + this); + } + catch (InvalidOperationException e) + { + errRecord = new ErrorRecord( + exception: e, + "HttpRequestFallFailure", + ErrorCategory.ConnectionError, + this); + } + + if (string.IsNullOrEmpty(response)) + { + debugMsgs.Enqueue("Response is empty"); + } + + return response; + } + #endregion #region Private Methods diff --git a/src/code/V3ServerAPICalls.cs b/src/code/V3ServerAPICalls.cs index a2beb1545..9df736d7e 100644 --- a/src/code/V3ServerAPICalls.cs +++ b/src/code/V3ServerAPICalls.cs @@ -89,14 +89,43 @@ public V3ServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmdletPassedIn, Ne #region Overridden Methods + /// + /// Async find method which allows for searching for single name with specific version. + /// Name: no wildcard support + /// Version: no wildcard support + /// Examples: Search "NuGet.Server.Core" "3.0.0-beta" + /// This is the concurrent (parallel) counterpart of FindVersion(). + /// public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("FindVersionAsync is not implemented for V3ServerAPICalls."); + debugMsgs.Enqueue("In V3ServerAPICalls::FindVersionAsync()"); + FindResults findResponse = FindVersionHelper(packageName, version, tags: Utils.EmptyStrArray, type, out ErrorRecord errRecord, debugMsgs); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } + /// + /// Async find method which allows for searching for single name with version range. + /// Name: no wildcard support + /// Version: supports wildcards + /// Examples: Search "NuGet.Server.Core" "[1.0.0.0, 5.0.0.0]" + /// Search "NuGet.Server.Core" "3.*" + /// This is the concurrent (parallel) counterpart of FindVersionGlobbing(). + /// public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("FindVersionAsync is not implemented for V3ServerAPICalls."); + debugMsgs.Enqueue("In V3ServerAPICalls::FindVersionGlobbingAsync()"); + FindResults findResponse = FindVersionGlobbingHelper(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord, debugMsgs); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } /// @@ -166,9 +195,22 @@ public override FindResults FindName(string packageName, bool includePrerelease, return FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out errRecord); } + /// + /// Async find method which allows for searching for single name and returns latest version. + /// Name: no wildcard support + /// Examples: Search "Newtonsoft.Json" + /// This is the concurrent (parallel) counterpart of FindName(). + /// public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("FindVersionAsync is not implemented for V3ServerAPICalls."); + debugMsgs.Enqueue("In V3ServerAPICalls::FindNameAsync()"); + FindResults findResponse = FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out ErrorRecord errRecord, debugMsgs); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + } + + return Task.FromResult(findResponse); } /// @@ -239,8 +281,13 @@ public override FindResults FindNameGlobbingWithTag(string packageName, string[] /// public override FindResults FindVersionGlobbing(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, out ErrorRecord errRecord) { - _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindVersionGlobbing()"); - string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord); + return FindVersionGlobbingHelper(packageName, versionRange, includePrerelease, type, getOnlyLatest, out errRecord); + } + + private FindResults FindVersionGlobbingHelper(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null) + { + WriteDebug("In V3ServerAPICalls::FindVersionGlobbing()", debugMsgs); + string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, debugMsgs); if (errRecord != null) { return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType); @@ -267,7 +314,7 @@ public override FindResults FindVersionGlobbing(string packageName, VersionRange if (NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion) && versionRange.Satisfies(pkgVersion)) { - _cmdletPassedIn.WriteDebug($"Package version parsed as '{pkgVersion}' satisfies the version range"); + WriteDebug($"Package version parsed as '{pkgVersion}' satisfies the version range", debugMsgs); if (!pkgVersion.IsPrerelease || includePrerelease) { satisfyingVersions.Add(response); @@ -353,9 +400,34 @@ public override Stream InstallPackage(string packageName, string packageVersion, /// Examples: Install "PowerShellGet" -Version "3.5.0-alpha" /// Install "PowerShellGet" -Version "3.0.0" /// - public override Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) + public override async Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) { - throw new NotImplementedException("InstallPackageAsync is not implemented for NuGetServerAPICalls."); + debugMsgs.Enqueue("In V3ServerAPICalls::InstallPackageAsync()"); + Stream results = new MemoryStream(); + if (string.IsNullOrEmpty(packageVersion)) + { + errorMsgs.Enqueue(new ErrorRecord( + exception: new ArgumentNullException($"Package version could not be found for {packageName}"), + "PackageVersionNullOrEmptyError", + ErrorCategory.InvalidArgument, + this)); + + return results; + } + + if (!NuGetVersion.TryParse(packageVersion, out NuGetVersion requiredVersion)) + { + errorMsgs.Enqueue(new ErrorRecord( + new ArgumentException($"Version {packageVersion} to be installed is not a valid NuGet version."), + "InstallVersionFailure", + ErrorCategory.InvalidArgument, + this)); + + return results; + } + + results = await InstallHelperAsync(packageName, requiredVersion, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); + return results; } #endregion @@ -512,10 +584,10 @@ private FindResults FindTagsFromNuGetRepo(string[] tags, bool includePrerelease, /// /// Helper method called by FindName() and FindNameWithTag() /// - private FindResults FindNameHelper(string packageName, string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord) + private FindResults FindNameHelper(string packageName, string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null) { - _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindNameHelper()"); - string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord); + WriteDebug("In V3ServerAPICalls::FindNameHelper()", debugMsgs); + string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, debugMsgs); if (errRecord != null) { return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType); @@ -553,7 +625,7 @@ private FindResults FindNameHelper(string packageName, string[] tags, bool inclu if (NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion)) { - _cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'"); + WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'", debugMsgs); 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 @@ -608,9 +680,9 @@ private FindResults FindNameHelper(string packageName, string[] tags, bool inclu /// /// Helper method called by FindVersion() and FindVersionWithTag() /// - private FindResults FindVersionHelper(string packageName, string version, string[] tags, ResourceType type, out ErrorRecord errRecord) + private FindResults FindVersionHelper(string packageName, string version, string[] tags, ResourceType type, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null) { - _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindVersionHelper()"); + WriteDebug("In V3ServerAPICalls::FindVersionHelper()", debugMsgs); if (!NuGetVersion.TryParse(version, out NuGetVersion requiredVersion)) { errRecord = new ErrorRecord( @@ -621,9 +693,9 @@ private FindResults FindVersionHelper(string packageName, string version, string return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType); } - _cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{requiredVersion}'"); + //_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{requiredVersion}'"); - string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord); + string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, debugMsgs); if (errRecord != null) { return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType); @@ -830,12 +902,94 @@ private Stream InstallHelper(string packageName, NuGetVersion version, out Error return content.ReadAsStreamAsync().GetAwaiter().GetResult(); } + /// + /// Helper method that is called by InstallPackageAsync() + /// For InstallName() we want latest version installed (so version parameter passed in will be null), for InstallVersion() we want specified, non-null version installed. + /// This is the async counterpart of InstallHelper() used for concurrent (parallel) installation workflows. + /// + private async Task InstallHelperAsync(string packageName, NuGetVersion version, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) + { + debugMsgs.Enqueue("In V3ServerAPICalls::InstallHelperAsync()"); + Stream pkgStream = null; + bool getLatestVersion = true; + if (version != null) + { + getLatestVersion = false; + } + + string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, packageContentProperty, isSearch: false, out ErrorRecord errRecord); + if (errRecord != null) + { + errorMsgs.Enqueue(errRecord); + return pkgStream; + } + + if (versionedResponses.Length == 0) + { + errorMsgs.Enqueue(new ErrorRecord( + new Exception($"Package with name '{packageName}' and version '{version}' could not be found in repository '{Repository.Name}'"), + "InstallFailure", + ErrorCategory.InvalidResult, + this)); + + return null; + } + + string pkgContentUrl = String.Empty; + if (getLatestVersion) + { + pkgContentUrl = versionedResponses[0]; + } + else + { + // loop through responses to find one containing required version + foreach (string response in versionedResponses) + { + // Response will be "packageContent" element value that looks like: "{packageBaseAddress}/{packageName}/{normalizedVersion}/{packageName}.{normalizedVersion}.nupkg" + // Ex: https://api.nuget.org/v3-flatcontainer/test_module/1.0.0/test_module.1.0.0.nupkg + if (response.Contains(version.ToNormalizedString())) + { + pkgContentUrl = response; + break; + } + } + } + + if (String.IsNullOrEmpty(pkgContentUrl)) + { + errorMsgs.Enqueue(new ErrorRecord( + new Exception($"Package with name '{packageName}' and version '{version}' could not be found in repository '{Repository.Name}'"), + "InstallFailure", + ErrorCategory.InvalidResult, + this)); + + return null; + } + + var content = await HttpRequestCallForContentAsync(pkgContentUrl, errorMsgs, warningMsgs, debugMsgs, verboseMsgs); + + if (content is null) + { + errorMsgs.Enqueue(new ErrorRecord( + new Exception($"No content was returned by repository '{Repository.Name}'"), + "InstallFailureContentNullv3Async", + ErrorCategory.InvalidResult, + this)); + + return new MemoryStream(); + } + + pkgStream = await content.ReadAsStreamAsync(); + + return pkgStream; + } + /// /// Gets the versioned package entries from the RegistrationsBaseUrl resource /// i.e when the package Name being searched for does not contain wildcard /// This is called by FindNameHelper(), FindVersionHelper(), FindVersionGlobbing(), InstallHelper() /// - private string[] GetVersionedPackageEntriesFromRegistrationsResource(string packageName, string propertyName, bool isSearch, out ErrorRecord errRecord) + private string[] GetVersionedPackageEntriesFromRegistrationsResource(string packageName, string propertyName, bool isSearch, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null) { // TODO: pass in ConcurrentQueue to write out debug message. //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetVersionedPackageEntriesFromRegistrationsResource()"); @@ -852,7 +1006,7 @@ private string[] GetVersionedPackageEntriesFromRegistrationsResource(string pack return responses; } - responses = GetVersionedResponsesFromRegistrationsResource(registrationsBaseUrl, packageName, propertyName, isSearch, out errRecord); + responses = GetVersionedResponsesFromRegistrationsResource(registrationsBaseUrl, packageName, propertyName, isSearch, out errRecord, debugMsgs); if (errRecord != null) { return Utils.EmptyStrArray; @@ -1060,6 +1214,7 @@ private string FindSearchQueryService(Dictionary resources, out /// private JsonElement[] GetMetadataElementFromIdLinkElement(JsonElement idLinkElement, string packageName, out string upperVersion, out ErrorRecord errRecord) { + // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ? _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetMetadataElementFromIdLinkElement()"); upperVersion = String.Empty; JsonElement[] innerItems = new JsonElement[]{}; @@ -1102,6 +1257,7 @@ private JsonElement[] GetMetadataElementFromIdLinkElement(JsonElement idLinkElem } else { + // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ? _cmdletPassedIn.WriteDebug($"Package with name '{packageName}' did not have 'upper' property so package versions may not be in descending order."); } @@ -1228,6 +1384,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert } else { + // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ? _cmdletPassedIn.WriteDebug($"Metadata for package with name '{packageName}' did not have inner 'items' or '@Id' properties."); } } @@ -1267,6 +1424,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert } else { + // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ? _cmdletPassedIn.WriteDebug($"Metadata for package with name '{packageName}' was not of value kind type string or object."); } } @@ -1294,7 +1452,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert /// The "packageContent" property is used for download, and the value is a URI for the .nupkg file. /// /// - private string[] GetVersionedResponsesFromRegistrationsResource(string registrationsBaseUrl, string packageName, string property, bool isSearch, out ErrorRecord errRecord) + private string[] GetVersionedResponsesFromRegistrationsResource(string registrationsBaseUrl, string packageName, string property, bool isSearch, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null) { // TODO: pass in ConcurrentQueue to write out debug message. //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetVersionedResponsesFromRegistrationsResource()"); @@ -1332,7 +1490,7 @@ private string[] GetVersionedResponsesFromRegistrationsResource(string registrat if (isSearch) { - if (!IsLatestVersionFirstForSearch(versionedResponseArr, out errRecord)) + if (!IsLatestVersionFirstForSearch(versionedResponseArr, out errRecord, debugMsgs)) { Array.Reverse(versionedResponseArr); } @@ -1353,9 +1511,9 @@ private string[] GetVersionedResponsesFromRegistrationsResource(string registrat /// ADO feeds usually return version entries in descending order, but Nuget.org repository returns them in ascending order. /// Package versions will reflect prerelease preference, but upper version and lower version would not so we don't use them for comparison. /// - private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out ErrorRecord errRecord) + private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null) { - _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::IsLatestVersionFirstForSearch()"); + WriteDebug("In V3ServerAPICalls::IsLatestVersionFirstForSearch()", debugMsgs); errRecord = null; bool latestVersionFirst = true; int versionResponsesCount = versionedResponses.Length; @@ -1447,6 +1605,18 @@ private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out Erro return latestVersionFirst; } + private void WriteDebug(string message, ConcurrentQueue debugMsgs = null) + { + if (debugMsgs == null) + { + _cmdletPassedIn.WriteDebug(message); + } + else + { + debugMsgs.Enqueue(message); + } + } + /// /// Returns true if the nupkg URI entries for each package version are arranged in descending order with respect to the package's version. /// ADO feeds usually return version entries in descending order, but Nuget.org repository returns them in ascending order. @@ -1675,6 +1845,40 @@ private HttpContent HttpRequestCallForContent(string requestUrlV3, out ErrorReco return content; } + /// + /// Helper method that makes the HTTP request for the V3 server protocol url passed in for install APIs asynchronously. + /// This is the async counterpart of HttpRequestCallForContent() used for concurrent (parallel) installation workflows. + /// + private async Task HttpRequestCallForContentAsync(string requestUrlV3, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs) + { + debugMsgs.Enqueue("In V3ServerAPICalls::HttpRequestCallForContentAsync()"); + HttpContent content = null; + try + { + debugMsgs.Enqueue($"Request url is '{requestUrlV3}'"); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrlV3); + + content = await SendV3RequestForContentAsync(request, _sessionClient); + } + catch (Exception e) + { + errorMsgs.Enqueue(new ErrorRecord( + exception: e, + "HttpRequestCallForContentFailure", + ErrorCategory.InvalidResult, + this)); + + return null; + } + + if (string.IsNullOrEmpty(content?.ToString())) + { + debugMsgs.Enqueue("Response is empty"); + } + + return content; + } + /// /// Helper method called by HttpRequestCall() that makes the HTTP request for string response. ///