Skip to content

Commit e5098c3

Browse files
authored
Regional recommender (#21009)
* Region recommender * west europe * official common build * limit the frequency of recommendation * Revert "limit the frequency of recommendation" ...to align with CLI This reverts commit 8abade1. * changelog; null check; fix * Parameter formatter for telemetry * waiting for common * new common build
1 parent 6ac8e44 commit e5098c3

File tree

11 files changed

+286
-17
lines changed

11 files changed

+286
-17
lines changed

src/Accounts/Accounts/ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
* Fixed the issue that errors related to WAM are thrown when it is not enabled. [#20871] [#20824]
2323
* Updated Azure.Core library to 1.28.0.
2424
* Fixed an issue that the helper message about missing modules shows up at the wrong time. [#19228]
25+
* Added a hint message for some resource creation cmdlets when there is another region which may reduce the costs.
2526

2627
## Version 2.11.2
2728
* Supported Web Account Manager on ARM64-based Windows systems. Fixed an issue where `Connect-AzAccount` failed with error "Unable to load DLL 'msalruntime_arm64'". [#20700]

src/Accounts/Authentication/AzureSessionInitializer.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
using TraceLevel = System.Diagnostics.TraceLevel;
3333
using System.Collections.Generic;
3434
using System.Threading;
35+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
36+
using Microsoft.Azure.Commands.Common.Authentication.Utilities;
37+
using Microsoft.WindowsAzure.Commands.Common.Utilities;
3538

3639
namespace Microsoft.Azure.Commands.Common.Authentication
3740
{
@@ -274,6 +277,11 @@ static IAzureSession CreateInstance(IDataStore dataStore = null, Action<string>
274277
InitializeConfigs(session, profilePath, writeWarning);
275278
InitializeDataCollection(session);
276279
session.RegisterComponent(HttpClientOperationsFactory.Name, () => HttpClientOperationsFactory.Create());
280+
281+
session.RegisterComponent<IEndProcessingRecommendationService>(nameof(IEndProcessingRecommendationService),
282+
() => new DefaultRecommendationService());
283+
session.RegisterComponent<IParameterTelemetryFormatter>(nameof(IParameterTelemetryFormatter),
284+
() => new ParameterTelemetryFormatter());
277285
session.TokenCache = session.TokenCache ?? new AzureTokenCache();
278286
return session;
279287
}

src/Accounts/Authentication/Config/ConfigInitializer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ private void RegisterConfigs(IConfigManager configManager)
190190
true,
191191
AzurePSDataCollectionProfile.EnvironmentVariableName,
192192
new[] { AppliesTo.Az }));
193+
configManager.RegisterConfig(new SimpleTypedConfig<bool>(
194+
ConfigKeys.DisplayRegionIdentified,
195+
Resources.HelpMessageOfDisplayRegionIdentified,
196+
true,
197+
null,
198+
new[] { AppliesTo.Az }));
193199
#if DEBUG || TESTCOVERAGE
194200
configManager.RegisterConfig(new SimpleTypedConfig<bool>(
195201
ConfigKeys.EnableTestCoverage,

src/Accounts/Authentication/Properties/Resources.Designer.cs

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

src/Accounts/Authentication/Properties/Resources.resx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,4 +383,11 @@
383383
<data name="HelpMessageOfEnableWamLogin" xml:space="preserve">
384384
<value>[Preview] When enabled, Web Account Manager (WAM) will be the default interactive login experience. It will fall back to using the browser if the platform does not support WAM. Note that this feature is under preview. Microsoft Account (MSA) is currently not supported. Feel free to reach out to Azure PowerShell team if you have any feedbacks: https://aka.ms/azpsissue</value>
385385
</data>
386+
<data name="RecommendationMessageForLocation" xml:space="preserve">
387+
<value>Selecting “{0}” may reduce your costs. The region you’ve selected may cost more for the same services. You can disable this message in the future with the command “Update-AzConfig -DisplayRegionIdentified $false”. Learn more at https://go.microsoft.com/fwlink/?linkid=2225665</value>
388+
<comment>0 = recommended location</comment>
389+
</data>
390+
<data name="HelpMessageOfDisplayRegionIdentified" xml:space="preserve">
391+
<value>When enabled, Azure PowerShell displays recommendations on regions which may reduce your costs.</value>
392+
</data>
386393
</root>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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.WindowsAzure.Commands.Utilities.Common;
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Management.Automation;
19+
20+
namespace Microsoft.Azure.Commands.Common.Authentication.Utilities
21+
{
22+
/// <summary>
23+
/// Default implementation of <see cref="IEndProcessingRecommendationService"/>
24+
/// which provides suggestions based on cmdlet execution.
25+
/// </summary>
26+
internal class DefaultRecommendationService : IEndProcessingRecommendationService
27+
{
28+
private readonly IEnumerable<IRecommender> _recommenders;
29+
30+
public DefaultRecommendationService()
31+
{
32+
_recommenders = new List<IRecommender>()
33+
{
34+
new RegionalRecommender()
35+
};
36+
}
37+
38+
/// <inheritdoc/>
39+
public override void Process(AzurePSCmdlet cmdlet, InvocationInfo invocation, AzurePSQoSEvent qosEvent)
40+
{
41+
foreach (var recommender in _recommenders)
42+
{
43+
try
44+
{
45+
if (recommender.Process(invocation, qosEvent, out var recommendation))
46+
{
47+
WriteRecommendation(cmdlet, recommendation);
48+
}
49+
}
50+
catch (Exception ex)
51+
{
52+
// swallow exceptions as recommendations are not vital
53+
cmdlet.WriteDebug($"[{nameof(DefaultRecommendationService)}] Error encountered: {ex.Message}.{Environment.NewLine}{ex.StackTrace}");
54+
}
55+
}
56+
}
57+
58+
private void WriteRecommendation(AzurePSCmdlet cmdlet, string recommendation)
59+
{
60+
cmdlet.WriteInformation(new HostInformationMessage() { Message = recommendation }, new string[] { "PSHOST" });
61+
}
62+
}
63+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 System.Management.Automation;
16+
17+
namespace Microsoft.Azure.Commands.Common.Authentication.Utilities
18+
{
19+
/// <summary>
20+
/// Give recommendations based on cmdlet execution.
21+
/// </summary>
22+
internal interface IRecommender
23+
{
24+
/// <summary>
25+
/// Process the cmdlet invocation, give recommendation if any. Update telemetry data if necessary.
26+
/// </summary>
27+
/// <returns>Whether there is a recommendation.</returns>
28+
bool Process(InvocationInfo invocation, AzurePSQoSEvent qosEvent, out string recommendation);
29+
}
30+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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.Common.Authentication.Properties;
16+
using Microsoft.Azure.Commands.Shared.Config;
17+
using Microsoft.Azure.PowerShell.Common.Config;
18+
using System;
19+
using System.Collections.Generic;
20+
using System.Management.Automation;
21+
22+
namespace Microsoft.Azure.Commands.Common.Authentication.Utilities
23+
{
24+
/// <summary>
25+
/// Giving recommendations based on the input location for resource creation.
26+
/// </summary>
27+
internal class RegionalRecommender : IRecommender
28+
{
29+
private readonly IDictionary<string, string> _regionMappings;
30+
31+
public RegionalRecommender()
32+
{
33+
_regionMappings = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
34+
{
35+
// to support both display name and code of any region,
36+
// this map contains duplicated values
37+
{ "West Europe", "UK South" },
38+
{ "westeurope", "UK South" },
39+
{ "France Central", "North Europe" },
40+
{ "francecentral", "North Europe" },
41+
{ "Germany West Central", "North Europe" },
42+
{ "germanywestcentral", "North Europe" }
43+
};
44+
}
45+
46+
/// <inheritdoc/>
47+
public bool Process(InvocationInfo invocation, AzurePSQoSEvent qosEvent, out string recommendation)
48+
{
49+
recommendation = null;
50+
51+
if (invocation?.MyCommand?.Name == null || invocation?.BoundParameters == null)
52+
{
53+
return false;
54+
}
55+
56+
57+
if (AzureSession.Instance.TryGetComponent<IConfigManager>(nameof(IConfigManager), out var configManager))
58+
{
59+
if (!configManager.GetConfigValue<bool>(ConfigKeys.DisplayRegionIdentified))
60+
{
61+
return false;
62+
}
63+
}
64+
65+
if (string.Equals("New-AzVM", invocation?.MyCommand.Name, StringComparison.InvariantCultureIgnoreCase)
66+
&& invocation.BoundParameters.TryGetValue("Location", out object x)
67+
&& x is string inputLocation)
68+
{
69+
inputLocation = inputLocation.Trim();
70+
71+
if (_regionMappings.TryGetValue(inputLocation, out string recommendedLocation))
72+
{
73+
recommendation = string.Format(Resources.RecommendationMessageForLocation, recommendedLocation);
74+
qosEvent.DisplayRegionIdentified = recommendedLocation;
75+
return true;
76+
}
77+
}
78+
return false;
79+
}
80+
}
81+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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.WindowsAzure.Commands.Common.Utilities;
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using System.Management.Automation;
20+
21+
namespace Microsoft.Azure.Commands.Common.Authentication.Utilities
22+
{
23+
/// <inheritdoc/>
24+
internal class ParameterTelemetryFormatter : IParameterTelemetryFormatter
25+
{
26+
/// <inheritdoc/>
27+
public string FormatParameters(InvocationInfo invocation)
28+
{
29+
if (invocation?.BoundParameters == null) return string.Empty;
30+
31+
return string.Join(" ",
32+
invocation.BoundParameters.Select(pair =>
33+
ShouldKeepValue(invocation, pair.Key)
34+
? FormatParameterWithValue(pair.Key, pair.Value)
35+
: FormatParameterWithMaskedValue(pair.Key, pair.Value)));
36+
}
37+
38+
private bool ShouldKeepValue(InvocationInfo invocation, string name)
39+
{
40+
return string.Equals("New-AzVM", invocation.MyCommand?.Name, StringComparison.InvariantCultureIgnoreCase)
41+
&& string.Equals("location", name, StringComparison.InvariantCultureIgnoreCase);
42+
}
43+
44+
private string FormatParameterWithValue(string name, object value)
45+
{
46+
return $"-{name} {value}";
47+
}
48+
49+
private string FormatParameterWithMaskedValue(string name, object value)
50+
{
51+
return $"-{name} ***";
52+
}
53+
}
54+
}

src/shared/ConfigKeys.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ internal static class ConfigKeys
3131
public const string EnableTestCoverage = "EnableTestCoverage";
3232
public const string EnableLoginByWam = "EnableLoginByWam";
3333
public const string TestCoverageLocation = "TestCoverageLocation";
34+
public const string DisplayRegionIdentified = "DisplayRegionIdentified";
3435
}
3536
}

0 commit comments

Comments
 (0)