diff --git a/help/Find-PSResource.md b/help/Find-PSResource.md index 82fbce245..e2a9d3371 100644 --- a/help/Find-PSResource.md +++ b/help/Find-PSResource.md @@ -200,7 +200,7 @@ Accept wildcard characters: False ``` ### -Repository -Specifies one or more repository names to search. +Specifies one or more repository names to search, which can include wildcard. If not specified, search will include all currently registered repositories, in order of highest priority, until a repository is found that contains the package. ```yaml @@ -212,7 +212,7 @@ Required: False Position: Named Default value: None Accept pipeline input: False -Accept wildcard characters: False +Accept wildcard characters: True ``` ### -Tag diff --git a/src/PSGet.Format.ps1xml b/src/PSGet.Format.ps1xml index 695dc2813..7b47f3878 100644 --- a/src/PSGet.Format.ps1xml +++ b/src/PSGet.Format.ps1xml @@ -11,6 +11,7 @@ + @@ -19,6 +20,7 @@ Name Version PrereleaseLabel + Repository Description diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs index 34d79f289..8ed8db1cc 100644 --- a/src/code/FindHelper.cs +++ b/src/code/FindHelper.cs @@ -37,7 +37,12 @@ internal class FindHelper private SwitchParameter _includeDependencies = false; private readonly string _psGalleryRepoName = "PSGallery"; private readonly string _psGalleryScriptsRepoName = "PSGalleryScripts"; + private readonly string _psGalleryURL = "https://www.powershellgallery.com/api/v2"; + private readonly string _poshTestGalleryRepoName = "PoshTestGallery"; + private readonly string _poshTestGalleryScriptsRepoName = "PoshTestGalleryScripts"; + private readonly string _poshTestGalleryURL = "https://www.poshtestgallery.com/api/v2"; private bool _isADOFeedRepository; + private bool _repositoryNameContainsWildcard; // NuGet's SearchAsync() API takes a top parameter of 6000, but testing shows for PSGallery // usually a max of around 5990 is returned while more are left to retrieve in a second SearchAsync() call @@ -74,10 +79,23 @@ public IEnumerable FindByResourceName( List repositoriesToSearch; + //determine if repository array of names of repositories input to be searched contains wildcard + if (repository != null) + { + repository = Utils.ProcessNameWildcards(repository, out string[] errorMsgs, out _repositoryNameContainsWildcard); + foreach (string error in errorMsgs) + { + _cmdletPassedIn.WriteError(new ErrorRecord( + new PSInvalidOperationException(error), + "ErrorFilteringNamesForUnsupportedWildcards", + ErrorCategory.InvalidArgument, + this)); + } + } + try { repositoriesToSearch = RepositorySettings.Read(repository, out string[] errorList); - foreach (string error in errorList) { _cmdletPassedIn.WriteError(new ErrorRecord( @@ -97,12 +115,14 @@ public IEnumerable FindByResourceName( yield break; } - // loop through repositoriesToSearch and if PSGallery add it to list with same priority as PSGallery repo + // loop through repositoriesToSearch and if PSGallery or PoshTestGallery add its Scripts endpoint repo + // to list with same priority as PSGallery repo + // This special casing is done to handle PSGallery and PoshTestGallery having 2 endpoints currently for different resources. for (int i = 0; i < repositoriesToSearch.Count; i++) { - if (String.Equals(repositoriesToSearch[i].Name, _psGalleryRepoName, StringComparison.InvariantCultureIgnoreCase)) + if (String.Equals(repositoriesToSearch[i].Url.AbsoluteUri, _psGalleryURL, StringComparison.InvariantCultureIgnoreCase)) { - // for PowerShellGallery, Module and Script resources have different endpoints so separate repositories have to be registered + // special case: for PowerShellGallery, Module and Script resources have different endpoints so separate repositories have to be registered // with those endpoints in order for the NuGet APIs to search across both in the case where name includes '*' // detect if Script repository needs to be added and/or Module repository needs to be skipped @@ -115,11 +135,32 @@ public IEnumerable FindByResourceName( } else if (_type != ResourceType.None && _type == ResourceType.Script) { - _cmdletPassedIn.WriteVerbose("Type Script provided, so add PSGalleryScripts and remove PSGallery (Modules only)"); + _cmdletPassedIn.WriteVerbose("Type Script provided, so add PSGalleryScripts and remove PSGallery (Modules only) from search consideration"); repositoriesToSearch.Insert(i + 1, psGalleryScripts); repositoriesToSearch.RemoveAt(i); // remove PSGallery } + } + else if (String.Equals(repositoriesToSearch[i].Url.AbsoluteUri, _poshTestGalleryURL, StringComparison.InvariantCultureIgnoreCase)) + { + // special case: for PoshTestGallery, Module and Script resources have different endpoints so separate repositories have to be registered + // with those endpoints in order for the NuGet APIs to search across both in the case where name includes '*' + + // detect if Script repository needs to be added and/or Module repository needs to be skipped + Uri poshTestGalleryScriptsUrl = new Uri("https://www.poshtestgallery.com/api/v2/items/psscript/"); + PSRepositoryInfo poshTestGalleryScripts = new PSRepositoryInfo(_poshTestGalleryScriptsRepoName, poshTestGalleryScriptsUrl, repositoriesToSearch[i].Priority, false); + if (_type == ResourceType.None) + { + _cmdletPassedIn.WriteVerbose("Null Type provided, so add PoshTestGalleryScripts repository"); + repositoriesToSearch.Insert(i + 1, poshTestGalleryScripts); + } + else if (_type != ResourceType.None && _type == ResourceType.Script) + { + _cmdletPassedIn.WriteVerbose("Type Script provided, so add PoshTestGalleryScripts and remove PoshTestGallery (Modules only) from search consideration"); + repositoriesToSearch.Insert(i + 1, poshTestGalleryScripts); + repositoriesToSearch.RemoveAt(i); // remove PoshTestGallery + } } + } for (int i = 0; i < repositoriesToSearch.Count && _pkgsLeftToFind.Any(); i++) @@ -203,7 +244,8 @@ public IEnumerable SearchFromRepository( context = new SourceCacheContext(); foreach(PSResourceInfo pkg in SearchAcrossNamesInRepository( - repositoryName: repositoryName, + repositoryName: String.Equals(repositoryUrl.AbsoluteUri, _psGalleryURL, StringComparison.InvariantCultureIgnoreCase) ? _psGalleryRepoName : + (String.Equals(repositoryUrl.AbsoluteUri, _poshTestGalleryURL, StringComparison.InvariantCultureIgnoreCase) ? _poshTestGalleryRepoName : repositoryName), pkgSearchResource: resourceSearch, pkgMetadataResource: resourceMetadata, searchFilter: filter, @@ -309,7 +351,13 @@ private IEnumerable FindFromPackageSourceSearchAPI( } foundPackagesMetadata.AddRange(retrievedPkgs.ToList()); - _pkgsLeftToFind.Remove(pkgName); + + // _pkgsLeftToFind.Remove(pkgName); + + if (!_repositoryNameContainsWildcard) + { + _pkgsLeftToFind.Remove(pkgName); + } } else { @@ -326,7 +374,6 @@ private IEnumerable FindFromPackageSourceSearchAPI( IEnumerable wildcardPkgs = null; try { - _cmdletPassedIn.WriteVerbose("searching with name: " + pkgName); // SearchAsync() API returns the latest version only for all packages that match the wild-card name wildcardPkgs = pkgSearchResource.SearchAsync( searchTerm: pkgName, @@ -369,18 +416,28 @@ private IEnumerable FindFromPackageSourceSearchAPI( foundPackagesMetadata.AddRange(wildcardPkgs.Where( p => nameWildcardPattern.IsMatch(p.Identity.Id)).ToList()); - // if the Script Uri endpoint still needs to be searched, don't remove the wildcard name from _pkgsLeftToFind - // PSGallery + Type == null -> M, S - // PSGallery + Type == M -> M - // PSGallery + Type == S -> S (but PSGallery would be skipped early on, only PSGalleryScripts would be checked) - // PSGallery + Type == C -> M - // PSGallery + Type == D -> M - - bool needToCheckPSGalleryScriptsRepo = String.Equals(repositoryName, _psGalleryRepoName, StringComparison.InvariantCultureIgnoreCase) && _type == ResourceType.None; - if (foundPackagesMetadata.Any() && !needToCheckPSGalleryScriptsRepo) + if (!_repositoryNameContainsWildcard) { - _pkgsLeftToFind.Remove(pkgName); + // if the Script Uri endpoint still needs to be searched, don't remove the wildcard name from _pkgsLeftToFind + // PSGallery + Type == null -> M, S + // PSGallery + Type == M -> M + // PSGallery + Type == S -> S (but PSGallery would be skipped early on, only PSGalleryScripts would be checked) + // PSGallery + Type == C -> M + // PSGallery + Type == D -> M + + if (String.Equals(repositoryName, _psGalleryRepoName, StringComparison.InvariantCultureIgnoreCase) || + String.Equals(repositoryName, _poshTestGalleryRepoName, StringComparison.InvariantCultureIgnoreCase)) + { + if (foundPackagesMetadata.Any() && _type != ResourceType.None) + { + _pkgsLeftToFind.Remove(pkgName); + } + } + } + + // if repository names did contain wildcard, we want to do an exhaustive search across all the repositories + // which matched the input repository name search term. } if (foundPackagesMetadata.Count == 0) diff --git a/src/code/FindPSResource.cs b/src/code/FindPSResource.cs index f317c4280..464fddc3d 100644 --- a/src/code/FindPSResource.cs +++ b/src/code/FindPSResource.cs @@ -176,26 +176,21 @@ private void ProcessResourceNameParameterSet() { if (!MyInvocation.BoundParameters.ContainsKey(nameof(Name))) { - // TODO: Add support for Tag and Type parameters without Name parameter being specified. - if (MyInvocation.BoundParameters.ContainsKey(nameof(Type)) || MyInvocation.BoundParameters.ContainsKey(nameof(Tag))) + // only cases where Name is allowed to not be specified is if Type or Tag parameters are + if (!MyInvocation.BoundParameters.ContainsKey(nameof(Type)) && !MyInvocation.BoundParameters.ContainsKey(nameof(Tag))) { ThrowTerminatingError( new ErrorRecord( - new PSNotImplementedException("Search by Tag or Type parameter is not yet implemented."), - "TagTypeSearchNotYetImplemented", - ErrorCategory.NotImplemented, + new PSInvalidOperationException("Name parameter must be provided."), + "NameParameterNotProvided", + ErrorCategory.InvalidOperation, this)); } - ThrowTerminatingError( - new ErrorRecord( - new PSInvalidOperationException("Name parameter must be provided."), - "NameParameterNotProvided", - ErrorCategory.InvalidOperation, - this)); + Name = new string[] {"*"}; } - - var namesToSearch = Utils.ProcessNameWildcards(Name, out string[] errorMsgs, out bool nameContainsWildcard); + + Name = Utils.ProcessNameWildcards(Name, out string[] errorMsgs, out bool nameContainsWildcard); foreach (string error in errorMsgs) { @@ -208,21 +203,10 @@ private void ProcessResourceNameParameterSet() // this catches the case where Name wasn't passed in as null or empty, // but after filtering out unsupported wildcard names there are no elements left in namesToSearch - if (namesToSearch.Length == 0) - { - return; - } - - if (String.Equals(namesToSearch[0], "*", StringComparison.InvariantCultureIgnoreCase)) + if (Name.Length == 0) { - // WriteVerbose("Package names were detected to be (or contain an element equal to): '*', so all packages will be updated"); - WriteError(new ErrorRecord( - new PSInvalidOperationException("-Name '*' is not supported for Find-PSResource so all Name entries will be discarded."), - "NameEqualsWildcardIsNotSupported", - ErrorCategory.InvalidArgument, - this)); return; - } + } FindHelper findHelper = new FindHelper(_cancellationToken, this); List foundPackages = new List(); @@ -241,7 +225,7 @@ private void ProcessResourceNameParameterSet() } foreach (var uniquePackageVersion in foundPackages.GroupBy( - m => new {m.Name, m.Version}).Select( + m => new {m.Name, m.Version, m.Repository}).Select( group => group.First()).ToList()) { WriteObject(uniquePackageVersion); diff --git a/test/FindPSResource.Tests.ps1 b/test/FindPSResource.Tests.ps1 index 57ab7a23b..4e82e383b 100644 --- a/test/FindPSResource.Tests.ps1 +++ b/test/FindPSResource.Tests.ps1 @@ -9,6 +9,8 @@ Describe 'Test Find-PSResource for Module' { $TestGalleryName = Get-PoshTestGalleryName $PSGalleryName = Get-PSGalleryName $NuGetGalleryName = Get-NuGetGalleryName + $testModuleName = "testmodule" + $testScriptName = "test_script" $commandName = "Get-TargetResource" $dscResourceName = "SystemLocale" $parentModuleName = "SystemLocaleDsc" @@ -41,7 +43,8 @@ Describe 'Test Find-PSResource for Module' { $res = Find-PSResource -Name "AzureS*" -Repository $PSGalleryName $res.Count | Should -BeGreaterThan 1 # should find Module and Script resources - foreach ($item in $res) { + foreach ($item in $res) + { if ($item.Type -eq "Script") { $foundScript = $true @@ -51,10 +54,62 @@ Describe 'Test Find-PSResource for Module' { $foundScript | Should -BeTrue } - It "should not find resources given Name that equals wildcard, '*'" { - Find-PSResource -Name "*" -ErrorVariable err -ErrorAction SilentlyContinue - $err.Count | Should -Not -Be 0 - $err[0].FullyQualifiedErrorId | Should -BeExactly "NameEqualsWildcardIsNotSupported,Microsoft.PowerShell.PowerShellGet.Cmdlets.FindPSResource" + It "should find all resources given Name that equals wildcard, '*'" { + $foundPreview = $False + $foundTestScript = $False + $foundTestModule = $False + $res = Find-PSResource -Name "*" -Repository $TestGalleryName + #should find Module and Script resources + foreach ($item in $res) + { + if ($item.Name -eq $testModuleName) + { + $foundTestModule = $True + } + + if ($item.Name -eq $testScriptName) + { + $foundTestScript = $True + } + + if(-not [string]::IsNullOrEmpty($item.PrereleaseLabel)) + { + $foundPreview = $True + } + } + + $foundPreview | Should -Be $False + $foundTestScript | Should -Be $True + $foundTestModule | Should -Be $True + } + + It "should find all resources (including prerelease) given Name that equals wildcard, '*' and Prerelease parameter" { + $foundPreview = $False + $foundTestScript = $False + $foundTestModule = $False + $res = Find-PSResource -Name "*" -Prerelease -Repository $TestGalleryName + #should find Module and Script resources + foreach ($item in $res) + { + if ($item.Name -eq $testModuleName) + { + $foundTestModule = $True + } + + if ($item.Name -eq $testScriptName) + { + $foundTestScript = $True + } + + if(-not [string]::IsNullOrEmpty($item.PrereleaseLabel)) + { + $foundPreview = $True + } + } + + $foundPreview | Should -Be $True + $foundTestScript | Should -Be $True + $foundTestModule | Should -Be $True } It "find resource given Name from V3 endpoint repository (NuGetGallery)" { @@ -194,7 +249,21 @@ Describe 'Test Find-PSResource for Module' { } } - It "find resuources given Tag parameter" { + It "find all resources of Type Module when Type parameter set is used" { + $foundScript = $False + $res = Find-PSResource -Type Module -Repository $PSGalleryName + $res.Count | Should -BeGreaterThan 1 + foreach ($item in $res) { + if ($item.Type -eq "Script") + { + $foundScript = $True + } + } + + $foundScript | Should -Be $False + } + + It "find resources given Tag parameter" { $resWithEitherExpectedTag = @("NetworkingDsc", "DSCR_FileContent", "SS.PowerShell") $res = Find-PSResource -Name "NetworkingDsc", "HPCMSL", "DSCR_FileContent", "SS.PowerShell", "PowerShellGet" -Tag "Dsc", "json" -Repository $PSGalleryName foreach ($item in $res) { @@ -202,6 +271,29 @@ Describe 'Test Find-PSResource for Module' { } } + It "find all resources with specified tag given Tag property" { + $foundTestModule = $False + $foundTestScript = $False + $tagToFind = "Tag1" + $res = Find-PSResource -Tag $tagToFind -Repository $TestGalleryName + foreach ($item in $res) { + $item.Tags -contains $tagToFind | Should -Be $True + + if ($item.Name -eq $testModuleName) + { + $foundTestModule = $True + } + + if ($item.Name -eq $testScriptName) + { + $foundTestScript = $True + } + } + + $foundTestModule | Should -Be $True + $foundTestScript | Should -Be $True + } + It "find resource with IncludeDependencies parameter" { $res = Find-PSResource -Name "Az.Compute" -IncludeDependencies -Repository $PSGalleryName $isDependencyNamePresent = $False