Skip to content

Commit

Permalink
Adds noninteractive mode for generate command, as well as --skip-buil…
Browse files Browse the repository at this point in the history
…d which skips build and push (#50)
  • Loading branch information
prom3theu5 authored Nov 26, 2023
1 parent 3e920cc commit a3740e7
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
namespace Aspirate.Cli.Actions.Containers;

public sealed class BuildAndPushContainersAction(
IServiceProvider serviceProvider) : BaseAction(serviceProvider)
IServiceProvider serviceProvider) : BaseActionWithNonInteractiveSupport(serviceProvider)
{
public const string ActionKey = "BuildAndPushContainersAction";

public override async Task<bool> ExecuteAsync()
{
if (CurrentState.SkipBuild)
{
Logger.MarkupLine("\r\n[bold]Skipping build and push action as requested.[/]");
return true;
}

if (NoSelectedProjectComponents())
{
return true;
Expand All @@ -18,7 +24,7 @@ public override async Task<bool> ExecuteAsync()

foreach (var resource in CurrentState.SelectedProjectComponents)
{
await projectProcessor.BuildAndPushProjectContainer(resource);
await projectProcessor.BuildAndPushProjectContainer(resource, CurrentState.NonInteractive);
}

Logger.MarkupLine("\r\n[bold]Building and push completed for all selected project components.[/]");
Expand All @@ -36,4 +42,8 @@ private bool NoSelectedProjectComponents()
Logger.MarkupLine("\r\n[bold]No project components selected. Skipping build and publish action.[/]");
return true;
}

public override void ValidateNonInteractiveState()
{
}
}
22 changes: 19 additions & 3 deletions src/Aspirate.Cli/Actions/Manifests/LoadAspireManifestAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Aspirate.Cli.Actions.Manifests;

public class LoadAspireManifestAction(
IManifestFileParserService manifestFileParserService,
IServiceProvider serviceProvider) : BaseAction(serviceProvider)
IServiceProvider serviceProvider) : BaseActionWithNonInteractiveSupport(serviceProvider)
{
public const string ActionKey = "LoadAspireManifestAction";

Expand All @@ -17,8 +17,15 @@ public override Task<bool> ExecuteAsync()
return Task.FromResult(true);
}

private List<string> SelectManifestItemsToProcess() =>
Logger.Prompt(
private List<string> SelectManifestItemsToProcess()
{
if (CurrentState.NonInteractive)
{
Logger.MarkupLine("\r\n[blue]Non-Interactive Mode: Processing all components in the loaded file.[/]\r\n");
return CurrentState.LoadedAspireManifestResources.Keys.ToList();
}

return Logger.Prompt(
new MultiSelectionPrompt<string>()
.Title("Select [green]components[/] to process from the loaded file")
.PageSize(10)
Expand All @@ -28,4 +35,13 @@ private List<string> SelectManifestItemsToProcess() =>
"[grey](Press [blue]<space>[/] to toggle a component, " +
"[green]<enter>[/] to accept)[/]")
.AddChoiceGroup("All Components", CurrentState.LoadedAspireManifestResources.Keys.ToList()));
}

public override void ValidateNonInteractiveState()
{
if (string.IsNullOrEmpty(CurrentState.ProjectManifest))
{
NonInteractiveValidationFailed("No Aspire Manifest file was supplied.");
}
}
}
2 changes: 2 additions & 0 deletions src/Aspirate.Cli/Commands/Generate/GenerateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ public GenerateCommand() : base("generate", "Builds, pushes containers, generate
{
AddOption(SharedOptions.AspireProjectPath);
AddOption(SharedOptions.OutputPath);
AddOption(SharedOptions.NonInteractive);
AddOption(SharedOptions.SkipBuild);
}
}
7 changes: 5 additions & 2 deletions src/Aspirate.Cli/Commands/Generate/GenerateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ namespace Aspirate.Cli.Commands.Generate;

public sealed class GenerateOptions : ICommandOptions
{
public string ProjectPath { get; init; } = AspirateLiterals.DefaultAspireProjectPath;
public string OutputPath { get; init; } = AspirateLiterals.DefaultOutputPath;
public string ProjectPath { get; set; } = AspirateLiterals.DefaultAspireProjectPath;
public string OutputPath { get; set; } = AspirateLiterals.DefaultOutputPath;

public bool SkipBuild { get; set; } = false;
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 @@ -30,6 +30,13 @@ public static class SharedOptions
IsRequired = false,
};

public static Option<bool> SkipBuild => new(new[] { "--skip-build" })
{
Description = "Skips build and Push of containers",
Arity = ArgumentArity.ZeroOrOne,
IsRequired = false,
};

public static Option<bool> NonInteractive => new(new[] { "--non-interactive" })
{
Description = "Disables interactive mode for the command",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resour
return Task.FromResult(true);
}

public async Task BuildAndPushProjectContainer(KeyValuePair<string, Resource> resource)
public async Task BuildAndPushProjectContainer(KeyValuePair<string, Resource> resource, bool nonInteractive)
{
var project = resource.Value as AspireProject;

Expand All @@ -64,7 +64,7 @@ public async Task BuildAndPushProjectContainer(KeyValuePair<string, Resource> re
throw new InvalidOperationException($"Container details for project {resource.Key} not found.");
}

await containerCompositionService.BuildAndPushContainerForProject(project, containerDetails);
await containerCompositionService.BuildAndPushContainerForProject(project, containerDetails, nonInteractive);

_console.MarkupLine($"\t[green]({EmojiLiterals.CheckMark}) Done: [/] Building and Pushing container for project [blue]{resource.Key}[/]");
}
Expand Down
60 changes: 45 additions & 15 deletions src/Aspirate.Cli/Services/ContainerCompositionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ public sealed class ContainerCompositionService(IFileSystem filesystem, IAnsiCon
private readonly StringBuilder _stdOutBuffer = new();
private readonly StringBuilder _stdErrBuffer = new();

public async Task<bool> BuildAndPushContainerForProject(Project project, MsBuildContainerProperties containerDetails)
public async Task<bool> BuildAndPushContainerForProject(
Project project,
MsBuildContainerProperties containerDetails,
bool nonInteractive)
{
_stdErrBuffer.Clear();
_stdOutBuffer.Clear();
Expand All @@ -17,12 +20,15 @@ public async Task<bool> BuildAndPushContainerForProject(Project project, MsBuild
await AddProjectPublishArguments(argumentsBuilder, fullProjectPath);
AddContainerDetailsToArguments(argumentsBuilder, containerDetails);

await ExecuteCommand(argumentsBuilder, onFailed: HandleBuildErrors);
await ExecuteCommand(argumentsBuilder, nonInteractive, onFailed: HandleBuildErrors);

return true;
}

private async Task ExecuteCommand(ArgumentsBuilder argumentsBuilder, Func<ArgumentsBuilder, string, Task>? onFailed = default)
private async Task ExecuteCommand(
ArgumentsBuilder argumentsBuilder,
bool nonInteractive,
Func<ArgumentsBuilder, bool, string, Task>? onFailed = default)
{
var arguments = argumentsBuilder.RenderArguments();

Expand Down Expand Up @@ -51,6 +57,7 @@ private async Task ExecuteCommand(ArgumentsBuilder argumentsBuilder, Func<Argume
{
await onFailed?.Invoke(
argumentsBuilder,
nonInteractive,
_stdErrBuffer.Append(_stdOutBuffer).ToString());
}
break;
Expand All @@ -75,16 +82,16 @@ private static async Task<bool> ExecuteCommandNoOutput(string command, IReadOnly
return commandResult.ExitCode != 0;
}

private Task HandleBuildErrors(ArgumentsBuilder argumentsBuilder, string errors)
private Task HandleBuildErrors(ArgumentsBuilder argumentsBuilder, bool nonInteractive, string errors)
{
if (errors.Contains(DotNetSdkLiterals.DuplicateFileOutputError, StringComparison.OrdinalIgnoreCase))
{
return HandleDuplicateFilesInOutput(argumentsBuilder);
return HandleDuplicateFilesInOutput(argumentsBuilder, nonInteractive);
}

if (errors.Contains(DotNetSdkLiterals.NoContainerRegistryAccess, StringComparison.OrdinalIgnoreCase))
{
return HandleNoDockerRegistryAccess(argumentsBuilder);
return HandleNoDockerRegistryAccess(argumentsBuilder, nonInteractive);
}

if (errors.Contains(DotNetSdkLiterals.UnknownContainerRegistryAddress, StringComparison.OrdinalIgnoreCase))
Expand All @@ -96,25 +103,31 @@ private Task HandleBuildErrors(ArgumentsBuilder argumentsBuilder, string errors)
throw new ActionCausesExitException(9999);
}

private Task HandleDuplicateFilesInOutput(ArgumentsBuilder argumentsBuilder)
private Task HandleDuplicateFilesInOutput(ArgumentsBuilder argumentsBuilder, bool nonInteractive)
{
var shouldRetry = AskIfShouldRetryHandlingDuplicateFiles();
var shouldRetry = AskIfShouldRetryHandlingDuplicateFiles(nonInteractive);
if (shouldRetry)
{
argumentsBuilder.AppendArgument(DotNetSdkLiterals.ErrorOnDuplicatePublishOutputFilesArgument, "false");

_stdErrBuffer.Clear();
_stdOutBuffer.Clear();

return ExecuteCommand(argumentsBuilder, HandleBuildErrors);
return ExecuteCommand(argumentsBuilder, nonInteractive, HandleBuildErrors);
}

throw new ActionCausesExitException(9999);
}

private async Task HandleNoDockerRegistryAccess(ArgumentsBuilder argumentsBuilder)
private async Task HandleNoDockerRegistryAccess(ArgumentsBuilder argumentsBuilder, bool nonInteractive)
{
var shouldLogin = AskIfShouldLoginToDocker();
if (nonInteractive)
{
console.MarkupLine($"\r\n[red bold]{DotNetSdkLiterals.NoContainerRegistryAccess}: No access to container registry. Cannot attempt login in non interactive mode.[/]");
throw new ActionCausesExitException(1000);
}

var shouldLogin = AskIfShouldLoginToDocker(nonInteractive);
if (shouldLogin)
{
var credentials = GatherDockerCredentials();
Expand All @@ -125,16 +138,33 @@ private async Task HandleNoDockerRegistryAccess(ArgumentsBuilder argumentsBuilde
{
await ExecuteCommand(
argumentsBuilder,
nonInteractive,
onFailed: HandleBuildErrors);
}
}
}

private bool AskIfShouldRetryHandlingDuplicateFiles() =>
console.Confirm("\r\n[red bold]Implicitly, dotnet publish does not allow duplicate filenames to be output to the artefact directory at build time.\r\nWould you like to retry the build explicitly allowing them?[/]\r\n");
private bool AskIfShouldRetryHandlingDuplicateFiles(bool nonInteractive)
{
if (nonInteractive)
{
return true;
}

private bool AskIfShouldLoginToDocker() =>
console.Confirm("\r\nWe could not access the container registry during build. Do you want to login to the registry and retry?\r\n");
return console.Confirm(
"\r\n[red bold]Implicitly, dotnet publish does not allow duplicate filenames to be output to the artefact directory at build time.\r\nWould you like to retry the build explicitly allowing them?[/]\r\n");
}

private bool AskIfShouldLoginToDocker(bool nonInteractive)
{
if (nonInteractive)
{
return false;
}

return console.Confirm(
"\r\nWe could not access the container registry during build. Do you want to login to the registry and retry?\r\n");
}

private Dictionary<string, string?> GatherDockerCredentials()
{
Expand Down
1 change: 1 addition & 0 deletions src/Aspirate.Shared/Models/State/AspirateState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class AspirateState
public string? TemplatePath { get; set; }
public string? KubeContext { get; set; }
public bool NonInteractive { get; set; }
public bool SkipBuild { get; set; }
public List<string> AspireComponentsToProcess { get; set; } = [];
public Dictionary<string, Resource> LoadedAspireManifestResources { get; set; } = [];
public Dictionary<string, Resource> FinalResources { get; } = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ namespace Aspirate.Shared.Services;

public interface IContainerCompositionService
{
Task<bool> BuildAndPushContainerForProject(Project project, MsBuildContainerProperties containerDetails);
Task<bool> BuildAndPushContainerForProject(Project project, MsBuildContainerProperties containerDetails, bool nonInteractive);
}

0 comments on commit a3740e7

Please sign in to comment.