From d0fb2f6f209a7925a95a62647518d06ad3034eba Mon Sep 17 00:00:00 2001 From: fenwickt <69487545+fenwickt@users.noreply.github.com> Date: Wed, 9 Jun 2021 19:31:09 -0700 Subject: [PATCH] Fix for output type and null subscriptions collection in ResourceGraph (#15135) * Fixed null subscriptions exception, added change log * Added output type fix and updated tests Changes from PR https://github.com/Azure/azure-powershell/pull/15125 * regenerated ps1xml, updated examples * Updated md file for the output type and changelog * added breaking change issues csv after rebasing with master * added parenthesses to csv to fix parsing issue Co-authored-by: makharch --- .../ScenarioTests/ResourceGraphQueryTests.ps1 | 18 ++++----- .../ResourceGraph/Az.ResourceGraph.psd1 | 4 +- src/ResourceGraph/ResourceGraph/ChangeLog.md | 2 + .../Cmdlets/SearchAzureRmGraph.cs | 8 ++-- ...ets.ResourceGraph.generated.format.ps1xml} | 15 +++---- .../Models/PSResourceGraphResponse.cs | 40 ++++++++++++++++++- .../ResourceGraph/help/Search-AzGraph.md | 27 +++++++++---- .../Az.ResourceGraph/BreakingChangeIssues.csv | 2 + 8 files changed, 81 insertions(+), 35 deletions(-) rename src/ResourceGraph/ResourceGraph/{ResourceGraph.format.ps1xml => Microsoft.Azure.PowerShell.Cmdlets.ResourceGraph.generated.format.ps1xml} (64%) create mode 100644 tools/StaticAnalysis/Exceptions/Az.ResourceGraph/BreakingChangeIssues.csv diff --git a/src/ResourceGraph/ResourceGraph.Test/ScenarioTests/ResourceGraphQueryTests.ps1 b/src/ResourceGraph/ResourceGraph.Test/ScenarioTests/ResourceGraphQueryTests.ps1 index baa48a3dfb79..71cdff1996e4 100644 --- a/src/ResourceGraph/ResourceGraph.Test/ScenarioTests/ResourceGraphQueryTests.ps1 +++ b/src/ResourceGraph/ResourceGraph.Test/ScenarioTests/ResourceGraphQueryTests.ps1 @@ -19,8 +19,8 @@ Run simple query function Search-AzureRmGraph-Query { $queryResult = Search-AzGraph 'Resources | where tags != "" | project id, tags, properties | limit 2' - - Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse $queryResult + + Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse[PSObject] $queryResult Assert-Null $queryResult.SkipToken Assert-IsInstance System.Collections.Generic.List[PSObject] $queryResult.Data Assert-AreEqual 2 $queryResult.Data.Count @@ -55,7 +55,7 @@ function Search-AzureRmGraph-PagedQuery # Page size was artificially set to 2 rows $queryResult = Search-AzGraph "project id" -First 3 -Skip 2 - Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse $queryResult + Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse[PSObject] $queryResult Assert-IsInstance System.String $queryResult.SkipToken Assert-IsInstance System.Collections.Generic.List[PSObject] $queryResult.Data Assert-AreEqual 3 $queryResult.Data.Count @@ -95,17 +95,17 @@ function Search-AzureRmGraph-Subscriptions $queryResultOneSub = Search-AzGraph $query -Subscription $testSubId $queryResultMultipleSubs = Search-AzGraph $query -Subscription @($testSubId, $nonExsitentTestSubId) - Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse $queryResultSubsFromContext + Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse[PSObject] $queryResultSubsFromContext Assert-Null $queryResultSubsFromContext.SkipToken Assert-IsInstance System.Collections.Generic.List[PSObject] $queryResultSubsFromContext.Data Assert-AreEqual $testSubId $queryResultSubsFromContext.Data.subscriptionId - Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse $queryResultOneSub + Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse[PSObject] $queryResultOneSub Assert-Null $queryResultOneSub.SkipToken Assert-IsInstance System.Collections.Generic.List[PSObject] $queryResultOneSub.Data Assert-AreEqual $testSubId $queryResultOneSub.Data.subscriptionId - Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse $queryResultMultipleSubs + Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse[PSObject] $queryResultMultipleSubs Assert-Null $queryResultMultipleSubs.SkipToken Assert-IsInstance System.Collections.Generic.List[PSObject] $queryResultMultipleSubs.Data Assert-AreEqual $testSubId $queryResultMultipleSubs.Data.subscriptionId @@ -126,12 +126,12 @@ function Search-AzureRmGraph-ManagementGroups $queryResultOneMg = Search-AzGraph $query -ManagementGroup $testMgId1 $queryResultMultipleMgs = Search-AzGraph $query -ManagementGroup @($testMgId1, $testMgId2, $nonExistentTestMgId) -AllowPartialScope - Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse $queryResultOneMg + Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse[PSObject] $queryResultOneMg Assert-Null $queryResultOneMg.SkipToken Assert-IsInstance System.Collections.Generic.List[PSObject] $queryResultOneMg.Data Assert-AreEqual $testSubId $queryResultOneMg.Data.subscriptionId - Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse $queryResultMultipleMgs + Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse[PSObject] $queryResultMultipleMgs Assert-Null $queryResultMultipleMgs.SkipToken Assert-IsInstance System.Collections.Generic.List[PSObject] $queryResultMultipleMgs.Data Assert-AreEqual $testSubId $queryResultMultipleMgs.Data.subscriptionId @@ -145,7 +145,7 @@ function Search-AzureRmGraph-SkipTokenQuery { $queryResult = Search-AzGraph "project id, properties" -SkipToken "ew0KICAiJGlkIjogIjEiLA0KICAiTWF4Um93cyI6IDMsDQogICJSb3dzVG9Ta2lwIjogMywNCiAgIkt1c3RvQ2x1c3RlclVybCI6ICJodHRwczovL2FyZy1ldXMtc2l4LXNmLmFyZy5jb3JlLndpbmRvd3MubmV0Ig0KfQ==" - Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse $queryResult + Assert-IsInstance Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse[PSObject] $queryResult Assert-IsInstance System.String $queryResult.SkipToken Assert-IsInstance System.Collections.Generic.List[PSObject] $queryResult.Data Assert-AreEqual 3 $queryResult.Data.Count diff --git a/src/ResourceGraph/ResourceGraph/Az.ResourceGraph.psd1 b/src/ResourceGraph/ResourceGraph/Az.ResourceGraph.psd1 index 29b3d7e14b69..0c34a6f1dbe3 100644 --- a/src/ResourceGraph/ResourceGraph/Az.ResourceGraph.psd1 +++ b/src/ResourceGraph/ResourceGraph/Az.ResourceGraph.psd1 @@ -66,11 +66,11 @@ RequiredAssemblies = 'Microsoft.Azure.Management.ResourceGraph.dll', # TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module -FormatsToProcess = 'ResourceGraph.format.ps1xml', +FormatsToProcess = 'Microsoft.Azure.PowerShell.Cmdlets.ResourceGraph.generated.format.ps1xml', 'ResourceGraph.Autorest\Az.ResourceGraph.format.ps1xml' # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess -NestedModules = @('Az.ResourceGraph.psm1', +NestedModules = @('Microsoft.Azure.PowerShell.Cmdlets.ResourceGraph.dll', 'ResourceGraph.Autorest\Az.ResourceGraph.psm1') # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. diff --git a/src/ResourceGraph/ResourceGraph/ChangeLog.md b/src/ResourceGraph/ResourceGraph/ChangeLog.md index b0c35744ab08..2c6fb3a64f1e 100644 --- a/src/ResourceGraph/ResourceGraph/ChangeLog.md +++ b/src/ResourceGraph/ResourceGraph/ChangeLog.md @@ -18,6 +18,8 @@ - Additional information about change #1 --> ## Upcoming Release +* Fixed the output print issue for `Search-AzGraph` by updating the output type to Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse`1[[System.Management.Automation.PSObject]]. +* Fixed the issue when Search-AzGraph fails if no subscriptions are stored in the context. ## Version 0.10.0 * Changed output of `Search-AzGraph` to PSResourceGraphResponse which wrapped previous output under Data property. diff --git a/src/ResourceGraph/ResourceGraph/Cmdlets/SearchAzureRmGraph.cs b/src/ResourceGraph/ResourceGraph/Cmdlets/SearchAzureRmGraph.cs index 674d722d56da..a8d7a79313ca 100644 --- a/src/ResourceGraph/ResourceGraph/Cmdlets/SearchAzureRmGraph.cs +++ b/src/ResourceGraph/ResourceGraph/Cmdlets/SearchAzureRmGraph.cs @@ -27,7 +27,7 @@ namespace Microsoft.Azure.Commands.ResourceGraph.Cmdlets /// Search-AzGraph cmdlet /// /// - [Cmdlet(VerbsCommon.Search, ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "Graph", DefaultParameterSetName = "SubscriptionScopedQuery"), OutputType(typeof(PSResourceGraphResponse))] + [Cmdlet(VerbsCommon.Search, ResourceManager.Common.AzureRMConstants.AzureRMPrefix + "Graph", DefaultParameterSetName = "SubscriptionScopedQuery"), OutputType(typeof(PSResourceGraphResponse))] public class SearchAzureRmGraph : ResourceGraphBaseCmdlet { /// @@ -150,7 +150,7 @@ public override void ExecuteCmdlet() IList subscriptions = null; if (managementGroups == null) { - subscriptions = this.GetSubscriptions().ToList(); + subscriptions = this.GetSubscriptions()?.ToList(); if (subscriptions != null && subscriptions.Count > SubscriptionLimit) { subscriptions = subscriptions.Take(SubscriptionLimit).ToList(); @@ -160,7 +160,7 @@ public override void ExecuteCmdlet() } } - var psResourceGraphResponse = new PSResourceGraphResponse(); + var psResourceGraphResponse = new PSResourceGraphResponse(); QueryResponse response = null; var resultTruncated = false; @@ -262,7 +262,7 @@ private IEnumerable GetSubscriptions() } var accountSubscriptions = this.DefaultContext.Account.GetSubscriptions(); - if (accountSubscriptions.Length > 0) + if (accountSubscriptions?.Length > 0) { return accountSubscriptions; } diff --git a/src/ResourceGraph/ResourceGraph/ResourceGraph.format.ps1xml b/src/ResourceGraph/ResourceGraph/Microsoft.Azure.PowerShell.Cmdlets.ResourceGraph.generated.format.ps1xml similarity index 64% rename from src/ResourceGraph/ResourceGraph/ResourceGraph.format.ps1xml rename to src/ResourceGraph/ResourceGraph/Microsoft.Azure.PowerShell.Cmdlets.ResourceGraph.generated.format.ps1xml index c37333a6aec6..376e897d9630 100644 --- a/src/ResourceGraph/ResourceGraph/ResourceGraph.format.ps1xml +++ b/src/ResourceGraph/ResourceGraph/Microsoft.Azure.PowerShell.Cmdlets.ResourceGraph.generated.format.ps1xml @@ -1,10 +1,10 @@ - + - Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse + Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse`1[[System.Management.Automation.PSObject]] - Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse + Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse`1[[System.Management.Automation.PSObject]] @@ -12,19 +12,14 @@ SkipToken - - - $_.SkipToken -ne $null - - Data - + - + \ No newline at end of file diff --git a/src/ResourceGraph/ResourceGraph/Models/PSResourceGraphResponse.cs b/src/ResourceGraph/ResourceGraph/Models/PSResourceGraphResponse.cs index 96e81b15eb35..31534deadb6e 100644 --- a/src/ResourceGraph/ResourceGraph/Models/PSResourceGraphResponse.cs +++ b/src/ResourceGraph/ResourceGraph/Models/PSResourceGraphResponse.cs @@ -14,17 +14,53 @@ namespace Microsoft.Azure.Commands.ResourceGraph.Models { + using System.Collections; using System.Collections.Generic; - using System.Management.Automation; using Microsoft.WindowsAzure.Commands.Common.Attributes; - public class PSResourceGraphResponse + public class PSResourceGraphResponse : IList { [Ps1Xml(Target = ViewControl.List)] public string SkipToken { get; set; } [Ps1Xml(Target = ViewControl.List)] public IList Data { get; set; } + public PSObject this[int index] + { + get => Data[index]; + set => Data[index] = value; + } + public IEnumerator GetEnumerator() + { + return Data.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public bool IsReadOnly => Data.IsReadOnly; + + public int Count => Data.Count; + + public void Add(PSObject value) => Data.Add(value); + + public void Clear() => Data.Clear(); + + public bool Contains(PSObject value) => Data.Contains(value); + + public void CopyTo(PSObject[] array, int index) => Data.CopyTo(array, index); + + public int IndexOf(PSObject value) => Data.IndexOf(value); + + public void Insert(int index, PSObject value) => Data.Insert(index, value); + + public void Remove(PSObject value) => Data.Remove(value); + + public void RemoveAt(int index) => Data.RemoveAt(index); + + bool ICollection.Remove(PSObject item) => Data.Remove(item); } } diff --git a/src/ResourceGraph/ResourceGraph/help/Search-AzGraph.md b/src/ResourceGraph/ResourceGraph/help/Search-AzGraph.md index cd4d771d943f..5b06bda9f2b2 100644 --- a/src/ResourceGraph/ResourceGraph/help/Search-AzGraph.md +++ b/src/ResourceGraph/ResourceGraph/help/Search-AzGraph.md @@ -39,12 +39,14 @@ name : nt type : microsoft.compute/virtualmachinescalesets location : eastus tags : @{resourceType=Service Fabric; clusterName=gov-art-int-nt-a} +ResourceId : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/Service-INT-a/providers/Microsoft.Compute/virtualMachineScaleSets/nt id : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/Service-INT-a/providers/Microsoft.EventGrid/topics/egtopic-1 name : egtopic-1 type : microsoft.eventgrid/topics location : westus2 tags : +ResourceId : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/Service-INT-a/providers/Microsoft.EventGrid/topics/egtopic-1 ``` Simple resources query requesting a subset of resource fields. @@ -64,21 +66,28 @@ A complex query on resources featuring field selection, filtering and summarizin ### Example 3 ```powershell -PS C:\> Search-AzGraph -Query 'project id, name' -SkipToken 'skiptokenvaluefromthepreviousquery==' +PS C:\> $response = Search-AzGraph -Query "project id, name, type, location" -First 2 +PS C:\> Search-AzGraph -Query "project id, name, type, location" -SkipToken $response.SkipToken -id : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/Service-INT-b/providers/Microsoft.Compute/virtualMachineScaleSets/nt2 -name : nt2 +id : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/test/providers/Microsoft.Network/networkInterfaces/17ni +name : 17ni +type : microsoft.network/networkinterfaces +location : westeurope +ResourceId : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/test/providers/Microsoft.Network/networkInterfaces/17ni -id : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/Service-INT-b/providers/Microsoft.EventGrid/topics/egtopic-2 -name : egtopic-2 +id : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/test/providers/Microsoft.Network/networkSecurityGroups/17nsg +name : 17nsg +type : microsoft.network/networksecuritygroups +location : westeurope +ResourceId : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/test/providers/Microsoft.Network/networkSecurityGroups/17nsg ``` -A query with the skip token passed from the previous query results +A query with the skip token passed from the previous query results. Please note that keeping id in the results is mandatory to get back a skip token. ### Example 4 ```powershell -PS C:\> Search-AzGraph -Query 'project id, name, type, location, tags' -First 2 -ManagementGroup 'MyManagementGroupId' -AllowPartialScope +PS C:\> Search-AzGraph -Query "project id, name, type, location, tags" -First 2 -ManagementGroup MyManagementGroupId -AllowPartialScope id : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/Service-INT-a/providers/Microsoft.Compute/virtualMachineScaleSets/nt @@ -86,12 +95,14 @@ name : nt type : microsoft.compute/virtualmachinescalesets location : eastus tags : @{resourceType=Service Fabric; clusterName=gov-art-int-nt-a} +ResourceId : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/Service-INT-a/providers/Microsoft.Compute/virtualMachineScaleSets/nt id : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/Service-INT-a/providers/Microsoft.EventGrid/topics/egtopic-1 name : egtopic-1 type : microsoft.eventgrid/topics location : westus2 tags : +ResourceId : /subscriptions/1ef51df4-f8a9-4b69-9919-1ef51df4eff6/resourceGroups/Service-INT-a/providers/Microsoft.EventGrid/topics/egtopic-1 ``` A query scoped to the management group that allows the query to succeed with partial scope result if MyManagementGroupId has more than N subscriptions underneath. @@ -229,7 +240,7 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ## OUTPUTS -### Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse +### Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse`1[[System.Management.Automation.PSObject]] ## NOTES diff --git a/tools/StaticAnalysis/Exceptions/Az.ResourceGraph/BreakingChangeIssues.csv b/tools/StaticAnalysis/Exceptions/Az.ResourceGraph/BreakingChangeIssues.csv new file mode 100644 index 000000000000..ef3ddfe731ab --- /dev/null +++ b/tools/StaticAnalysis/Exceptions/Az.ResourceGraph/BreakingChangeIssues.csv @@ -0,0 +1,2 @@ +"AssemblyFileName","ClassName","Target","Severity","ProblemId","Description","Remediation" +"Az.ResourceGraph","Microsoft.Azure.Commands.ResourceGraph.Cmdlets.SearchAzureRmGraph","Search-AzGraph","0","1020","The cmdlet 'Search-AzGraph' no longer has output type 'Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse'.","Make cmdlet 'Search-AzGraph' return type 'Microsoft.Azure.Commands.ResourceGraph.Models.PSResourceGraphResponse'."