Skip to content

Commit 036d75f

Browse files
authored
Merge pull request #6163 from vladimir-shcherbakov/iss#5415
Tab completer for existing resources (Iss #5415)
2 parents 31ab972 + 12df6d2 commit 036d75f

File tree

12 files changed

+343
-0
lines changed

12 files changed

+343
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Management.Automation;
5+
using System.Threading;
6+
using Microsoft.Azure.Commands.Common.Authentication;
7+
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
8+
using Microsoft.Azure.Management.Internal.Resources;
9+
using Microsoft.Azure.Management.Internal.Resources.Models;
10+
using Microsoft.Rest.Azure.OData;
11+
12+
namespace Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters
13+
{
14+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
15+
public class ResourceIdCompleterAttribute : ArgumentCompleterAttribute
16+
{
17+
private static readonly object Lock = new object();
18+
19+
/// <summary>
20+
/// Consturctor
21+
/// </summary>
22+
/// <param name="resourceType">Azure recource type</param>
23+
public ResourceIdCompleterAttribute(string resourceType)
24+
: base(CreateScriptBlock(resourceType))
25+
{}
26+
27+
public static ScriptBlock CreateScriptBlock(string resourceType)
28+
{
29+
string script = "param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)\n" +
30+
$"$resourceType = \"{resourceType}\"\n" +
31+
"$resourceIds = [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceIdCompleterAttribute]::GetResourceIds($resourceType)\n" +
32+
"$resourceIds | Where-Object { $_ -Like \"*$wordToComplete*\" } | Sort-Object | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }";
33+
var scriptBlock = ScriptBlock.Create(script);
34+
return scriptBlock;
35+
}
36+
37+
private class CacheItem
38+
{
39+
public DateTime Timestamp { get; set; }
40+
public IEnumerable<string> ResourceInfoList { get; set; }
41+
}
42+
43+
private static readonly IDictionary<int, CacheItem> Cache = new Dictionary<int, CacheItem>();
44+
45+
public static TimeSpan TimeToUpdate { get; set; } = TimeSpan.FromMinutes(5);
46+
47+
public static TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(3);
48+
49+
public static IEnumerable<string> GetResourceIds(string resourceType)
50+
{
51+
lock (Lock)
52+
{
53+
var context = AzureRmProfileProvider.Instance.Profile.DefaultContext;
54+
var contextHash = HashContext(context, resourceType);
55+
var cacheItem = Cache.ContainsKey(contextHash) ? Cache[contextHash] : null;
56+
57+
if (cacheItem != null && DateTime.Now.Subtract(cacheItem.Timestamp).CompareTo(TimeToUpdate) < 0)
58+
{
59+
return Cache[contextHash].ResourceInfoList;
60+
}
61+
62+
var client = AzureSession.Instance.ClientFactory.CreateArmClient<ResourceManagementClient>(context, AzureEnvironment.Endpoint.ResourceManager);
63+
64+
var odata = new ODataQuery<GenericResourceFilter>(r => r.ResourceType == resourceType);
65+
66+
IEnumerable<string> resourceInfoList = new List<string>();
67+
68+
try
69+
{
70+
using (var cancelSource = new CancellationTokenSource())
71+
{
72+
var task = client.Resources.ListAsync(odata, cancelSource.Token);
73+
74+
if (!task.Wait(RequestTimeout))
75+
{
76+
cancelSource.Cancel();
77+
return resourceInfoList;
78+
}
79+
80+
var result = task.Result;
81+
resourceInfoList = result
82+
.Select(r => r.Id)
83+
.ToList();
84+
}
85+
}
86+
catch (Exception)
87+
{
88+
return resourceInfoList;
89+
}
90+
91+
if (cacheItem != null)
92+
{
93+
cacheItem.Timestamp = DateTime.Now;
94+
cacheItem.ResourceInfoList = resourceInfoList;
95+
Cache[contextHash] = cacheItem;
96+
}
97+
else
98+
{
99+
Cache.Add(contextHash, new CacheItem
100+
{
101+
Timestamp = DateTime.Now,
102+
ResourceInfoList = resourceInfoList
103+
});
104+
}
105+
106+
return resourceInfoList;
107+
}
108+
}
109+
110+
private static int HashContext(IAzureContext context, string resourceType)
111+
{
112+
return (context.Account.Id + context.Environment.Name + context.Subscription.Id + context.Tenant.Id + resourceType).GetHashCode();
113+
}
114+
}
115+
}

src/ResourceManager/Common/Commands.ResourceManager.Common/Commands.ResourceManager.Common.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
<ItemGroup>
5757
<Compile Include="AccessTokenExtensions.cs" />
5858
<Compile Include="ArgumentCompleters\PSArgumentCompleter.cs" />
59+
<Compile Include="ArgumentCompleters\ResourceIdCompleter.cs" />
5960
<Compile Include="ArgumentCompleters\ResourceTypeCompleter.cs" />
6061
<Compile Include="ArgumentCompleters\ScopeCompleter.cs" />
6162
<Compile Include="AzureRmCmdlet.cs" />

src/ResourceManager/Compute/ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
## Current Release
2121

2222
## Version 5.1.1
23+
* ResourceId tab completer applied to the cmdelts top level resource id parameters if any.
2324
* `Get-AzureRmVmDiskEncryptionStatus` fixes an issue observed for VMs with no data disks
2425
* Update Compute client library version to fix following cmdlets
2526
- Grant-AzureRmDiskAccess

src/ResourceManager/Compute/Commands.Compute/VirtualMachine/Action/RestartAzureVMCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public class RestartAzureVMCommand : VirtualMachineBaseCmdlet
5858
ValueFromPipelineByPropertyName = true,
5959
HelpMessage = "The resource group name.")]
6060
[ValidateNotNullOrEmpty]
61+
[ResourceIdCompleter("Microsoft.Compute/virtualMachines")]
6162
public string Id { get; set; }
6263

6364

src/ResourceManager/Compute/Commands.Compute/VirtualMachine/Action/SetAzureVMCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public class SetAzureVMCommand : VirtualMachineBaseCmdlet
5858
ValueFromPipelineByPropertyName = true,
5959
HelpMessage = "The resource group name.")]
6060
[ValidateNotNullOrEmpty]
61+
[ResourceIdCompleter("Microsoft.Compute/virtualMachines")]
6162
public string Id { get; set; }
6263

6364
[Parameter(

src/ResourceManager/Compute/Commands.Compute/VirtualMachine/Action/VirtualMachineActionBaseCmdlet.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public abstract class VirtualMachineActionBaseCmdlet : VirtualMachineBaseCmdlet
3939
ValueFromPipelineByPropertyName = true,
4040
HelpMessage = "The resource group name.")]
4141
[ValidateNotNullOrEmpty]
42+
[ResourceIdCompleter("Microsoft.Compute/virtualMachines")]
4243
public string Id { get; set; }
4344

4445
[Parameter(Mandatory = false, HelpMessage = "Run cmdlet in the background")]

src/ResourceManager/Compute/Commands.Compute/VirtualMachine/Operation/UpdateAzureVMCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public class UpdateAzureVMCommand : VirtualMachineBaseCmdlet
6262
ParameterSetName = IdParameterSet,
6363
ValueFromPipelineByPropertyName = true,
6464
HelpMessage = "The resource group name.")]
65+
[ResourceIdCompleter("Microsoft.Compute/virtualMachines")]
6566
public string Id { get; set; }
6667

6768
[Alias("VMProfile")]

src/ResourceManager/Profile/ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
## Version 5.3.0
2323
* Updated error messages for Enable-AzureRmContextAutoSave
2424
* Create a context for each subscription when running `Connect-AzureRmAccount` with no previous context
25+
* Resource Id completer added.
2526

2627
## Version 5.2.0
2728
* Added the following three values to the telemetry:

src/ResourceManager/Profile/Commands.Profile.Test/ArgumentCompleterTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,12 @@ public void TestResourceGroupCompleter()
4848
{
4949
ProfileController.NewInstance.RunPsTest(xunitLogger, "72f988bf-86f1-41af-91ab-2d7cd011db47", "Test-ResourceGroupCompleter");
5050
}
51+
52+
[Fact]
53+
[Trait(Category.AcceptanceType, Category.CheckIn)]
54+
public void TestResourceIdCompleter()
55+
{
56+
ProfileController.NewInstance.RunPsTest(xunitLogger, "72f988bf-86f1-41af-91ab-2d7cd011db47", "Test-ResourceIdCompleter");
57+
}
5158
}
5259
}

src/ResourceManager/Profile/Commands.Profile.Test/ArgumentCompleterTests.ps1

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,27 @@ function Test-ResourceGroupCompleter
3939
$resourceGroups = [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceGroupCompleterAttribute]::GetResourceGroups(-1)
4040
$expectResourceGroups = Get-AzureRmResourceGroup | ForEach-Object {$_.ResourceGroupName}
4141
Assert-AreEqualArray $resourceGroups $expectResourceGroups
42+
}
43+
44+
<#
45+
.SYNOPSIS
46+
Tests resource id completer
47+
#>
48+
function Test-ResourceIdCompleter
49+
{
50+
$filePath = Join-Path -Path $PSScriptRoot -ChildPath "\Microsoft.Azure.Commands.ResourceManager.Common.dll"
51+
[System.Reflection.Assembly]::LoadFrom($filePath)
52+
$resourceType = "Microsoft.Storage/storageAccounts"
53+
$expectResourceIds = Get-AzureRmResource -ResourceType $resourceType | ForEach-Object {$_.Id}
54+
# take data from Azure and put to cache
55+
$resourceIds = [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceIdCompleterAttribute]::GetResourceIds($resourceType)
56+
Assert-AreEqualArray $resourceIds $expectResourceIds
57+
# take data from the cache
58+
$resourceIds = [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceIdCompleterAttribute]::GetResourceIds($resourceType)
59+
Assert-AreEqualArray $resourceIds $expectResourceIds
60+
# change time to update the cache
61+
[Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceIdCompleterAttribute]::TimeToUpdate = [System.TimeSpan]::FromSeconds(0)
62+
# take data from Azure again and put to cache
63+
$resourceIds = [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceIdCompleterAttribute]::GetResourceIds($resourceType)
64+
Assert-AreEqualArray $resourceIds $expectResourceIds
4265
}

src/ResourceManager/Profile/Commands.Profile.Test/Commands.Profile.Test.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@
199199
<None Include="SessionRecords\Microsoft.Azure.Commands.Profile.Test.ArgumentCompleterTests\TestResourceGroupCompleter.json">
200200
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
201201
</None>
202+
<None Include="SessionRecords\Microsoft.Azure.Commands.Profile.Test.ArgumentCompleterTests\TestResourceIdCompleter.json">
203+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
204+
</None>
202205
<None Include="SessionRecords\Microsoft.Azure.Commands.Profile.Test.DefaultCmdletTests\DefaultResourceGroup.json">
203206
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
204207
</None>

0 commit comments

Comments
 (0)