Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ public static Command CreateCommand(
IBotConfigurator botConfigurator,
CommandExecutor executor,
AgentBlueprintService agentBlueprintService,
IConfirmationProvider confirmationProvider)
IConfirmationProvider confirmationProvider,
FederatedCredentialService federatedCredentialService)
{
var cleanupCommand = new Command("cleanup", "Clean up ALL resources (blueprint, instance, Azure) - use subcommands for granular cleanup");

Expand All @@ -40,11 +41,11 @@ public static Command CreateCommand(
// Set default handler for 'a365 cleanup' (without subcommand) - cleans up everything
cleanupCommand.SetHandler(async (configFile, verbose) =>
{
await ExecuteAllCleanupAsync(logger, configService, botConfigurator, executor, agentBlueprintService, confirmationProvider, configFile);
await ExecuteAllCleanupAsync(logger, configService, botConfigurator, executor, agentBlueprintService, confirmationProvider, federatedCredentialService, configFile);
}, configOption, verboseOption);

// Add subcommands for granular control
cleanupCommand.AddCommand(CreateBlueprintCleanupCommand(logger, configService, botConfigurator, executor, agentBlueprintService));
cleanupCommand.AddCommand(CreateBlueprintCleanupCommand(logger, configService, botConfigurator, executor, agentBlueprintService, federatedCredentialService));
cleanupCommand.AddCommand(CreateAzureCleanupCommand(logger, configService, executor));
cleanupCommand.AddCommand(CreateInstanceCleanupCommand(logger, configService, executor));

Expand All @@ -56,7 +57,8 @@ private static Command CreateBlueprintCleanupCommand(
IConfigService configService,
IBotConfigurator botConfigurator,
CommandExecutor executor,
AgentBlueprintService agentBlueprintService)
AgentBlueprintService agentBlueprintService,
FederatedCredentialService federatedCredentialService)
{
var command = new Command("blueprint", "Remove Entra ID blueprint application and service principal");

Expand Down Expand Up @@ -124,7 +126,32 @@ private static Command CreateBlueprintCleanupCommand(
return;
}

// Delete federated credentials first before deleting the blueprint
logger.LogInformation("");
logger.LogInformation("Deleting federated credentials from blueprint...");

// Configure FederatedCredentialService with custom client app ID if available
if (!string.IsNullOrWhiteSpace(config.ClientAppId))
{
federatedCredentialService.CustomClientAppId = config.ClientAppId;
}

var ficsDeleted = await federatedCredentialService.DeleteAllFederatedCredentialsAsync(
config.TenantId,
config.AgentBlueprintId);

if (!ficsDeleted)
{
logger.LogWarning("Some federated credentials may not have been deleted successfully");
logger.LogWarning("Continuing with blueprint deletion...");
}
else
{
logger.LogInformation("Federated credentials deleted successfully");
}

// Delete the agent blueprint using the special Graph API endpoint
logger.LogInformation("");
logger.LogInformation("Deleting agent blueprint application...");
var deleted = await agentBlueprintService.DeleteAgentBlueprintAsync(
config.TenantId,
Expand Down Expand Up @@ -395,6 +422,7 @@ private static async Task ExecuteAllCleanupAsync(
CommandExecutor executor,
AgentBlueprintService agentBlueprintService,
IConfirmationProvider confirmationProvider,
FederatedCredentialService federatedCredentialService,
FileInfo? configFile)
{
var cleanupSucceeded = false;
Expand Down Expand Up @@ -447,7 +475,34 @@ private static async Task ExecuteAllCleanupAsync(

logger.LogInformation("Starting complete cleanup...");

// 1. Delete agent blueprint application
// 1. Delete federated credentials from agent blueprint (if exists)
if (!string.IsNullOrWhiteSpace(config.AgentBlueprintId))
{
logger.LogInformation("Deleting federated credentials from blueprint...");

// Configure FederatedCredentialService with custom client app ID if available
if (!string.IsNullOrWhiteSpace(config.ClientAppId))
{
federatedCredentialService.CustomClientAppId = config.ClientAppId;
}

var ficsDeleted = await federatedCredentialService.DeleteAllFederatedCredentialsAsync(
config.TenantId,
config.AgentBlueprintId);

if (!ficsDeleted)
{
logger.LogWarning("Some federated credentials may not have been deleted successfully");
logger.LogWarning("Continuing with blueprint deletion...");
hasFailures = true;
}
else
{
logger.LogInformation("Federated credentials deleted successfully");
}
}

// 2. Delete agent blueprint application
if (!string.IsNullOrWhiteSpace(config.AgentBlueprintId))
{
logger.LogInformation("Deleting agent blueprint application...");
Expand All @@ -467,7 +522,7 @@ private static async Task ExecuteAllCleanupAsync(
}
}

// 2. Delete agent identity application
// 3. Delete agent identity application
if (!string.IsNullOrWhiteSpace(config.AgenticAppId))
{
logger.LogInformation("Deleting agent identity application...");
Expand All @@ -488,15 +543,15 @@ private static async Task ExecuteAllCleanupAsync(
}
}

// 3. Delete agent user
// 4. Delete agent user
if (!string.IsNullOrWhiteSpace(config.AgenticUserId))
{
logger.LogInformation("Deleting agent user...");
await executor.ExecuteAsync("az", $"ad user delete --id {config.AgenticUserId}", null, true, false, CancellationToken.None);
logger.LogInformation("Agent user deleted");
}

// 4. Delete bot messaging endpoint using shared helper
// 5. Delete bot messaging endpoint using shared helper
if (!string.IsNullOrWhiteSpace(config.BotName))
{
var endpointDeleted = await DeleteMessagingEndpointAsync(logger, config, botConfigurator);
Expand All @@ -506,7 +561,7 @@ private static async Task ExecuteAllCleanupAsync(
}
}

// 5. Delete Azure resources (Web App and App Service Plan)
// 6. Delete Azure resources (Web App and App Service Plan)
if (!string.IsNullOrWhiteSpace(config.WebAppName) && !string.IsNullOrWhiteSpace(config.ResourceGroup))
{
logger.LogInformation("Deleting Azure resources...");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public static Command CreateCommand(
PlatformDetector platformDetector,
GraphApiService graphApiService,
AgentBlueprintService blueprintService,
BlueprintLookupService blueprintLookupService,
FederatedCredentialService federatedCredentialService,
IClientAppValidator clientAppValidator)
{
var command = new Command("setup",
Expand All @@ -47,13 +49,13 @@ public static Command CreateCommand(
logger, configService, azureValidator, webAppCreator, platformDetector, executor));

command.AddCommand(BlueprintSubcommand.CreateCommand(
logger, configService, executor, azureValidator, webAppCreator, platformDetector, botConfigurator, graphApiService, blueprintService, clientAppValidator));
logger, configService, executor, azureValidator, webAppCreator, platformDetector, botConfigurator, graphApiService, blueprintService, clientAppValidator, blueprintLookupService, federatedCredentialService));

command.AddCommand(PermissionsSubcommand.CreateCommand(
logger, configService, executor, graphApiService, blueprintService));

command.AddCommand(AllSubcommand.CreateCommand(
logger, configService, executor, botConfigurator, azureValidator, webAppCreator, platformDetector, graphApiService, blueprintService, clientAppValidator));
logger, configService, executor, botConfigurator, azureValidator, webAppCreator, platformDetector, graphApiService, blueprintService, clientAppValidator, blueprintLookupService, federatedCredentialService));

return command;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public static Command CreateCommand(
PlatformDetector platformDetector,
GraphApiService graphApiService,
AgentBlueprintService blueprintService,
IClientAppValidator clientAppValidator)
IClientAppValidator clientAppValidator,
BlueprintLookupService blueprintLookupService,
FederatedCredentialService federatedCredentialService)
{
var command = new Command("all",
"Run complete Agent 365 setup (all steps in sequence)\n" +
Expand Down Expand Up @@ -134,9 +136,7 @@ public static Command CreateCommand(
// PHASE 0: CHECK REQUIREMENTS (if not skipped)
if (!skipRequirements)
{
logger.LogInformation("Step 0: Requirements Check");
logger.LogInformation("Validating system prerequisites...");
logger.LogInformation("");
logger.LogDebug("Validating system prerequisites...");

try
{
Expand Down Expand Up @@ -164,53 +164,51 @@ public static Command CreateCommand(
}
else
{
logger.LogInformation("Skipping requirements validation (--skip-requirements flag used)");
logger.LogInformation("");
logger.LogDebug("Skipping requirements validation (--skip-requirements flag used)");
}

// PHASE 1: VALIDATE ALL PREREQUISITES UPFRONT
logger.LogInformation("Validating all prerequisites...");
logger.LogInformation("");
logger.LogDebug("Validating all prerequisites...");

var allErrors = new List<string>();

// Validate Azure CLI authentication first
logger.LogInformation("Validating Azure CLI authentication...");
logger.LogDebug("Validating Azure CLI authentication...");
if (!await azureValidator.ValidateAllAsync(setupConfig.SubscriptionId))
{
allErrors.Add("Azure CLI authentication failed or subscription not set correctly");
logger.LogError("Azure CLI authentication validation failed");
}
else
{
logger.LogInformation("Azure CLI authentication: OK");
logger.LogDebug("Azure CLI authentication: OK");
}

// Validate Infrastructure prerequisites
if (!skipInfrastructure && setupConfig.NeedDeployment)
{
logger.LogInformation("Validating Infrastructure prerequisites...");
logger.LogDebug("Validating Infrastructure prerequisites...");
var infraErrors = await InfrastructureSubcommand.ValidateAsync(setupConfig, azureValidator, CancellationToken.None);
if (infraErrors.Count > 0)
{
allErrors.AddRange(infraErrors.Select(e => $"Infrastructure: {e}"));
}
else
{
logger.LogInformation("Infrastructure prerequisites: OK");
logger.LogDebug("Infrastructure prerequisites: OK");
}
}

// Validate Blueprint prerequisites
logger.LogInformation("Validating Blueprint prerequisites...");
logger.LogDebug("Validating Blueprint prerequisites...");
var blueprintErrors = await BlueprintSubcommand.ValidateAsync(setupConfig, azureValidator, clientAppValidator, CancellationToken.None);
if (blueprintErrors.Count > 0)
{
allErrors.AddRange(blueprintErrors.Select(e => $"Blueprint: {e}"));
}
else
{
logger.LogInformation("Blueprint prerequisites: OK");
logger.LogDebug("Blueprint prerequisites: OK");
}

// Stop if any validation failed
Expand All @@ -229,9 +227,7 @@ public static Command CreateCommand(
return;
}

logger.LogInformation("");
logger.LogInformation("All validations passed. Starting setup execution...");
logger.LogInformation("");
logger.LogDebug("All validations passed. Starting setup execution...");

var generatedConfigPath = Path.Combine(
config.DirectoryName ?? Environment.CurrentDirectory,
Expand All @@ -240,10 +236,8 @@ public static Command CreateCommand(
// Step 1: Infrastructure (optional)
try
{
logger.LogInformation("Step 1:");
logger.LogInformation("");

bool setupInfra = await InfrastructureSubcommand.CreateInfrastructureImplementationAsync(
var (setupInfra, infraAlreadyExisted) = await InfrastructureSubcommand.CreateInfrastructureImplementationAsync(
logger,
config.FullName,
generatedConfigPath,
Expand All @@ -254,6 +248,7 @@ public static Command CreateCommand(
CancellationToken.None);

setupResults.InfrastructureCreated = skipInfrastructure ? false : setupInfra;
setupResults.InfrastructureAlreadyExisted = infraAlreadyExisted;
}
catch (Agent365Exception infraEx)
{
Expand All @@ -270,10 +265,6 @@ public static Command CreateCommand(
}

// Step 2: Blueprint
logger.LogInformation("");
logger.LogInformation("Step 2:");
logger.LogInformation("");

try
{
var result = await BlueprintSubcommand.CreateBlueprintImplementationAsync(
Expand All @@ -288,11 +279,15 @@ public static Command CreateCommand(
botConfigurator,
platformDetector,
graphApiService,
blueprintService
blueprintService,
blueprintLookupService,
federatedCredentialService
);

setupResults.BlueprintCreated = result.BlueprintCreated;
setupResults.BlueprintAlreadyExisted = result.BlueprintAlreadyExisted;
setupResults.MessagingEndpointRegistered = result.EndpointRegistered;
setupResults.EndpointAlreadyExisted = result.EndpointAlreadyExisted;

if (result.EndpointAlreadyExisted)
{
Expand Down Expand Up @@ -350,10 +345,6 @@ public static Command CreateCommand(
}

// Step 3: MCP Permissions
logger.LogInformation("");
logger.LogInformation("Step 3:");
logger.LogInformation("");

try
{
bool mcpPermissionSetup = await PermissionsSubcommand.ConfigureMcpPermissionsAsync(
Expand Down Expand Up @@ -381,11 +372,6 @@ public static Command CreateCommand(
}

// Step 4: Bot API Permissions

logger.LogInformation("");
logger.LogInformation("Step 4:");
logger.LogInformation("");

try
{
bool botPermissionSetup = await PermissionsSubcommand.ConfigureBotPermissionsAsync(
Expand Down
Loading