Skip to content
Open
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 @@ -6,8 +6,10 @@
using Microsoft.Extensions.Logging;
using Microsoft.Agents.A365.DevTools.Cli.Constants;
using Microsoft.Agents.A365.DevTools.Cli.Services.Helpers;
using Microsoft.Agents.A365.DevTools.Cli.Exceptions;
using Microsoft.Agents.A365.DevTools.Cli.Services;
using Microsoft.Agents.A365.DevTools.Cli.Services.Internal;
using Microsoft.Agents.A365.DevTools.Cli.Services.Requirements.RequirementChecks;
using Microsoft.Agents.A365.DevTools.Cli.Models;

namespace Microsoft.Agents.A365.DevTools.Cli.Commands;
Expand All @@ -24,7 +26,9 @@ public static Command CreateCommand(
CommandExecutor executor,
AgentBlueprintService agentBlueprintService,
IConfirmationProvider confirmationProvider,
FederatedCredentialService federatedCredentialService)
FederatedCredentialService federatedCredentialService,
IPrerequisiteRunner prerequisiteRunner,
AzureAuthValidator authValidator)
{
var cleanupCommand = new Command("cleanup", "Clean up ALL resources (blueprint, instance, Azure) - use subcommands for granular cleanup");

Expand Down Expand Up @@ -55,7 +59,7 @@ public static Command CreateCommand(

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

return cleanupCommand;
Expand Down Expand Up @@ -304,7 +308,9 @@ private static Command CreateBlueprintCleanupCommand(
private static Command CreateAzureCleanupCommand(
ILogger<CleanupCommand> logger,
IConfigService configService,
CommandExecutor executor)
CommandExecutor executor,
IPrerequisiteRunner prerequisiteRunner,
AzureAuthValidator authValidator)
{
var command = new Command("azure", "Remove Azure resources (App Service, App Service Plan)");

Expand All @@ -331,6 +337,10 @@ private static Command CreateAzureCleanupCommand(
var config = await LoadConfigAsync(configFile, logger, configService);
if (config == null) return;

var authChecks = new List<Services.Requirements.IRequirementCheck> { new AzureAuthRequirementCheck(authValidator) };
if (!await prerequisiteRunner.RunAsync(authChecks, config, logger, CancellationToken.None))
return;
Comment on lines +341 to +342
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the Azure auth prerequisite fails, this handler currently just returns, which will likely result in a zero exit code even though cleanup could not proceed. Consider exiting with a non-zero code (e.g., ExceptionHandler.ExitWithCleanup(1)) after prerequisiteRunner reports failure so scripts can detect the error.

Suggested change
if (!await prerequisiteRunner.RunAsync(authChecks, config, logger, CancellationToken.None))
return;
if (!await prerequisiteRunner.RunAsync(authChecks, config, logger, CancellationToken.None))
{
ExceptionHandler.ExitWithCleanup(1);
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +341 to +342
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the Azure auth prerequisite check fails, the handler currently just returns. Similar to other commands, this should likely produce a non-zero exit code so CI/scripts can detect failure (e.g., call the exit helper or set the invocation exit code).

Suggested change
if (!await prerequisiteRunner.RunAsync(authChecks, config, logger, CancellationToken.None))
return;
if (!await prerequisiteRunner.RunAsync(authChecks, config, logger, CancellationToken.None))
{
System.Environment.ExitCode = 1;
return;
}

Copilot uses AI. Check for mistakes.

logger.LogInformation("");
logger.LogInformation("Azure Cleanup Preview:");
logger.LogInformation("=========================");
Expand Down Expand Up @@ -962,9 +972,9 @@ private static void PrintOrphanSummary(
logger.LogInformation("Loaded configuration successfully from {ConfigFile}", configPath);
return config;
}
catch (FileNotFoundException ex)
catch (ConfigFileNotFoundException ex)
{
logger.LogError("Configuration file not found: {Message}", ex.Message);
logger.LogError("Configuration file not found: {Message}", ex.IssueDescription);
return null;
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.Agents.A365.DevTools.Cli.Constants;
using Microsoft.Agents.A365.DevTools.Cli.Helpers;
using Microsoft.Agents.A365.DevTools.Cli.Models;
using Microsoft.Agents.A365.DevTools.Cli.Exceptions;
using Microsoft.Agents.A365.DevTools.Cli.Services;
using Microsoft.Agents.A365.DevTools.Cli.Services.Helpers;
using Microsoft.Extensions.Logging;
Expand All @@ -18,7 +19,7 @@ namespace Microsoft.Agents.A365.DevTools.Cli.Commands;
public class CreateInstanceCommand
{
public static Command CreateCommand(ILogger<CreateInstanceCommand> logger, IConfigService configService, CommandExecutor executor,
IBotConfigurator botConfigurator, GraphApiService graphApiService, IAzureValidator azureValidator)
IBotConfigurator botConfigurator, GraphApiService graphApiService)
{
// Command description - deprecated
// Old: Create and configure agent user identities with appropriate
Expand Down Expand Up @@ -75,12 +76,6 @@ public static Command CreateCommand(ILogger<CreateInstanceCommand> logger, IConf
var instanceConfig = await LoadConfigAsync(logger, configService, config.FullName);
if (instanceConfig == null) Environment.Exit(1);

// Validate Azure CLI authentication, subscription, and environment
if (!await azureValidator.ValidateAllAsync(instanceConfig.SubscriptionId))
{
logger.LogError("Instance creation cannot proceed without proper Azure CLI authentication and subscription");
Environment.Exit(1);
}
logger.LogInformation("");

// Step 1-3: Identity, Licenses, and MCP Registration
Expand Down Expand Up @@ -505,16 +500,9 @@ private static Command CreateLicensesSubcommand(
: await configService.LoadAsync();
return config;
}
catch (FileNotFoundException ex)
catch (ConfigFileNotFoundException ex)
{
logger.LogError("Configuration file not found: {Message}", ex.Message);
logger.LogInformation("");
logger.LogInformation("To get started:");
logger.LogInformation(" 1. Copy a365.config.example.json to a365.config.json");
logger.LogInformation(" 2. Edit a365.config.json with your Azure tenant and subscription details");
logger.LogInformation(" 3. Run 'a365 setup' to initialize your environment first");
logger.LogInformation(" 4. Then run 'a365 createinstance' to create agent instances");
logger.LogInformation("");
logger.LogError("Configuration file not found: {Message}", ex.IssueDescription);
return null;
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.Agents.A365.DevTools.Cli.Models;
using Microsoft.Agents.A365.DevTools.Cli.Services;
using Microsoft.Agents.A365.DevTools.Cli.Services.Helpers;
using Microsoft.Agents.A365.DevTools.Cli.Services.Requirements.RequirementChecks;
using Microsoft.Extensions.Logging;
using System.CommandLine;

Expand All @@ -19,7 +20,8 @@ public static Command CreateCommand(
IConfigService configService,
CommandExecutor executor,
DeploymentService deploymentService,
IAzureValidator azureValidator,
IPrerequisiteRunner prerequisiteRunner,
AzureAuthValidator authValidator,
GraphApiService graphApiService,
AgentBlueprintService blueprintService)
{
Expand Down Expand Up @@ -53,7 +55,7 @@ public static Command CreateCommand(
command.AddOption(restartOption);

// Add subcommands
command.AddCommand(CreateAppSubcommand(logger, configService, executor, deploymentService, azureValidator));
command.AddCommand(CreateAppSubcommand(logger, configService, executor, deploymentService, prerequisiteRunner, authValidator));
command.AddCommand(CreateMcpSubcommand(logger, configService, executor, graphApiService, blueprintService));

// Single handler for the deploy command - runs only the application deployment flow
Expand Down Expand Up @@ -82,7 +84,7 @@ public static Command CreateCommand(
}

var validatedConfig = await ValidateDeploymentPrerequisitesAsync(
config.FullName, configService, azureValidator, executor, logger);
config.FullName, configService, prerequisiteRunner, authValidator, executor, logger);
if (validatedConfig == null) return;
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When ValidateDeploymentPrerequisitesAsync returns null (e.g., failed Azure auth / missing web app), the handler simply returns, which will usually produce exit code 0. Consider setting a non-zero exit code when prerequisites fail so automated callers can detect deployment failure reliably.

Suggested change
if (validatedConfig == null) return;
if (validatedConfig == null)
{
System.Environment.ExitCode = 1;
return;
}

Copilot uses AI. Check for mistakes.

await DeployApplicationAsync(validatedConfig, deploymentService, verbose, inspect, restart, logger);
Expand All @@ -101,7 +103,8 @@ private static Command CreateAppSubcommand(
IConfigService configService,
CommandExecutor executor,
DeploymentService deploymentService,
IAzureValidator azureValidator)
IPrerequisiteRunner prerequisiteRunner,
AzureAuthValidator authValidator)
{
var command = new Command("app", "Deploy Microsoft Agent 365 application binaries to the configured Azure App Service");

Expand Down Expand Up @@ -157,7 +160,7 @@ private static Command CreateAppSubcommand(
}

var validatedConfig = await ValidateDeploymentPrerequisitesAsync(
config.FullName, configService, azureValidator, executor, logger);
config.FullName, configService, prerequisiteRunner, authValidator, executor, logger);
if (validatedConfig == null) return;

await DeployApplicationAsync(validatedConfig, deploymentService, verbose, inspect, restart, logger);
Expand Down Expand Up @@ -218,6 +221,18 @@ private static Command CreateMcpSubcommand(
var updateConfig = await configService.LoadAsync(config.FullName);
if (updateConfig == null) Environment.Exit(1);

// Early validation: fail before any network calls
if (string.IsNullOrWhiteSpace(updateConfig.AgentBlueprintId))
{
logger.LogError("agentBlueprintId is not configured. Run 'a365 setup all' to create the agent blueprint.");
return;
}
if (string.IsNullOrWhiteSpace(updateConfig.AgenticAppId))
{
logger.LogError("agenticAppId is not configured. Run 'a365 setup all' to complete setup.");
return;
Comment on lines +228 to +233
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These early-guard failures log an error and then return from the handler, which will likely produce a zero exit code even though the command failed. To make this an actionable early-fail, exit with a non-zero code (e.g., ExceptionHandler.ExitWithCleanup(1) / Environment.Exit(1), or set the invocation exit code) after logging.

Suggested change
return;
}
if (string.IsNullOrWhiteSpace(updateConfig.AgenticAppId))
{
logger.LogError("agenticAppId is not configured. Run 'a365 setup all' to complete setup.");
return;
Environment.Exit(1);
}
if (string.IsNullOrWhiteSpace(updateConfig.AgenticAppId))
{
logger.LogError("agenticAppId is not configured. Run 'a365 setup all' to complete setup.");
Environment.Exit(1);

Copilot uses AI. Check for mistakes.
Comment on lines +228 to +233
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue here: logging an error and returning will likely exit with code 0. This should terminate the command with a non-zero exit code so automation/scripts can detect the failure.

Suggested change
return;
}
if (string.IsNullOrWhiteSpace(updateConfig.AgenticAppId))
{
logger.LogError("agenticAppId is not configured. Run 'a365 setup all' to complete setup.");
return;
Environment.Exit(1);
}
if (string.IsNullOrWhiteSpace(updateConfig.AgenticAppId))
{
logger.LogError("agenticAppId is not configured. Run 'a365 setup all' to complete setup.");
Environment.Exit(1);

Copilot uses AI. Check for mistakes.
}

// Configure GraphApiService with custom client app ID if available
if (!string.IsNullOrWhiteSpace(updateConfig.ClientAppId))
{
Expand Down Expand Up @@ -246,7 +261,8 @@ private static Command CreateMcpSubcommand(
private static async Task<Agent365Config?> ValidateDeploymentPrerequisitesAsync(
string configPath,
IConfigService configService,
IAzureValidator azureValidator,
IPrerequisiteRunner prerequisiteRunner,
AzureAuthValidator authValidator,
CommandExecutor executor,
ILogger logger)
{
Expand All @@ -255,7 +271,11 @@ private static Command CreateMcpSubcommand(
if (configData == null) return null;

// Validate Azure CLI authentication, subscription, and environment
if (!await azureValidator.ValidateAllAsync(configData.SubscriptionId))
var checks = new List<Services.Requirements.IRequirementCheck>
{
new AzureAuthRequirementCheck(authValidator)
Comment on lines 273 to +276
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says this validates Azure CLI authentication, subscription, and environment, but the current check list only runs AzureAuthRequirementCheck and does not run the AzureEnvironmentValidator anymore. Please update the comment to match the actual behavior (or re-add the environment validation if it’s still intended here).

Copilot uses AI. Check for mistakes.
};
if (!await prerequisiteRunner.RunAsync(checks, configData, logger))
Comment on lines +274 to +278
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This auth validation replaced azureValidator.ValidateAllAsync, but it no longer runs the advisory Azure environment validation (Azure CLI architecture/version warnings). If that diagnostic is still useful during deploy, consider invoking IAzureEnvironmentValidator.ValidateEnvironmentAsync() (non-blocking) after auth succeeds.

Copilot uses AI. Check for mistakes.
{
logger.LogError("Deployment cannot proceed without proper Azure CLI authentication and the correct subscription context");
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public static Command CreateCommand(
CommandExecutor executor,
DeploymentService deploymentService,
IBotConfigurator botConfigurator,
IAzureValidator azureValidator,
IPrerequisiteRunner prerequisiteRunner,
AzureAuthValidator authValidator,
IAzureEnvironmentValidator environmentValidator,
AzureWebAppCreator webAppCreator,
PlatformDetector platformDetector,
GraphApiService graphApiService,
Expand All @@ -29,7 +31,7 @@ public static Command CreateCommand(
FederatedCredentialService federatedCredentialService,
IClientAppValidator clientAppValidator)
{
var command = new Command("setup",
var command = new Command("setup",
"Set up your Agent 365 environment with granular control over each step\n\n" +
"Recommended execution order:\n" +
" 0. a365 setup requirements # Check prerequisites (optional)\n" +
Expand All @@ -43,19 +45,19 @@ public static Command CreateCommand(

// Add subcommands
command.AddCommand(RequirementsSubcommand.CreateCommand(
logger, configService, clientAppValidator));
logger, configService, authValidator, clientAppValidator));

command.AddCommand(InfrastructureSubcommand.CreateCommand(
logger, configService, azureValidator, webAppCreator, platformDetector, executor));
logger, configService, prerequisiteRunner, authValidator, webAppCreator, platformDetector, executor));

command.AddCommand(BlueprintSubcommand.CreateCommand(
logger, configService, executor, azureValidator, webAppCreator, platformDetector, botConfigurator, graphApiService, blueprintService, clientAppValidator, blueprintLookupService, federatedCredentialService));
logger, configService, executor, prerequisiteRunner, authValidator, 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, blueprintLookupService, federatedCredentialService));
logger, configService, executor, botConfigurator, prerequisiteRunner, authValidator, environmentValidator, webAppCreator, platformDetector, graphApiService, blueprintService, clientAppValidator, blueprintLookupService, federatedCredentialService));

return command;
}
Expand Down
Loading
Loading