Skip to content

Update the resource group tag filter to use server side odata query #11128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 26, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,7 @@ public class GetAzureResourceGroupCmdlet : ResourceManagerCmdletBase
public override void ExecuteCmdlet()
{
Name = Name ?? ResourceIdentifier.FromResourceGroupIdentifier(this.Id).ResourceGroupName;

this.WriteObject(
ResourceManagerSdkClient.FilterResourceGroups(name: this.Name, tag: this.Tag, detailed: false, location: this.Location),
true);
this.WriteObject(ResourceManagerSdkClient.FilterResourceGroups(name: this.Name, tag: this.Tag, detailed: false, location: this.Location), true);
}

}
Expand Down
44 changes: 17 additions & 27 deletions src/Resources/ResourceManager/SdkClient/ResourceManagerSdkClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
using Microsoft.Azure.Management.ResourceManager;
using Microsoft.Azure.Management.ResourceManager.Models;
using Microsoft.Rest.Azure;
using Microsoft.Rest.Azure.OData;
using Microsoft.WindowsAzure.Commands.Common;
using Microsoft.WindowsAzure.Commands.Utilities.Common;
using Newtonsoft.Json;
Expand Down Expand Up @@ -905,11 +906,26 @@ public virtual List<PSResourceGroup> FilterResourceGroups(string name, Hashtable
{
List<PSResourceGroup> result = new List<PSResourceGroup>();

ODataQuery<ResourceGroupFilter> resourceGroupFilter = null;

if (tag != null && tag.Count >= 1)
{
PSTagValuePair tagValuePair = TagsConversionHelper.Create(tag);
if (tagValuePair == null || tag.Count > 1)
{
throw new ArgumentException(ProjectResources.InvalidTagFormat);
}

resourceGroupFilter = string.IsNullOrEmpty(tagValuePair.Value)
? new ODataQuery<ResourceGroupFilter>(rgFilter => rgFilter.TagName == tagValuePair.Name)
: new ODataQuery<ResourceGroupFilter>(rgFilter => rgFilter.TagName == tagValuePair.Name && rgFilter.TagValue == tagValuePair.Value);
}

if (string.IsNullOrEmpty(name) || WildcardPattern.ContainsWildcardCharacters(name))
{
List<ResourceGroup> resourceGroups = new List<ResourceGroup>();

var listResult = ResourceManagementClient.ResourceGroups.List(null);
var listResult = ResourceManagementClient.ResourceGroups.List(odataQuery: resourceGroupFilter);
resourceGroups.AddRange(listResult);

while (!string.IsNullOrEmpty(listResult.NextPageLink))
Expand All @@ -928,32 +944,6 @@ public virtual List<PSResourceGroup> FilterResourceGroups(string name, Hashtable
? resourceGroups.Where(resourceGroup => resourceGroup.Location.EqualsAsLocation(location)).ToList()
: resourceGroups;

// TODO: Replace with server side filtering when available
if (tag != null && tag.Count >= 1)
{
PSTagValuePair tagValuePair = TagsConversionHelper.Create(tag);
if (tagValuePair == null)
{
throw new ArgumentException(ProjectResources.InvalidTagFormat);
}
if (string.IsNullOrEmpty(tagValuePair.Value))
{
resourceGroups =
resourceGroups.Where(rg => rg.Tags != null
&& rg.Tags.Keys.Contains(tagValuePair.Name,
StringComparer.OrdinalIgnoreCase))
.Select(rg => rg).ToList();
}
else
{
resourceGroups =
resourceGroups.Where(rg => rg.Tags != null && rg.Tags.Keys.Contains(tagValuePair.Name,
StringComparer.OrdinalIgnoreCase))
.Where(rg => rg.Tags.Values.Contains(tagValuePair.Value,
StringComparer.OrdinalIgnoreCase))
.Select(rg => rg).ToList();
}
}
result.AddRange(resourceGroups.Select(rg => rg.ToPSResourceGroup()));
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Formatters;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -1153,108 +1152,6 @@ public void GetsAllResourceGroupsWithDetails()
Assert.Equal(resourceGroup4.Name, actual[3].ResourceGroupName);
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void GetsResourceGroupsFilteredByTags()
{
Dictionary<string, string> tag1 = new Dictionary<string, string> { { "tag1", "val1" }, { "tag2", "val2" } };
Dictionary<string, string> tag2 = new Dictionary<string, string> { { "tag1", "valx" } };
Dictionary<string, string> tag3 = new Dictionary<string, string> { { "tag2", "" } };

ResourceGroup resourceGroup1 = new ResourceGroup(location: resourceGroupLocation, name: resourceGroupName + 1, tags: tag1);
ResourceGroup resourceGroup2 = new ResourceGroup(location: resourceGroupLocation, name: resourceGroupName + 2, tags: tag2);
ResourceGroup resourceGroup3 = new ResourceGroup(location: resourceGroupLocation, name: resourceGroupName + 3, tags: tag3);
ResourceGroup resourceGroup4 = new ResourceGroup(location: resourceGroupLocation, name: resourceGroupName + 4);
var listResult = new List<ResourceGroup>() { resourceGroup1, resourceGroup2, resourceGroup3, resourceGroup4 };
var pagableResult = new Page<ResourceGroup>();
pagableResult.SetItemValue(listResult);
resourceGroupMock.Setup(f => f.ListWithHttpMessagesAsync(null, null, new CancellationToken()))
.Returns(Task.Factory.StartNew(() =>
new AzureOperationResponse<IPage<ResourceGroup>>()
{
Body = pagableResult
}));
SetupListForResourceGroupAsync(resourceGroup1.Name, new List<GenericResource>() { CreateGenericResource(null, null, "resource") });
SetupListForResourceGroupAsync(resourceGroup2.Name, new List<GenericResource>() { CreateGenericResource(null, null, "resource") });
SetupListForResourceGroupAsync(resourceGroup3.Name, new List<GenericResource>() { CreateGenericResource(null, null, "resource") });
SetupListForResourceGroupAsync(resourceGroup4.Name, new List<GenericResource>() { CreateGenericResource(null, null, "resource") });

List<PSResourceGroup> groups1 = resourcesClient.FilterResourceGroups(null,
new Hashtable(new Dictionary<string, string> { { "tag1", "val1" } }), false);

Assert.Single(groups1);
Assert.Equal(resourceGroup1.Name, groups1[0].ResourceGroupName);

List<PSResourceGroup> groups2 = resourcesClient.FilterResourceGroups(null,
new Hashtable(new Dictionary<string, string> { { "tag2", "" } }), false);

Assert.Equal(2, groups2.Count);
Assert.Equal(resourceGroup1.Name, groups2[0].ResourceGroupName);
Assert.Equal(resourceGroup3.Name, groups2[1].ResourceGroupName);

List<PSResourceGroup> groups3 = resourcesClient.FilterResourceGroups(null,
new Hashtable(new Dictionary<string, string> { { "Name", "tag3" } }), false);

Assert.Empty(groups3);

List<PSResourceGroup> groups4 = resourcesClient.FilterResourceGroups(null,
new Hashtable(new Dictionary<string, string> { { "TAG1", "val1" } }), false);

Assert.Single(groups4);
Assert.Equal(resourceGroup1.Name, groups4[0].ResourceGroupName);
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void GetsResourceGroupsFilteredByTagsWithDetails()
{
Dictionary<string, string> tag1 = new Dictionary<string, string> { { "tag1", "val1" }, { "tag2", "val2" } };
Dictionary<string, string> tag2 = new Dictionary<string, string> { { "tag1", "valx" } };
Dictionary<string, string> tag3 = new Dictionary<string, string> { { "tag2", "" } };

ResourceGroup resourceGroup1 = new ResourceGroup(location: resourceGroupLocation, name: resourceGroupName + 1, tags: tag1);
ResourceGroup resourceGroup2 = new ResourceGroup(location: resourceGroupLocation, name: resourceGroupName + 2, tags: tag2);
ResourceGroup resourceGroup3 = new ResourceGroup(location: resourceGroupLocation, name: resourceGroupName + 3, tags: tag3);
ResourceGroup resourceGroup4 = new ResourceGroup(location: resourceGroupLocation, name: resourceGroupName + 4);
var listResult = new List<ResourceGroup>() { resourceGroup1, resourceGroup2, resourceGroup3, resourceGroup4 };
var pagableResult = new Page<ResourceGroup>();
pagableResult.SetItemValue(listResult);
resourceGroupMock.Setup(f => f.ListWithHttpMessagesAsync(null, null, new CancellationToken()))
.Returns(Task.Factory.StartNew(() =>
new AzureOperationResponse<IPage<ResourceGroup>>()
{
Body = pagableResult
}));
SetupListForResourceGroupAsync(resourceGroup1.Name, new List<GenericResource>() { CreateGenericResource(null, null, "resource") });
SetupListForResourceGroupAsync(resourceGroup2.Name, new List<GenericResource>() { CreateGenericResource(null, null, "resource") });
SetupListForResourceGroupAsync(resourceGroup3.Name, new List<GenericResource>() { CreateGenericResource(null, null, "resource") });
SetupListForResourceGroupAsync(resourceGroup4.Name, new List<GenericResource>() { CreateGenericResource(null, null, "resource") });

List<PSResourceGroup> groups1 = resourcesClient.FilterResourceGroups(null,
new Hashtable(new Dictionary<string, string> { { "tag1", "val1" } }), true);

Assert.Single(groups1);
Assert.Equal(resourceGroup1.Name, groups1[0].ResourceGroupName);

List<PSResourceGroup> groups2 = resourcesClient.FilterResourceGroups(null,
new Hashtable(new Dictionary<string, string> { { "tag2", "" } }), true);

Assert.Equal(2, groups2.Count);
Assert.Equal(resourceGroup1.Name, groups2[0].ResourceGroupName);
Assert.Equal(resourceGroup3.Name, groups2[1].ResourceGroupName);

List<PSResourceGroup> groups3 = resourcesClient.FilterResourceGroups(null,
new Hashtable(new Dictionary<string, string> { { "tag3", "" } }), true);

Assert.Empty(groups3);

List<PSResourceGroup> groups4 = resourcesClient.FilterResourceGroups(null,
new Hashtable(new Dictionary<string, string> { { "TAG1", "val1" }}), true);

Assert.Single(groups4);
Assert.Equal(resourceGroup1.Name, groups4[0].ResourceGroupName);
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void DeletesResourcesGroup()
Expand Down
22 changes: 17 additions & 5 deletions src/Resources/Resources.Test/ScenarioTests/ResourceGroupTests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ function Test-FindResourceGroup
{
# Test
$actual = New-AzResourceGroup -Name $rgname -Location $location -Tag @{ testtag = "testval" }
$actual2 = New-AzResourceGroup -Name $rgname2 -Location $location -Tag @{ testtag = "testval2" }
$actual2 = New-AzResourceGroup -Name $rgname2 -Location $location -Tag @{ testtag2 = "testval2"; testtag = "test_val" }

$expected1 = Get-AzResourceGroup -Name $rgname
# Assert
Expand Down Expand Up @@ -316,7 +316,19 @@ function Test-FindResourceGroup

$expected6 = Get-AzResourceGroup -Tag @{ testtag2 = $null }
# Assert
Assert-AreEqual @($expected6).Count 0
Assert-AreEqual @($expected6).Count 1

$expected7 = Get-AzResourceGroup -Tag @{ testtag2 = "testval" }
# Assert
Assert-AreEqual @($expected7).Count 0

$expected8 = Get-AzResourceGroup -Tag @{ testtagX = $null }
# Assert
Assert-AreEqual @($expected8).Count 0

$expected9 = Get-AzResourceGroup -Tag @{ testtagY = "testval" }
# Assert
Assert-AreEqual @($expected9).Count 0
}
finally
{
Expand Down Expand Up @@ -357,7 +369,7 @@ function Test-ExportResourceGroup
# Test
New-AzResourceGroup -Name $rgname -Location $rglocation
#[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine")]
$r = New-AzResource -Name $rname -Location "centralus" -Tags @{ testtag = "testval"} -ResourceGroupName $rgname -ResourceType $resourceType -PropertyObject @{"administratorLogin" = "adminuser"; "administratorLoginPassword" = "P@ssword1"} -SkuObject @{ Name = "A0" } -ApiVersion $apiversion -Force
$r = New-AzResource -Name $rname -Location "centralus" -Tags @{ testtag = "testval" } -ResourceGroupName $rgname -ResourceType $resourceType -PropertyObject @{"administratorLogin" = "adminuser"; "administratorLoginPassword" = "P@ssword1"} -SkuObject @{ Name = "A0" } -ApiVersion $apiversion -Force
Assert-AreEqual $r.ResourceGroupName $rgname

$exportOutput = Export-AzResourceGroup -ResourceGroupName $rgname -Force
Expand Down Expand Up @@ -393,11 +405,11 @@ function Test-ExportResourceGroupWithFiltering
New-AzResourceGroup -Name $rgname -Location $rglocation

#[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine")]
$r1 = New-AzResource -Name $rname1 -Location "centralus" -Tags @{ testtag = "testval"} -ResourceGroupName $rgname -ResourceType $resourceType -PropertyObject @{"administratorLogin" = "adminuser"; "administratorLoginPassword" = "P@ssword1"} -SkuObject @{ Name = "A0" } -ApiVersion $apiversion -Force
$r1 = New-AzResource -Name $rname1 -Location "centralus" -Tags @{ testtag = "testval" } -ResourceGroupName $rgname -ResourceType $resourceType -PropertyObject @{"administratorLogin" = "adminuser"; "administratorLoginPassword" = "P@ssword1"} -SkuObject @{ Name = "A0" } -ApiVersion $apiversion -Force
Assert-NotNull $r1.ResourceId

#[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine")]
$r2 = New-AzResource -Name $rname2 -Location "centralus" -Tags @{ testtag = "testval"} -ResourceGroupName $rgname -ResourceType $resourceType -PropertyObject @{"administratorLogin" = "adminuser"; "administratorLoginPassword" = "P@ssword1"} -SkuObject @{ Name = "A0" } -ApiVersion $apiversion -Force
$r2 = New-AzResource -Name $rname2 -Location "centralus" -Tags @{ testtag = "testval" } -ResourceGroupName $rgname -ResourceType $resourceType -PropertyObject @{"administratorLogin" = "adminuser"; "administratorLoginPassword" = "P@ssword1"} -SkuObject @{ Name = "A0" } -ApiVersion $apiversion -Force
Assert-NotNull $r2.ResourceId

$exportOutput = Export-AzResourceGroup -ResourceGroupName $rgname -Force -Resource @($r2.ResourceId) -IncludeParameterDefaultValue -IncludeComments
Expand Down

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/Resources/Resources/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
-->
## Upcoming Release
* Fix for null reference bug in GetAzureRoleAssignmentCommand
* Updated `Get-AzResourceGroup` to perform resource group tag filtering on server-side

## Version 1.11.0
* Refactored template deployment cmdlets
Expand Down