Skip to content

Commit 8ac3c6b

Browse files
authored
Improve status and list commands (#8102)
1 parent de61b0c commit 8ac3c6b

5 files changed

Lines changed: 117 additions & 69 deletions

File tree

tools/secret-management/Azure.Sdk.Tools.SecretManagement.Cli/Commands/ListCommand.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.CommandLine.Invocation;
2-
using System.Text.Json;
2+
using System.Text;
33
using Azure.Sdk.Tools.SecretRotation.Configuration;
4-
using Azure.Sdk.Tools.SecretRotation.Core;
54

65
namespace Azure.Sdk.Tools.SecretManagement.Cli.Commands;
76

@@ -16,7 +15,21 @@ protected override Task HandleCommandAsync(ILogger logger, RotationConfiguration
1615
{
1716
foreach (PlanConfiguration plan in rotationConfiguration.PlanConfigurations)
1817
{
19-
Console.WriteLine($"name: {plan.Name} - tags: {string.Join(", ", plan.Tags)}");
18+
logger.LogInformation(plan.Name);
19+
20+
if (logger.IsEnabled(LogLevel.Debug))
21+
{
22+
var builder = new StringBuilder();
23+
24+
builder.AppendLine($" Tags: {string.Join(", ", plan.Tags)}");
25+
builder.AppendLine($" Rotation Period: {plan.RotationPeriod}");
26+
builder.AppendLine($" Rotation Threshold: {plan.RotationThreshold}");
27+
builder.AppendLine($" Warning Threshold: {plan.WarningThreshold}");
28+
builder.AppendLine($" Revoke After Period: {plan.RevokeAfterPeriod}");
29+
builder.AppendLine($" Store Types: {string.Join(", ", plan.StoreConfigurations.Select(s => s.Type).Distinct() )}");
30+
31+
logger.LogDebug(builder.ToString());
32+
}
2033
}
2134

2235
return Task.CompletedTask;

tools/secret-management/Azure.Sdk.Tools.SecretManagement.Cli/Commands/StatusCommand.cs

Lines changed: 47 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
using System.CommandLine.Invocation;
2-
using System.Reflection;
32
using System.Text;
4-
using System.Text.Json;
5-
using System.Text.Json.Serialization;
63
using Azure.Sdk.Tools.SecretRotation.Configuration;
74
using Azure.Sdk.Tools.SecretRotation.Core;
85

@@ -18,93 +15,80 @@ protected override async Task HandleCommandAsync(ILogger logger, RotationConfigu
1815
InvocationContext invocationContext)
1916
{
2017
var timeProvider = new TimeProvider();
21-
IEnumerable<RotationPlan> plans = rotationConfiguration.GetAllRotationPlans(logger, timeProvider);
18+
RotationPlan[] plans = rotationConfiguration.GetAllRotationPlans(logger, timeProvider).ToArray();
2219

23-
List<(RotationPlan Plan, RotationPlanStatus Status)> statuses = new();
20+
logger.LogInformation($"Getting status for {plans.Length} plans");
2421

25-
foreach (RotationPlan plan in plans)
26-
{
27-
logger.LogInformation($"Getting status for plan '{plan.Name}'");
28-
RotationPlanStatus status = await plan.GetStatusAsync();
29-
30-
if (logger.IsEnabled(LogLevel.Debug))
31-
{
32-
var builder = new StringBuilder();
33-
34-
builder.AppendLine($" Plan:");
35-
builder.AppendLine($" RotationPeriod: {plan.RotationPeriod}");
36-
builder.AppendLine($" RotationThreshold: {plan.RotationThreshold}");
37-
builder.AppendLine($" RevokeAfterPeriod: {plan.RevokeAfterPeriod}");
38-
39-
builder.AppendLine($" Status:");
40-
builder.AppendLine($" ExpirationDate: {status.ExpirationDate}");
41-
builder.AppendLine($" State: {status.State}");
42-
builder.AppendLine($" RequiresRevocation: {status.RequiresRevocation}");
43-
builder.AppendLine($" Exception: {status.Exception?.Message}");
44-
45-
logger.LogDebug(builder.ToString());
46-
}
22+
(RotationPlan Plan, RotationPlanStatus Status)[] statuses = await plans
23+
.Select(async plan => {
24+
logger.LogDebug($"Getting status for plan '{plan.Name}'.");
25+
return (plan, await plan.GetStatusAsync());
26+
})
27+
.LimitConcurrencyAsync(10);
4728

4829

49-
statuses.Add((plan, status));
50-
}
51-
5230
var plansBuyState = statuses.GroupBy(x => x.Status.State)
5331
.ToDictionary(x => x.Key, x => x.ToArray());
5432

55-
var statusBuilder = new StringBuilder();
5633

57-
void AppendStatusSection(RotationState state, string header)
34+
void LogStatusSection(RotationState state, string header)
5835
{
59-
if (!plansBuyState.TryGetValue(RotationState.Expired, out var matchingPlans))
36+
if (!plansBuyState.TryGetValue(state, out var matchingPlans))
6037
{
6138
return;
6239
}
6340

64-
statusBuilder.AppendLine();
65-
statusBuilder.AppendLine(header);
41+
logger.LogInformation($"\n{header}");
42+
6643
foreach ((RotationPlan plan, RotationPlanStatus status) in matchingPlans)
6744
{
68-
foreach (string line in GetPlanStatusLine(plan, status).Split("\n"))
45+
var builder = new StringBuilder();
46+
var debugBuilder = new StringBuilder();
47+
48+
builder.Append($" {plan.Name} - ");
49+
DateTimeOffset? expirationDate = status.ExpirationDate;
50+
if (expirationDate.HasValue)
51+
{
52+
builder.AppendLine($"{expirationDate} ({FormatTimeSpan(expirationDate.Value.Subtract(DateTimeOffset.UtcNow))})");
53+
}
54+
else
6955
{
70-
statusBuilder.Append(" ");
71-
statusBuilder.AppendLine(line);
56+
builder.AppendLine("no expiration date");
7257
}
58+
59+
debugBuilder.AppendLine($" Plan:");
60+
debugBuilder.AppendLine($" Rotation Period: {plan.RotationPeriod}");
61+
debugBuilder.AppendLine($" Rotation Threshold: {plan.RotationThreshold}");
62+
debugBuilder.AppendLine($" Warning Threshold: {plan.WarningThreshold}");
63+
debugBuilder.AppendLine($" Revoke After Period: {plan.RevokeAfterPeriod}");
64+
debugBuilder.AppendLine($" Status:");
65+
debugBuilder.AppendLine($" Expiration Date: {status.ExpirationDate}");
66+
debugBuilder.AppendLine($" State: {status.State}");
67+
debugBuilder.AppendLine($" Requires Revocation: {status.RequiresRevocation}");
68+
69+
if (status.Exception != null)
70+
{
71+
builder.AppendLine($" Exception:");
72+
builder.AppendLine($" {status.Exception.Message}");
73+
}
74+
75+
logger.LogInformation(builder.ToString());
76+
logger.LogDebug(debugBuilder.ToString());
7377
}
7478
}
7579

76-
AppendStatusSection(RotationState.Expired, "Expired:");
77-
AppendStatusSection(RotationState.Warning, "Expiring:");
78-
AppendStatusSection(RotationState.Rotate, "Should Rotate:");
79-
AppendStatusSection(RotationState.UpToDate, "Up-to-date:");
80-
AppendStatusSection(RotationState.Error, "Error reading plan status:");
81-
82-
logger.LogInformation(statusBuilder.ToString());
80+
LogStatusSection(RotationState.Expired, "Expired:");
81+
LogStatusSection(RotationState.Warning, "Expiring:");
82+
LogStatusSection(RotationState.Rotate, "Should Rotate:");
83+
LogStatusSection(RotationState.UpToDate, "Up-to-date:");
84+
LogStatusSection(RotationState.Error, "Error reading plan status:");
8385

8486
if (statuses.Any(x => x.Status.State is RotationState.Expired or RotationState.Warning))
8587
{
8688
invocationContext.ExitCode = 1;
8789
}
8890
}
8991

90-
private static string GetPlanStatusLine(RotationPlan plan, RotationPlanStatus status)
91-
{
92-
if (status.Exception != null)
93-
{
94-
return $"{plan.Name}:\n {status.Exception.Message}";
95-
}
96-
97-
DateTimeOffset? expirationDate = status.ExpirationDate;
98-
99-
DateTimeOffset now = DateTimeOffset.UtcNow;
100-
101-
string expiration = expirationDate.HasValue
102-
? $"{FormatTimeSpan(expirationDate.Value.Subtract(now))}"
103-
: "No expiration date";
104-
105-
return $"{plan.Name} - {expiration} / ({FormatTimeSpan(plan.RotationPeriod)} @ {FormatTimeSpan(plan.RotationThreshold)})";
106-
}
107-
10892
private static string FormatTimeSpan(TimeSpan timeSpan)
10993
{
11094
if (timeSpan == TimeSpan.Zero)

tools/secret-management/Azure.Sdk.Tools.SecretManagement.Cli/SimplerConsoleFormatter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.Extensions.Logging.Abstractions;
1+
using Microsoft.Extensions.Logging.Abstractions;
22
using Microsoft.Extensions.Logging.Console;
33
using Microsoft.Extensions.Options;
44

@@ -83,7 +83,7 @@ private DateTimeOffset GetCurrentDateTime()
8383
return logLevel switch
8484
{
8585
LogLevel.Trace => "trce: ",
86-
LogLevel.Debug => "dbug: ",
86+
LogLevel.Debug => null,
8787
LogLevel.Information => null,
8888
LogLevel.Warning => "warn: ",
8989
LogLevel.Error => "fail: ",

tools/secret-management/Azure.Sdk.Tools.SecretRotation.Core/RotationException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Azure.Sdk.Tools.SecretRotation.Core;
1+
namespace Azure.Sdk.Tools.SecretRotation.Core;
22

33
public class RotationException : Exception
44
{
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
namespace Azure.Sdk.Tools.SecretRotation.Core;
2+
3+
public static class TaskExtensions
4+
{
5+
public static async Task<T[]> LimitConcurrencyAsync<T>(this IEnumerable<Task<T>> tasks, int concurrencyLimit = 1, CancellationToken cancellationToken = default)
6+
{
7+
if (concurrencyLimit == int.MaxValue)
8+
{
9+
return await Task.WhenAll(tasks);
10+
}
11+
12+
var results = new List<T>();
13+
14+
if (concurrencyLimit == 1)
15+
{
16+
foreach (var task in tasks)
17+
{
18+
results.Add(await task);
19+
}
20+
21+
return results.ToArray();
22+
}
23+
24+
var pending = new List<Task<T>>();
25+
26+
foreach (var task in tasks)
27+
{
28+
pending.Add(task);
29+
30+
if (cancellationToken.IsCancellationRequested)
31+
{
32+
break;
33+
}
34+
35+
if (pending.Count < concurrencyLimit) continue;
36+
37+
var completed = await Task.WhenAny(pending);
38+
pending.Remove(completed);
39+
results.Add(await completed);
40+
}
41+
42+
results.AddRange(await Task.WhenAll(pending));
43+
44+
return results.ToArray();
45+
}
46+
47+
public static Task<TResult[]> LimitConcurrencyAsync<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, Task<TResult>> taskFactory, int concurrencyLimit = 1, CancellationToken cancellationToken = default)
48+
{
49+
return LimitConcurrencyAsync(source.Select(taskFactory), concurrencyLimit, cancellationToken);
50+
}
51+
}

0 commit comments

Comments
 (0)