-
Notifications
You must be signed in to change notification settings - Fork 243
execute az cli commands async #3286
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -161,23 +161,24 @@ internal class AspNetOptions | |||||
| PickerType = InteractivePickerType.YesNo | ||||||
| }; | ||||||
|
|
||||||
| private readonly bool _areAzCliCommandsSuccessful; | ||||||
| private readonly List<string> _usernames = []; | ||||||
| private readonly List<string> _tenants = []; | ||||||
| private readonly List<string> _appIds = []; | ||||||
| private ScaffolderOption<string>? _username = null; | ||||||
| private ScaffolderOption<string>? _tenantId = null; | ||||||
| private ScaffolderOption<string>? _applicationId = null; | ||||||
| private AzureInformation? _azureInformation; | ||||||
|
|
||||||
| public AspNetOptions() | ||||||
|
|
||||||
| private AspNetOptions(AzureInformation? azureInformation) | ||||||
| { | ||||||
| _azureInformation = azureInformation; | ||||||
| } | ||||||
|
|
||||||
| public static async Task<AspNetOptions> CreateAsync(CancellationToken cancellationToken) | ||||||
| { | ||||||
| _areAzCliCommandsSuccessful = AzCliHelper.GetAzureInformation(out List<string> usernames, out List<string> tenants, out List<string> appIds); | ||||||
| _usernames = usernames; | ||||||
| _tenants = tenants; | ||||||
| _appIds = appIds; | ||||||
| AzureInformation? azureInfo = await AzCliHelper.GetAzureInformationAsync(cancellationToken); | ||||||
| return new AspNetOptions(azureInfo); | ||||||
| } | ||||||
|
|
||||||
| public bool AreAzCliCommandsSuccessful() => _areAzCliCommandsSuccessful; | ||||||
| public bool AreAzCliCommandsSuccessful() => _azureInformation is not null; | ||||||
|
|
||||||
| public ScaffolderOption<string> Username => _username ??= new() | ||||||
| { | ||||||
|
|
@@ -186,7 +187,7 @@ public AspNetOptions() | |||||
| Description = AspnetStrings.Options.Username.Description, | ||||||
| Required = true, | ||||||
| PickerType = InteractivePickerType.CustomPicker, | ||||||
| CustomPickerValues = _usernames | ||||||
| CustomPickerValues = _azureInformation!.Usernames | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't appear to be safe to suppress these null checks. The previous code would have a default empty collection here if getting Azure info failed. This will have null, and this will throw. Consider something like:
Suggested change
Ditto below two more times. |
||||||
| }; | ||||||
|
|
||||||
|
|
||||||
|
|
@@ -197,7 +198,7 @@ public AspNetOptions() | |||||
| Description = AspnetStrings.Options.TenantId.Description, | ||||||
| Required = true, | ||||||
| PickerType = InteractivePickerType.CustomPicker, | ||||||
| CustomPickerValues = _tenants | ||||||
| CustomPickerValues = _azureInformation!.Tenants | ||||||
| }; | ||||||
|
|
||||||
| public static ScaffolderOption<string> Application => new() | ||||||
|
|
@@ -216,6 +217,6 @@ public AspNetOptions() | |||||
| Description = AspnetStrings.Options.SelectApplication.Description, | ||||||
| Required = false, | ||||||
| PickerType = InteractivePickerType.CustomPicker, | ||||||
| CustomPickerValues = _appIds | ||||||
| CustomPickerValues = _azureInformation!.AppIds | ||||||
| }; | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,60 +13,53 @@ internal class AzCliHelper | |
| /// <summary> | ||
| /// Gets Azure usernames, tenant IDs, and application IDs using the Azure CLI. | ||
| /// </summary> | ||
| /// <param name="usernames">the user IDs</param> | ||
| /// <param name="tenants">the tenant IDs</param> | ||
| /// <param name="appIds">the app IDs</param> | ||
| /// <returns>if successful, return true</returns> | ||
| public static bool GetAzureInformation(out List<string> usernames, out List<string> tenants, out List<string> appIds) | ||
| /// <returns>non null AzureInformation if the commands run successfully</returns> | ||
| public static async Task<AzureInformation?> GetAzureInformationAsync(CancellationToken cancellationToken) | ||
| { | ||
| // Create a runner to execute the 'az account list' command with json output format | ||
| var runner = AzCliRunner.Create(); | ||
|
|
||
| if (EnsureUserIsLoggedIn(runner, out string? output) && !string.IsNullOrEmpty(output)) | ||
| (bool isUserLoggedIn, string? output) = await EnsureUserIsLoggedInAsync(runner, cancellationToken); | ||
| if (isUserLoggedIn && !string.IsNullOrEmpty(output)) | ||
| { | ||
| if (GetAzureUsernamesAndTenatIds(runner, output, out usernames, out tenants)) | ||
| if (GetAzureUsernamesAndTenatIds(runner, output, out List<string> usernames, out List<string> tenants)) | ||
| { | ||
| if (GetAzureAppIds(runner, out appIds)) | ||
| (bool areAppIdSuccessful, List<string> appIds) = await GetAzureAppIdsAsync(runner, cancellationToken); | ||
| if (areAppIdSuccessful) | ||
| { | ||
| return true; | ||
| return new AzureInformation(usernames, tenants, appIds); | ||
| } | ||
| } | ||
| } | ||
| usernames = []; | ||
| tenants = []; | ||
| appIds = []; | ||
|
|
||
| return false; | ||
| return null; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Ensures the user is logged into Azure CLI. If not logged in, it will prompt for login. | ||
| /// </summary> | ||
| /// <param name="runner">the az cli runner</param> | ||
| /// <param name="output">the CLI output if available</param> | ||
| /// <returns>if successful, return true</returns> | ||
| private static bool EnsureUserIsLoggedIn(AzCliRunner runner, out string? output) | ||
| /// <param name="cancellationToken">the cancellation token</param> | ||
| /// <returns>if successful, return true and the output if applicable</returns> | ||
| private static async Task<(bool success, string? output)> EnsureUserIsLoggedInAsync(AzCliRunner runner, CancellationToken cancellationToken) | ||
| { | ||
| try | ||
| { | ||
| int exitCode = runner.RunAzCli("account list --output json", out var stdOut, out var stdErr); | ||
| (int exitCode, string? stdOut, string? stdErr) = await runner.RunAzCliAsync("account list --output json", cancellationToken); | ||
|
|
||
| if (stdOut is not null) | ||
| { | ||
| var result = StringUtil.ConvertStringToArray(stdOut); | ||
| if (result.Length is 0) | ||
|
Comment on lines
51
to
52
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unrelated to this PR, but this seems to allocate an array when all it really wants to know is that there was at least one value. If |
||
| { | ||
| exitCode = runner.RunAzCli("login", out stdOut, out stdErr); | ||
| (exitCode, stdOut, stdErr) = await runner.RunAzCliAsync("login", cancellationToken); | ||
| } | ||
| } | ||
| output = stdOut; | ||
| return exitCode == 0 && string.IsNullOrEmpty(stdErr); | ||
| return (exitCode == 0 && string.IsNullOrEmpty(stdErr), stdOut); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| output = null; | ||
| AnsiConsole.WriteLine($"Error checking Azure login status: {ex.Message}"); | ||
| return false; | ||
| return (false, null); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -132,15 +125,14 @@ private static bool GetAzureUsernamesAndTenatIds(AzCliRunner runner, string outp | |
| /// Gets Azure application IDs using the Azure CLI. | ||
| /// </summary> | ||
| /// <param name="runner">the az cli runner</param> | ||
| /// <param name="appIds"> the appIds</param> | ||
| /// <returns>if successful, returns true</returns> | ||
| private static bool GetAzureAppIds(AzCliRunner runner, out List<string> appIds) | ||
| /// <param name="cancellationToken">the cancellation token</param> | ||
| /// <returns>if successful, returns true with the appIds if retrieved</returns> | ||
| private static async Task<(bool, List<string> appIds)> GetAzureAppIdsAsync(AzCliRunner runner, CancellationToken cancellationToken) | ||
| { | ||
| try | ||
| { | ||
|
|
||
| appIds = []; | ||
| var exitCode = runner.RunAzCli("ad app list --output json", out string? stdOut, out string? stdErr); | ||
| List<string> appIds = []; | ||
| (int exitCode, string? stdOut, string? stdErr) = await runner.RunAzCliAsync("ad app list --output json", cancellationToken); | ||
|
|
||
| if (exitCode == 0 && !string.IsNullOrEmpty(stdOut)) | ||
| { | ||
|
|
@@ -166,7 +158,7 @@ private static bool GetAzureAppIds(AzCliRunner runner, out List<string> appIds) | |
| } | ||
| } | ||
| } | ||
| return true; | ||
| return (true, appIds); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -177,10 +169,9 @@ private static bool GetAzureAppIds(AzCliRunner runner, out List<string> appIds) | |
| } | ||
| catch (Exception ex) | ||
| { | ||
| appIds = []; | ||
| // Handle any exceptions, like az CLI not being installed | ||
| AnsiConsole.WriteLine($"Error getting Azure apps: {ex.Message}"); | ||
| } | ||
| return false; | ||
| return (false, []); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| namespace Microsoft.DotNet.Tools.Scaffold.AspNet.Helpers; | ||
|
|
||
| internal record AzureInformation(List<string> Usernames, List<string> Tenants, List<string> AppIds); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,4 +74,10 @@ public void AddScaffolderCommands() | |
| .WithStorageAddPackageSteps() | ||
| .WithStorageCodeModificationSteps(); | ||
| } | ||
|
|
||
| public Task AddScaffolderCommandsAsync(CancellationToken cancellationToken) | ||
| { | ||
| //ignore | ||
| throw new NotImplementedException(); | ||
|
Comment on lines
+80
to
+81
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not obvious to me why this is safe to ignore. Can it be described in the comment? |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,12 @@ | |
|
|
||
| IScaffoldRunnerBuilder builder = Host.CreateScaffoldBuilder(); | ||
|
|
||
| using var cts = new CancellationTokenSource(); | ||
| Console.CancelKeyPress += (sender, e) => { | ||
| e.Cancel = true; | ||
| cts.Cancel(); | ||
| }; | ||
|
|
||
| ConfigureServices(builder.Services); | ||
| ConfigureSharedSteps(builder.Services); | ||
|
|
||
|
|
@@ -23,11 +29,13 @@ | |
| Option nonInteractiveOption = nonInteractiveScaffoldOption.ToCliOption(); | ||
|
|
||
| AspireCommandService aspireCommandService = new(builder); | ||
|
|
||
| //aspire command adding does not need to be async | ||
| aspireCommandService.AddScaffolderCommands(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we adding ScaffolderComamnds twice. on L28 and L32
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one is |
||
| ConfigureCommandSteps(builder.Services, aspireCommandService); | ||
|
|
||
| AspNetCommandService aspNetCommandService = new(builder); | ||
| aspNetCommandService.AddScaffolderCommands(); | ||
| await aspNetCommandService.AddScaffolderCommandsAsync(cts.Token); | ||
| ConfigureCommandSteps(builder.Services, aspNetCommandService); | ||
|
|
||
| IScaffoldRunner runner = builder.Build(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this use
WaitForExitAsyncinstead, and simplify the code below a little? It's .NET 5+ (not sure what targets this needs to run on).Could make an extensions method for this. Something like:
Another option would be to register a callback on the cancellation token that performs the kill.