Skip to content

Commit

Permalink
NonInteractive mode for Apply and Destroy commands
Browse files Browse the repository at this point in the history
  • Loading branch information
prom3theu5 committed Nov 26, 2023
1 parent fe28f2b commit 3cd8183
Show file tree
Hide file tree
Showing 13 changed files with 112 additions and 45 deletions.
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);NU5104</NoWarn>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ public class InitializeConfigurationAction(

public override Task<bool> ExecuteAsync()
{
if (CurrentState.NonInteractive)
{
ValidateNonInteractiveState();
}

configurationService.HandleExistingConfiguration(CurrentState.ProjectPath, CurrentState.NonInteractive);

var aspirateSettings = PerformConfigurationBootstrapping();
Expand Down Expand Up @@ -171,30 +166,26 @@ private void HandleCreateTemplateFiles(AspirateSettings aspirateConfiguration)
}
}

protected override void ValidateNonInteractiveState()
public override void ValidateNonInteractiveState()
{
if (string.IsNullOrEmpty(CurrentState.ProjectPath))
{
Logger.MarkupLine("\r\n[red](!)[/] Project path must be supplied when running in non-interactive mode.");
throw new ActionCausesExitException(9999);
NonInteractiveValidationFailed("Project path must be supplied when running in non-interactive mode.");
}

if (string.IsNullOrEmpty(CurrentState.ContainerRegistry))
{
Logger.MarkupLine("\r\n[red](!)[/] Container registry must be supplied when running in non-interactive mode.");
throw new ActionCausesExitException(9999);
NonInteractiveValidationFailed("Container Registry must be supplied when running in non-interactive mode.");
}

if (string.IsNullOrEmpty(CurrentState.ContainerImageTag))
{
Logger.MarkupLine("\r\n[red](!)[/] Container image tag must be supplied when running in non-interactive mode.");
throw new ActionCausesExitException(9999);
NonInteractiveValidationFailed("Container image tag must be supplied when running in non-interactive mode.");
}

if (string.IsNullOrEmpty(CurrentState.TemplatePath))
{
Logger.MarkupLine("\r\n[red](!)[/] Template path must be supplied when running in non-interactive mode.");
throw new ActionCausesExitException(9999);
NonInteractiveValidationFailed("Template path must be supplied when running in non-interactive mode.");
}
}
}
42 changes: 30 additions & 12 deletions src/Aspirate.Cli/Actions/Manifests/ApplyManifestsToClusterAction.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
namespace Aspirate.Cli.Actions.Manifests;

public sealed class ApplyManifestsToClusterAction(IKubeCtlService kubeCtlService, IServiceProvider serviceProvider) : BaseAction(serviceProvider)
public sealed class ApplyManifestsToClusterAction(IKubeCtlService kubeCtlService, IServiceProvider serviceProvider) : BaseActionWithNonInteractiveSupport(serviceProvider)
{
public const string ActionKey = "ApplyManifestsToClusterAction";

public override async Task<bool> ExecuteAsync()
{
Logger.WriteLine();
var shouldDeploy = Logger.Confirm("[bold]Would you like to deploy the generated manifests to a kubernetes cluster defined in your kubeconfig file?[/]");
if (!shouldDeploy)
if (!CurrentState.NonInteractive)
{
Logger.MarkupLine("[yellow]Cancelled![/]");
Logger.WriteLine();
var shouldDeploy = Logger.Confirm(
"[bold]Would you like to deploy the generated manifests to a kubernetes cluster defined in your kubeconfig file?[/]");

return true;
if (!shouldDeploy)
{
Logger.MarkupLine("[yellow]Cancelled![/]");

return true;
}

CurrentState.KubeContext = await kubeCtlService.SelectKubernetesContextForDeployment();

if (!CurrentState.ActiveKubernetesContextIsSet)
{
return false;
}
}

CurrentState.ActiveKubernetesContext = await kubeCtlService.SelectKubernetesContextForDeployment();
await kubeCtlService.ApplyManifests(CurrentState.KubeContext, CurrentState.InputPath);
Logger.MarkupLine($"\r\n\t[green]({EmojiLiterals.CheckMark}) Done:[/] Deployments successfully applied to cluster [blue]'{CurrentState.KubeContext}'[/]");

return true;
}

public override void ValidateNonInteractiveState()
{
if (!CurrentState.ActiveKubernetesContextIsSet)
{
return false;
NonInteractiveValidationFailed("Cannot apply manifests to cluster without specifying the kubernetes context to use.");
}

await kubeCtlService.ApplyManifests(CurrentState.ActiveKubernetesContext, CurrentState.InputPath);
Logger.MarkupLine($"\r\n\t[green]({EmojiLiterals.CheckMark}) Done:[/] Deployments successfully applied to cluster [blue]'{CurrentState.ActiveKubernetesContext}'[/]");

return true;
if (string.IsNullOrEmpty(CurrentState.InputPath))
{
NonInteractiveValidationFailed("Cannot apply manifests to cluster without specifying the input path to use for manifests.");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,50 @@
namespace Aspirate.Cli.Actions.Manifests;

public sealed class RemoveManifestsFromClusterAction(IKubeCtlService kubeCtlService, IServiceProvider serviceProvider) : BaseAction(serviceProvider)
public sealed class RemoveManifestsFromClusterAction(IKubeCtlService kubeCtlService, IServiceProvider serviceProvider) :
BaseActionWithNonInteractiveSupport(serviceProvider)
{
public const string ActionKey = "RemoveManifestsFromClusterAction";

public override async Task<bool> ExecuteAsync()
{
Logger.WriteLine();
var shouldDeploy = Logger.Confirm("[bold]Would you like to remove the deployed manifests from a kubernetes cluster defined in your kubeconfig file?[/]");
if (!shouldDeploy)
if (!CurrentState.NonInteractive)
{
Logger.MarkupLine("[yellow]Cancelled![/]");

return true;
Logger.WriteLine();
var shouldDeploy = Logger.Confirm(
"[bold]Would you like to remove the deployed manifests from a kubernetes cluster defined in your kubeconfig file?[/]");

if (!shouldDeploy)
{
Logger.MarkupLine("[yellow]Cancelled![/]");

return true;
}

CurrentState.KubeContext = await kubeCtlService.SelectKubernetesContextForDeployment();

if (!CurrentState.ActiveKubernetesContextIsSet)
{
return false;
}
}

CurrentState.ActiveKubernetesContext = await kubeCtlService.SelectKubernetesContextForDeployment();
await kubeCtlService.RemoveManifests(CurrentState.KubeContext, CurrentState.InputPath);
Logger.MarkupLine($"\r\n\t[green]({EmojiLiterals.CheckMark}) Done:[/] Deployments removed from cluster [blue]'{CurrentState.KubeContext}'[/]");

return true;
}

public override void ValidateNonInteractiveState()
{
if (!CurrentState.ActiveKubernetesContextIsSet)
{
return false;
NonInteractiveValidationFailed("Cannot remove manifests from a cluster without specifying the kubernetes context to use.");
}

await kubeCtlService.RemoveManifests(CurrentState.ActiveKubernetesContext, CurrentState.InputPath);
Logger.MarkupLine($"\r\n\t[green]({EmojiLiterals.CheckMark}) Done:[/] Deployments removed from cluster [blue]'{CurrentState.ActiveKubernetesContext}'[/]");

return true;
if (string.IsNullOrEmpty(CurrentState.InputPath))
{
NonInteractiveValidationFailed("Cannot remove manifests from a cluster without specifying the input path to use for manifests.");
}
}
}
6 changes: 5 additions & 1 deletion src/Aspirate.Cli/Commands/Apply/ApplyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ namespace Aspirate.Cli.Commands.Apply;

public sealed class ApplyCommand : BaseCommand<ApplyOptions, ApplyCommandHandler>
{
public ApplyCommand() : base("apply", "Apply the generated kustomize manifest to the cluster.") =>
public ApplyCommand() : base("apply", "Apply the generated kustomize manifest to the cluster.")
{
AddOption(SharedOptions.ManifestDirectoryPath);
AddOption(SharedOptions.KubernetesContext);
AddOption(SharedOptions.NonInteractive);
}
}
2 changes: 2 additions & 0 deletions src/Aspirate.Cli/Commands/Apply/ApplyOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ namespace Aspirate.Cli.Commands.Apply;
public sealed class ApplyOptions : ICommandOptions
{
public string InputPath { get; init; } = AspirateLiterals.DefaultOutputPath;
public string? KubeContext { get; set; }
public bool NonInteractive { get; set; } = false;
}
6 changes: 5 additions & 1 deletion src/Aspirate.Cli/Commands/Destroy/DestroyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ namespace Aspirate.Cli.Commands.Destroy;

public sealed class DestroyCommand : BaseCommand<DestroyOptions, DestroyCommandHandler>
{
public DestroyCommand() : base("destroy", "Removes the manifests from your cluster..") =>
public DestroyCommand() : base("destroy", "Removes the manifests from your cluster..")
{
AddOption(SharedOptions.ManifestDirectoryPath);
AddOption(SharedOptions.KubernetesContext);
AddOption(SharedOptions.NonInteractive);
}
}
2 changes: 2 additions & 0 deletions src/Aspirate.Cli/Commands/Destroy/DestroyOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ namespace Aspirate.Cli.Commands.Destroy;
public sealed class DestroyOptions : ICommandOptions
{
public string InputPath { get; init; } = AspirateLiterals.DefaultOutputPath;
public string? KubeContext { get; set; }
public bool NonInteractive { get; set; } = false;
}
7 changes: 7 additions & 0 deletions src/Aspirate.Cli/Commands/SharedOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ public static class SharedOptions
IsRequired = false,
};

public static Option<string> KubernetesContext => new(new[] { "-k", "--kube-context" })
{
Description = "The name of the kubernetes context to use",
Arity = ArgumentArity.ExactlyOne,
IsRequired = false,
};

public static Option<bool> NonInteractive => new(new[] { "--non-interactive" })
{
Description = "Disables interactive mode for the command",
Expand Down
15 changes: 13 additions & 2 deletions src/Aspirate.Shared/Actions/ActionExecutor.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
namespace Aspirate.Shared.Actions;

public class ActionExecutor(IAnsiConsole console, IServiceProvider serviceProvider)
public class ActionExecutor(IAnsiConsole console, IServiceProvider serviceProvider, AspirateState state)
{
public static ActionExecutor CreateInstance(IServiceProvider serviceProvider) => new(serviceProvider.GetRequiredService<IAnsiConsole>(), serviceProvider);
public static ActionExecutor CreateInstance(IServiceProvider serviceProvider) =>
new(serviceProvider.GetRequiredService<IAnsiConsole>(), serviceProvider, serviceProvider.GetRequiredService<AspirateState>());

private readonly Queue<ExecutionAction> _actionQueue = new();

Expand All @@ -16,13 +17,23 @@ public ActionExecutor QueueAction(string actionKey, Func<Task>? onFailure = null

public async Task<int> ExecuteCommandsAsync()
{
if (state.NonInteractive)
{
console.MarkupLine("[blue]Non-interactive mode enabled.[/]");
}

while (_actionQueue.Count > 0)
{
var executionAction = _actionQueue.Dequeue();
var action = serviceProvider.GetRequiredKeyedService<IAction>(executionAction.ActionKey);

try
{
if (state.NonInteractive && action is BaseActionWithNonInteractiveSupport nonInteractiveAction)
{
nonInteractiveAction.ValidateNonInteractiveState();
}

var successfullyCompleted = await action.ExecuteAsync();

if (successfullyCompleted)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@ namespace Aspirate.Shared.Actions;

public abstract class BaseActionWithNonInteractiveSupport(IServiceProvider serviceProvider) : BaseAction(serviceProvider)
{
protected abstract void ValidateNonInteractiveState();
public abstract void ValidateNonInteractiveState();

protected void NonInteractiveValidationFailed(string message)
{
Logger.MarkupLine($"\r\n[red](!)[/] {message}");
throw new ActionCausesExitException(9999);
}
}
1 change: 1 addition & 0 deletions src/Aspirate.Shared/Aspirate.Shared.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/Aspirate.Shared/Models/State/AspirateState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ public class AspirateState
public string? ContainerRegistry { get; set; }
public string? ContainerImageTag { get; set; }
public string? TemplatePath { get; set; }
public string? ActiveKubernetesContext { get; set; }
public string? KubeContext { get; set; }
public bool NonInteractive { get; set; }
public List<string> AspireComponentsToProcess { get; set; } = [];
public Dictionary<string, Resource> LoadedAspireManifestResources { get; set; } = [];
public Dictionary<string, Resource> FinalResources { get; } = [];
public bool ActiveKubernetesContextIsSet => !string.IsNullOrEmpty(ActiveKubernetesContext);
public bool ActiveKubernetesContextIsSet => !string.IsNullOrEmpty(KubeContext);
public List<KeyValuePair<string, Resource>> SelectedProjectComponents =>
LoadedAspireManifestResources
.Where(x => x.Value is Project && AspireComponentsToProcess.Contains(x.Key))
Expand Down

0 comments on commit 3cd8183

Please sign in to comment.