up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -2205,6 +2205,237 @@ internal static class CommandHandlers
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task HandleTenantsListAsync(
|
||||
IServiceProvider services,
|
||||
StellaOpsCliOptions options,
|
||||
string? tenant,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using var scope = services.CreateAsyncScope();
|
||||
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("tenants-list");
|
||||
Environment.ExitCode = 0;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(options.Authority?.Url))
|
||||
{
|
||||
logger.LogError("Authority URL is not configured. Set STELLAOPS_AUTHORITY_URL or update your configuration.");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
var client = scope.ServiceProvider.GetService<IAuthorityConsoleClient>();
|
||||
if (client is null)
|
||||
{
|
||||
logger.LogError("Authority console client is not available. Ensure Authority is configured and services are registered.");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
var effectiveTenant = TenantProfileStore.GetEffectiveTenant(tenant);
|
||||
if (string.IsNullOrWhiteSpace(effectiveTenant))
|
||||
{
|
||||
logger.LogError("Tenant context is required. Provide --tenant, set STELLAOPS_TENANT environment variable, or run 'stella tenants use <tenant-id>'.");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var tenants = await client.ListTenantsAsync(effectiveTenant, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (json)
|
||||
{
|
||||
var output = new { tenants = tenants };
|
||||
var jsonText = JsonSerializer.Serialize(output, new JsonSerializerOptions { WriteIndented = true });
|
||||
Console.WriteLine(jsonText);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tenants.Count == 0)
|
||||
{
|
||||
logger.LogInformation("No tenants available for the authenticated principal.");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogInformation("Available tenants ({Count}):", tenants.Count);
|
||||
foreach (var t in tenants)
|
||||
{
|
||||
var status = string.Equals(t.Status, "active", StringComparison.OrdinalIgnoreCase) ? "" : $" ({t.Status})";
|
||||
logger.LogInformation(" {Id}: {DisplayName}{Status}", t.Id, t.DisplayName, status);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
logger.LogInformation(" Isolation: {IsolationMode}", t.IsolationMode);
|
||||
if (t.DefaultRoles.Count > 0)
|
||||
{
|
||||
logger.LogInformation(" Default roles: {Roles}", string.Join(", ", t.DefaultRoles));
|
||||
}
|
||||
if (t.Projects.Count > 0)
|
||||
{
|
||||
logger.LogInformation(" Projects: {Projects}", string.Join(", ", t.Projects));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
logger.LogError("Authentication required. Run 'stella auth login' first.");
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
|
||||
{
|
||||
logger.LogError("Access denied. The authenticated principal does not have permission to list tenants.");
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to retrieve tenant list: {Message}", ex.Message);
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task HandleTenantsUseAsync(
|
||||
IServiceProvider services,
|
||||
StellaOpsCliOptions options,
|
||||
string tenantId,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using var scope = services.CreateAsyncScope();
|
||||
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("tenants-use");
|
||||
Environment.ExitCode = 0;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
logger.LogError("Tenant identifier is required.");
|
||||
Environment.ExitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedTenant = tenantId.Trim().ToLowerInvariant();
|
||||
string? displayName = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(options.Authority?.Url))
|
||||
{
|
||||
var client = scope.ServiceProvider.GetService<IAuthorityConsoleClient>();
|
||||
if (client is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenants = await client.ListTenantsAsync(normalizedTenant, cancellationToken).ConfigureAwait(false);
|
||||
var match = tenants.FirstOrDefault(t =>
|
||||
string.Equals(t.Id, normalizedTenant, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (match is not null)
|
||||
{
|
||||
displayName = match.DisplayName;
|
||||
if (verbose)
|
||||
{
|
||||
logger.LogDebug("Validated tenant '{TenantId}' with display name '{DisplayName}'.", normalizedTenant, displayName);
|
||||
}
|
||||
}
|
||||
else if (verbose)
|
||||
{
|
||||
logger.LogWarning("Tenant '{TenantId}' not found in available tenants. Setting anyway.", normalizedTenant);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
|
||||
{
|
||||
if (verbose)
|
||||
{
|
||||
logger.LogWarning("Could not validate tenant against Authority: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await TenantProfileStore.SetActiveTenantAsync(normalizedTenant, displayName, cancellationToken).ConfigureAwait(false);
|
||||
logger.LogInformation("Active tenant set to '{TenantId}'.", normalizedTenant);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(displayName))
|
||||
{
|
||||
logger.LogInformation("Tenant display name: {DisplayName}", displayName);
|
||||
}
|
||||
|
||||
logger.LogInformation("Profile saved to: {Path}", TenantProfileStore.GetProfilePath());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to save tenant profile: {Message}", ex.Message);
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task HandleTenantsCurrentAsync(
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Environment.ExitCode = 0;
|
||||
|
||||
try
|
||||
{
|
||||
var profile = await TenantProfileStore.LoadAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (json)
|
||||
{
|
||||
var output = profile ?? new TenantProfile();
|
||||
var jsonText = JsonSerializer.Serialize(output, new JsonSerializerOptions { WriteIndented = true });
|
||||
Console.WriteLine(jsonText);
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile is null || string.IsNullOrWhiteSpace(profile.ActiveTenant))
|
||||
{
|
||||
Console.WriteLine("No active tenant configured.");
|
||||
Console.WriteLine("Use 'stella tenants use <tenant-id>' to set one.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Active tenant: {profile.ActiveTenant}");
|
||||
if (!string.IsNullOrWhiteSpace(profile.ActiveTenantDisplayName))
|
||||
{
|
||||
Console.WriteLine($"Display name: {profile.ActiveTenantDisplayName}");
|
||||
}
|
||||
|
||||
if (profile.LastUpdated.HasValue)
|
||||
{
|
||||
Console.WriteLine($"Last updated: {profile.LastUpdated.Value:u}");
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
Console.WriteLine($"Profile path: {TenantProfileStore.GetProfilePath()}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Failed to load tenant profile: {ex.Message}");
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task HandleTenantsClearAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Environment.ExitCode = 0;
|
||||
|
||||
try
|
||||
{
|
||||
await TenantProfileStore.ClearActiveTenantAsync(cancellationToken).ConfigureAwait(false);
|
||||
Console.WriteLine("Active tenant cleared.");
|
||||
Console.WriteLine("Subsequent commands will require --tenant or STELLAOPS_TENANT environment variable.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"Failed to clear tenant profile: {ex.Message}");
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task HandleVulnObservationsAsync(
|
||||
IServiceProvider services,
|
||||
string tenant,
|
||||
@@ -8315,6 +8546,21 @@ internal static class CommandHandlers
|
||||
diag.Code ?? "-",
|
||||
diag.Path ?? "-",
|
||||
Markup.Escape(diag.Message));
|
||||
}
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
}
|
||||
}
|
||||
|
||||
return result.Success ? ExitSuccess : ExitValidationError;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[red]Error:[/] {0}", Markup.Escape(ex.Message));
|
||||
return ExitInputError;
|
||||
}
|
||||
}
|
||||
|
||||
#region Risk Profile Commands
|
||||
|
||||
public static async Task HandleRiskProfileValidateAsync(
|
||||
@@ -8417,16 +8663,12 @@ internal static class CommandHandlers
|
||||
}
|
||||
}
|
||||
|
||||
return result.Success ? ExitSuccess : ExitValidationError;
|
||||
Environment.ExitCode = result.IsValid ? 0 : 1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {Markup.Escape(ex.Message)}");
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.WriteException(ex);
|
||||
}
|
||||
return ExitInputError;
|
||||
AnsiConsole.MarkupLine("[red]Error:[/] {0}", Markup.Escape(ex.Message));
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8919,33 +9161,6 @@ internal static class CommandHandlers
|
||||
public JsonElement? ExpectedFindings { get; set; }
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(outputPath))
|
||||
{
|
||||
var reportJson = JsonSerializer.Serialize(report, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
|
||||
});
|
||||
await File.WriteAllTextAsync(outputPath, reportJson).ConfigureAwait(false);
|
||||
AnsiConsole.MarkupLine("Validation report written to [cyan]{0}[/]", Markup.Escape(outputPath));
|
||||
}
|
||||
}
|
||||
|
||||
Environment.ExitCode = result.IsValid ? 0 : (strict ? 1 : 0);
|
||||
if (!result.IsValid && !strict)
|
||||
{
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[red]Error:[/] {0}", Markup.Escape(ex.Message));
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task HandleRiskProfileSchemaAsync(string? outputPath, bool verbose)
|
||||
{
|
||||
_ = verbose;
|
||||
|
||||
Reference in New Issue
Block a user