diff --git a/src/code/V3ServerAPICalls.cs b/src/code/V3ServerAPICalls.cs index a6b8d3620..f425c15e3 100644 --- a/src/code/V3ServerAPICalls.cs +++ b/src/code/V3ServerAPICalls.cs @@ -27,6 +27,7 @@ internal class V3ServerAPICalls : ServerApiCall private bool _isJFrogRepo { get; set; } private bool _isGHPkgsRepo { get; set; } private bool _isMyGetRepo { get; set; } + private bool _isAWSCodeArtifactRepo { get; set; } public FindResponseType v3FindResponseType = FindResponseType.ResponseString; private static readonly Hashtable[] emptyHashResponses = new Hashtable[]{}; private static readonly string nugetRepoUri = "https://api.nuget.org/v3/index.json"; @@ -55,7 +56,7 @@ public V3ServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmdletPassedIn, Ne handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; bool token = false; - if(networkCredential != null) + if(networkCredential != null) { token = String.Equals("token", networkCredential.UserName) ? true : false; }; @@ -71,7 +72,7 @@ public V3ServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmdletPassedIn, Ne } else { handler.Credentials = networkCredential; - + _sessionClient = new HttpClient(handler); }; @@ -80,9 +81,10 @@ public V3ServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmdletPassedIn, Ne _sessionClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", userAgentString); _isNuGetRepo = String.Equals(Repository.Uri.AbsoluteUri, nugetRepoUri, StringComparison.InvariantCultureIgnoreCase); - _isJFrogRepo = Repository.Uri.AbsoluteUri.ToLower().Contains("jfrog.io"); - _isGHPkgsRepo = Repository.Uri.AbsoluteUri.ToLower().Contains("pkg.github.com"); - _isMyGetRepo = Repository.Uri.AbsoluteUri.ToLower().Contains("myget.org"); + _isJFrogRepo = RepositoryUriContains("jfrog.io"); + _isGHPkgsRepo = RepositoryUriContains("pkg.github.com"); + _isMyGetRepo = RepositoryUriContains("myget.org"); + _isAWSCodeArtifactRepo = RepositoryUriContains(".codeartifact.") && RepositoryUriContains(".amazonaws.com"); } #endregion @@ -91,18 +93,37 @@ public V3ServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmdletPassedIn, Ne /// /// Find method which allows for searching for all packages from a repository and returns latest version for each. - /// Not supported for V3 repository. + /// Supported for AWS CodeArtifact V3 repository only. /// public override FindResults FindAll(bool includePrerelease, ResourceType type, out ErrorRecord errRecord) { _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindAll()"); - errRecord = new ErrorRecord( - new InvalidOperationException($"Find all is not supported for the V3 server protocol repository '{Repository.Name}'"), - "FindAllFailure", - ErrorCategory.InvalidOperation, - this); - return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType); + if (!_isAWSCodeArtifactRepo) + { + errRecord = new ErrorRecord( + new InvalidOperationException($"Find all is not supported for the V3 server protocol repository '{Repository.Name}'"), + "FindAllFailure", + ErrorCategory.InvalidOperation, + this); + + return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType); + } + + var queryTerm = ""; + var matchingPkgEntries = GetVersionedPackageEntriesFromSearchQueryResource(queryTerm, includePrerelease, out errRecord); + if (errRecord != null) + { + return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType); + } + + List matchingResponses = new List(); + foreach (var pkgEntry in matchingPkgEntries) + { + matchingResponses.Add(pkgEntry.ToString()); + } + + return new FindResults(stringResponse: matchingResponses.ToArray(), hashtableResponse: emptyHashResponses, responseType: v3FindResponseType); } /// @@ -175,7 +196,7 @@ public override FindResults FindNameWithTag(string packageName, string[] tags, b public override FindResults FindNameGlobbing(string packageName, bool includePrerelease, ResourceType type, out ErrorRecord errRecord) { _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindNameGlobbing()"); - if (_isNuGetRepo || _isJFrogRepo || _isGHPkgsRepo || _isMyGetRepo) + if (_isNuGetRepo || _isJFrogRepo || _isGHPkgsRepo || _isMyGetRepo || _isAWSCodeArtifactRepo) { return FindNameGlobbingFromNuGetRepo(packageName, tags: Utils.EmptyStrArray, includePrerelease, out errRecord); } @@ -198,7 +219,7 @@ public override FindResults FindNameGlobbing(string packageName, bool includePre public override FindResults FindNameGlobbingWithTag(string packageName, string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord) { _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindNameGlobbingWithTag()"); - if (_isNuGetRepo || _isJFrogRepo || _isGHPkgsRepo || _isMyGetRepo) + if (_isNuGetRepo || _isJFrogRepo || _isGHPkgsRepo || _isMyGetRepo || _isAWSCodeArtifactRepo) { return FindNameGlobbingFromNuGetRepo(packageName, tags, includePrerelease, out errRecord); } @@ -343,7 +364,7 @@ private FindResults FindNameGlobbingFromNuGetRepo(string packageName, string[] t var names = packageName.Split(new char[] { '*' }, StringSplitOptions.RemoveEmptyEntries); string querySearchTerm; - if (names.Length == 0) + if (names.Length == 0 && !_isAWSCodeArtifactRepo) { errRecord = new ErrorRecord( new ArgumentException("-Name '*' for V3 server protocol repositories is not supported"), @@ -834,7 +855,7 @@ private string[] GetVersionedPackageEntriesFromRegistrationsResource(string pack /// /// Gets the versioned package entries from SearchQueryService resource /// i.e when the package Name being searched for contains wildcards or a Tag query search is performed - /// This is called by FindNameGlobbingFromNuGetRepo() and FindTagsFromNuGetRepo() + /// This is called by FindAll(), FindNameGlobbingFromNuGetRepo() and FindTagsFromNuGetRepo() /// private List GetVersionedPackageEntriesFromSearchQueryResource(string queryTerm, bool includePrerelease, out ErrorRecord errRecord) { @@ -852,22 +873,27 @@ private List GetVersionedPackageEntriesFromSearchQueryResource(stri return pkgEntries; } - // Get initial response int skip = 0; - string query = $"{searchQueryServiceUrl}?q={queryTerm}&prerelease={includePrerelease}&semVerLevel=2.0.0&skip={skip}&take=100"; + int skipAndTakeAmount = 100; + if (_isAWSCodeArtifactRepo) { + skipAndTakeAmount = 25; + } - // Get responses for all packages that contain the required tags - pkgEntries.AddRange(GetJsonElementArr(query, dataName, out int initialCount, out errRecord).ToList()); + // Get initial response + string query = $"{searchQueryServiceUrl}?q={queryTerm}&prerelease={includePrerelease}&semVerLevel=2.0.0&skip={skip}&take={skipAndTakeAmount}"; - // check count (ie "totalHits") 425 ==> count/100 ~~> 4 calls ~~> + 1 = 5 calls - int count = initialCount / 100 + 1; - // if more than 100 count, loop and add response to list - while (count > 0) - { - skip += 100; - query = $"{searchQueryServiceUrl}?q={queryTerm}&prerelease={includePrerelease}&semVerLevel=2.0.0&skip={skip}&take=100"; - pkgEntries.AddRange(GetJsonElementArr(query, dataName, out int unneededCount, out errRecord).ToList()); - count--; + var itemList = GetJsonElementArr(query, dataName, out int initialCount, out errRecord).ToList(); + pkgEntries.AddRange(itemList); + + // Get responses for all packages that contain the required tags + while (itemList.Count > 0) { + skip += skipAndTakeAmount; + query = $"{searchQueryServiceUrl}?q={queryTerm}&prerelease={includePrerelease}&semVerLevel=2.0.0&skip={skip}&take={skipAndTakeAmount}"; + itemList = GetJsonElementArr(query, dataName, out int unneededCount, out errRecord).ToList(); + if (itemList.Count == 0) { + break; + } + pkgEntries.AddRange(itemList); } return pkgEntries; @@ -1716,6 +1742,14 @@ private static async Task SendV3RequestForContentAsync(HttpRequestM } } + + /// + /// Helper method called by FindAll() to validate whether the repositories AbsoluteUri contains a specified value. + /// + private bool RepositoryUriContains(string str) { + return Repository.Uri.AbsoluteUri.ToLower().Contains(str); + } + #endregion } } diff --git a/test/FindPSResourceTests/FindPSResourceV3Server.Tests.ps1 b/test/FindPSResourceTests/FindPSResourceV3Server.Tests.ps1 index dc1e46262..3dcdc1787 100644 --- a/test/FindPSResourceTests/FindPSResourceV3Server.Tests.ps1 +++ b/test/FindPSResourceTests/FindPSResourceV3Server.Tests.ps1 @@ -10,6 +10,8 @@ Write-Verbose -Verbose "Current module search paths: $psmodulePaths" Describe 'Test HTTP Find-PSResource for V3 Server Protocol' -tags 'CI' { BeforeAll{ + $AWSCodeArtifactGalleryName = Get-AWSCodeArtifactGalleryName + $AWSCodeArtifactGalleryLocation = Get-AWSCodeArtifactGalleryLocation $NuGetGalleryName = Get-NuGetGalleryName $testModuleName = "test_module" Get-NewPSResourceRepositoryFile @@ -269,13 +271,23 @@ Describe 'Test HTTP Find-PSResource for V3 Server Protocol' -tags 'CI' { $err[0].FullyQualifiedErrorId | Should -BeExactly "FindAllFailure,Microsoft.PowerShell.PSResourceGet.Cmdlets.FindPSResource" } + It "should support AWS CodeArtifact with find all resources given Name '*'" { + Register-PSResourceRepository -Name $AWSCodeArtifactGalleryName -Uri $AWSCodeArtifactGalleryLocation + $res = Find-PSResource -Name "*" -Repository $AWSCodeArtifactGalleryName -ErrorVariable err -ErrorAction SilentlyContinue + Unregister-PSResourceRepository -Name $AWSCodeArtifactGalleryName + $res | Should -BeNullOrEmpty + $err.Count | Should -BeGreaterThan 0 + # The `HttpRequestCallFailure` error indicates the cmdlet moved past the fast-fail for `-Name '*'` not supported + $err[0].FullyQualifiedErrorId | Should -BeExactly "HttpRequestCallFailure,Microsoft.PowerShell.PSResourceGet.Cmdlets.FindPSResource" + } + It "should not find an unlisted module" { $res = Find-PSResource -Name "PMTestDependency1" -Repository $NuGetGalleryName -ErrorVariable err -ErrorAction SilentlyContinue $res | Should -BeNullOrEmpty $err.Count | Should -BeGreaterThan 0 $err[0].FullyQualifiedErrorId | Should -BeExactly "PackageNotFound,Microsoft.PowerShell.PSResourceGet.Cmdlets.FindPSResource" } - + # "carb*" is intentionally chosen as a sequence that will trigger pagination (ie more than 100 results), # but is not too time consuming. # There are currently between 236 packages that should be returned diff --git a/test/PSGetTestUtils.psm1 b/test/PSGetTestUtils.psm1 index c9f5006b4..dad8b6a15 100644 --- a/test/PSGetTestUtils.psm1 +++ b/test/PSGetTestUtils.psm1 @@ -22,6 +22,11 @@ $script:PSGalleryLocation = 'https://www.powershellgallery.com/api/v2' $script:NuGetGalleryName = 'NuGetGallery' $script:NuGetGalleryLocation = 'https://api.nuget.org/v3/index.json' +# This is not a valid Code Artifact endpoint. However, this will allow a unit test to move +# past the NuGet v3 fast fail in the FindAll() method. +$script:AWSCodeArtifactName = 'AWSCodeArtifact' +$script:AWSCodeArtifactLocation = 'https://cadomainname-awsaccountid.d.codeartifact.region.amazonaws.com/nuget/carepositoryname/v3/index.json' + if($script:IsInbox) { $script:ProgramFilesPSPath = Microsoft.PowerShell.Management\Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell" @@ -135,6 +140,14 @@ function Get-PSGetLocalAppDataPath { return $script:PSGetAppLocalPath } +function Get-AWSCodeArtifactGalleryName { + return $script:AWSCodeArtifactName +} + +function Get-AWSCodeArtifactGalleryLocation { + return $script:AWSCodeArtifactLocation +} + function Get-NuGetGalleryName { return $script:NuGetGalleryName