Skip to content

Commit 93dc1de

Browse files
author
maddieclayton
authored
Merge pull request Azure#4721 from maddieclayton/ResourceCompleter1
Add ResourceGroup completer
2 parents a68b2fc + 9e95f80 commit 93dc1de

File tree

7 files changed

+270
-1
lines changed

7 files changed

+270
-1
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
namespace Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters
16+
{
17+
using Commands.Common.Authentication;
18+
using Commands.Common.Authentication.Abstractions;
19+
using Management.Internal.Resources;
20+
using Management.Internal.Resources.Models;
21+
using Properties;
22+
using Rest.Azure;
23+
using System;
24+
using System.Collections.Concurrent;
25+
using System.Collections.Generic;
26+
using System.Linq;
27+
using System.Management.Automation;
28+
29+
/// <summary>
30+
/// This attribute will allow the user to autocomplete the -ResourceGroup parameter of a cmdlet with valid resource groups
31+
/// </summary>
32+
public class ResourceGroupCompleterAttribute : ArgumentCompleterAttribute
33+
{
34+
private static IDictionary<int, IList<String>> _resourceGroupNamesDictionary = new ConcurrentDictionary<int, IList<string>>();
35+
private static readonly object _lock = new object();
36+
37+
protected static IList<String> ResourceGroupNames
38+
{
39+
get
40+
{
41+
lock (_lock)
42+
{
43+
IAzureContext context = AzureRmProfileProvider.Instance.Profile.DefaultContext;
44+
var contextHash = HashContext(context);
45+
if (!_resourceGroupNamesDictionary.ContainsKey(contextHash))
46+
{
47+
var tempResourceGroupList = new List<string>();
48+
try
49+
{
50+
var instance = AzureSession.Instance;
51+
var client = instance.ClientFactory.CreateCustomArmClient<ResourceManagementClient>(
52+
context.Environment.GetEndpointAsUri(AzureEnvironment.Endpoint.ResourceManager),
53+
instance.AuthenticationFactory.GetServiceClientCredentials(context, AzureEnvironment.Endpoint.ResourceManager),
54+
instance.ClientFactory.GetCustomHandlers());
55+
client.SubscriptionId = context.Subscription.Id;
56+
// Retrieve only the first page of ResourceGroups to display to the user
57+
var resourceGroups = client.ResourceGroups.ListAsync();
58+
if (resourceGroups.Wait(TimeSpan.FromSeconds(5)))
59+
{
60+
tempResourceGroupList = CreateResourceGroupList(resourceGroups.Result);
61+
if (resourceGroups.Result != null)
62+
{
63+
_resourceGroupNamesDictionary[contextHash] = tempResourceGroupList;
64+
}
65+
}
66+
#if DEBUG
67+
else
68+
{
69+
throw new Exception("client.ResourceGroups call timed out");
70+
}
71+
#endif
72+
}
73+
74+
catch (Exception ex)
75+
{
76+
#if DEBUG
77+
throw ex;
78+
#endif
79+
}
80+
81+
return tempResourceGroupList;
82+
}
83+
84+
else
85+
{
86+
return _resourceGroupNamesDictionary[contextHash];
87+
}
88+
}
89+
}
90+
}
91+
92+
/// <summary>
93+
/// This class will provide a list of resource groups that are available to the user (with default resource group first if it exists). This will then be available to the user to tab through.
94+
/// Example: [Parameter(ParameterSetName = ListByNameInTenantParameterSet, ValueFromPipelineByPropertyName = true, Mandatory = false), ResourceGroupCompleter()]
95+
/// </summary>
96+
/// <param name="resourceTypes"></param>
97+
public ResourceGroupCompleterAttribute() : base(CreateScriptBlock())
98+
{
99+
}
100+
101+
public static string[] GetResourceGroups()
102+
{
103+
IAzureContext context = AzureRmProfileProvider.Instance.Profile.DefaultContext;
104+
var resourceGroupNamesCopy = ResourceGroupNames;
105+
if (context.IsPropertySet(Resources.DefaultResourceGroupKey))
106+
{
107+
return GetResourceGroups(resourceGroupNamesCopy, context.ExtendedProperties[Resources.DefaultResourceGroupKey]);
108+
}
109+
return GetResourceGroups(resourceGroupNamesCopy, null);
110+
}
111+
112+
public static string[] GetResourceGroups(IList<string> resourceGroupNames, string defaultResourceGroup)
113+
{
114+
if (defaultResourceGroup != null)
115+
{
116+
if (resourceGroupNames.Contains(defaultResourceGroup))
117+
{
118+
resourceGroupNames.Remove(defaultResourceGroup);
119+
}
120+
resourceGroupNames.Insert(0, defaultResourceGroup);
121+
}
122+
return resourceGroupNames.ToArray();
123+
}
124+
125+
private static ScriptBlock CreateScriptBlock()
126+
{
127+
string script = "param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)\n" +
128+
"$locations = [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceGroupCompleterAttribute]::GetResourceGroups()\n" +
129+
"$locations | Where-Object { $_ -Like \"$wordToComplete*\" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }";
130+
ScriptBlock scriptBlock = ScriptBlock.Create(script);
131+
return scriptBlock;
132+
}
133+
134+
private static int HashContext(IAzureContext context)
135+
{
136+
return (context.Account.Id + context.Environment.Name + context.Subscription.Id + context.Tenant.Id).GetHashCode();
137+
}
138+
139+
public static List<string> CreateResourceGroupList(IPage<ResourceGroup> result)
140+
{
141+
var tempResourceGroupList = new List<string>();
142+
if (result != null)
143+
{
144+
foreach (ResourceGroup resourceGroup in result)
145+
{
146+
tempResourceGroupList.Add(resourceGroup.Name);
147+
}
148+
}
149+
#if DEBUG
150+
else
151+
{
152+
throw new Exception("Result from client.ResourceGroups is null");
153+
}
154+
#endif
155+
return tempResourceGroupList;
156+
}
157+
}
158+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
<DesignTime>True</DesignTime>
186186
<DependentUpon>Resources.resx</DependentUpon>
187187
</Compile>
188+
<Compile Include="ArgumentCompleters\ResourceGroupCompleter.cs" />
188189
<Compile Include="ResponseWithContinuation.cs" />
189190
<Compile Include="RPRegistrationDelegatingHandler.cs" />
190191
<Compile Include="ServiceClientTracingInterceptor.cs" />

src/ResourceManager/Common/Commands.ResourceManager.Common/Properties/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ResourceManager/Common/Commands.ResourceManager.Common/Properties/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,7 @@ The data is collected by Microsoft.
216216
Use the Disable-AzureDataCollection cmdlet to turn the feature Off. The cmdlet can be found in the Azure module. To disable data collection: PS &gt; Disable-AzureDataCollection.
217217
Use the Enable-AzureDataCollection cmdlet to turn the feature On. The cmdlet can be found in the Azure module. To enable data collection: PS &gt; Enable-AzureDataCollection.</value>
218218
</data>
219+
<data name="DefaultResourceGroupKey" xml:space="preserve">
220+
<value>Default Resource Group</value>
221+
</data>
219222
</root>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@
203203
<Compile Include="ProfileController.cs" />
204204
<Compile Include="ProtectedFileProviderTests.cs" />
205205
<Compile Include="PSSerializationTests.cs" />
206+
<Compile Include="ResourceGroupCompleterUnitTests.cs" />
206207
<Compile Include="RPRegistrationDelegatingHandlerTests.cs" />
207208
<Compile Include="SessionInitializationTests.cs" />
208209
<Compile Include="SubscriptionCmdletTests.cs" />
@@ -286,4 +287,4 @@
286287
<ItemGroup />
287288
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
288289
<Import Project="..\..\..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\..\..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" />
289-
</Project>
290+
</Project>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
16+
using Microsoft.WindowsAzure.Commands.ScenarioTest;
17+
using System;
18+
using System.Collections.Generic;
19+
using Xunit;
20+
21+
namespace Microsoft.Azure.Commands.Profile.Test
22+
{
23+
public class ResourceGroupCompleterUnitTests
24+
{
25+
[Fact]
26+
[Trait(Category.AcceptanceType, Category.CheckIn)]
27+
public void ReturnsEmptyListWhenNoResourceGroupsExist()
28+
{
29+
IList<string> resourceGroupsReturned = new List<string>();
30+
Assert.Collection(ResourceGroupCompleterAttribute.GetResourceGroups(resourceGroupsReturned, null));
31+
}
32+
33+
[Fact]
34+
[Trait(Category.AcceptanceType, Category.CheckIn)]
35+
public void OneResourceGroupNoDefault()
36+
{
37+
IList<string> resourceGroupsReturned = new List<string>();
38+
resourceGroupsReturned.Add("test1");
39+
Assert.Collection(ResourceGroupCompleterAttribute.GetResourceGroups(resourceGroupsReturned, null), e1 => Assert.Equal("test1", e1));
40+
}
41+
42+
[Fact]
43+
[Trait(Category.AcceptanceType, Category.CheckIn)]
44+
public void OneResourceGroupWithDefault()
45+
{
46+
IList<string> resourceGroupsReturned = new List<string>();
47+
resourceGroupsReturned.Add("test1");
48+
Assert.Collection(ResourceGroupCompleterAttribute.GetResourceGroups(resourceGroupsReturned, "test1"), e1 => Assert.Equal("test1", e1));
49+
}
50+
51+
[Fact]
52+
[Trait(Category.AcceptanceType, Category.CheckIn)]
53+
public void OneResourceGroupWithInvalidDefault()
54+
{
55+
IList<string> resourceGroupsReturned = new List<string>();
56+
resourceGroupsReturned.Add("test1");
57+
Assert.Collection(ResourceGroupCompleterAttribute.GetResourceGroups(resourceGroupsReturned, "defaultOutOfPage"), e1 => Assert.Equal("defaultOutOfPage", e1),
58+
e2 => Assert.Equal("test1", e2));
59+
}
60+
61+
[Fact]
62+
[Trait(Category.AcceptanceType, Category.CheckIn)]
63+
public void MultipleResourceGroupsNoDefault()
64+
{
65+
IList<string> resourceGroupsReturned = new List<string>();
66+
resourceGroupsReturned.Add("test1");
67+
resourceGroupsReturned.Add("test2");
68+
resourceGroupsReturned.Add("test3");
69+
resourceGroupsReturned.Add("test4");
70+
Assert.Collection(ResourceGroupCompleterAttribute.GetResourceGroups(resourceGroupsReturned, null), e1 => Assert.Equal("test1", e1),
71+
e2 => Assert.Equal("test2", e2), e3 => Assert.Equal("test3", e3), e4 => Assert.Equal("test4", e4));
72+
}
73+
74+
[Fact]
75+
[Trait(Category.AcceptanceType, Category.CheckIn)]
76+
public void MultipleResourceGroupsWithDefault()
77+
{
78+
IList<string> resourceGroupsReturned = new List<string>();
79+
resourceGroupsReturned.Add("test1");
80+
resourceGroupsReturned.Add("test2");
81+
resourceGroupsReturned.Add("test3");
82+
resourceGroupsReturned.Add("test4");
83+
Assert.Collection(ResourceGroupCompleterAttribute.GetResourceGroups(resourceGroupsReturned, "test3"), e1 => Assert.Equal("test3", e1),
84+
e2 => Assert.Equal("test1", e2), e3 => Assert.Equal("test2", e3), e4 => Assert.Equal("test4", e4));
85+
}
86+
87+
[Fact]
88+
[Trait(Category.AcceptanceType, Category.CheckIn)]
89+
public void ThrowsErrorWhenResultNull()
90+
{
91+
var ex = Assert.Throws<Exception>(() => ResourceGroupCompleterAttribute.CreateResourceGroupList(null));
92+
Assert.Equal(ex.Message, "Result from client.ResourceGroups is null");
93+
}
94+
}
95+
}

src/ResourceManager/Profile/Commands.Profile/Default/SetAzureRMDefault.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using Microsoft.Azure.Commands.Profile.Models;
2020
using Microsoft.Azure.Commands.Profile.Properties;
2121
using Microsoft.Azure.Commands.ResourceManager.Common;
22+
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
2223
using Microsoft.Azure.Management.Internal.Resources;
2324
using Microsoft.Azure.Management.Internal.Resources.Models;
2425
using System.Management.Automation;
@@ -36,6 +37,7 @@ public class SetAzureRMDefaultCommand : AzureContextModificationCmdlet
3637
private const string ResourceGroupNameParameterSet = "ResourceGroupName";
3738

3839
[Parameter(ParameterSetName = ResourceGroupNameParameterSet, Mandatory = false, HelpMessage = "Name of the resource group being set as default", ValueFromPipelineByPropertyName = true)]
40+
[ResourceGroupCompleter()]
3941
[ValidateNotNullOrEmpty]
4042
public string ResourceGroupName { get; set; }
4143

0 commit comments

Comments
 (0)