diff --git a/src/Aspirate.Commands/Actions/Configuration/InitializeConfigurationAction.cs b/src/Aspirate.Commands/Actions/Configuration/InitializeConfigurationAction.cs index 31dc2aa..2c8fd78 100644 --- a/src/Aspirate.Commands/Actions/Configuration/InitializeConfigurationAction.cs +++ b/src/Aspirate.Commands/Actions/Configuration/InitializeConfigurationAction.cs @@ -30,6 +30,10 @@ private AspirateSettings PerformConfigurationBootstrapping() HandleContainerTag(aspirateConfiguration); + HandleContainerBuildArgs(aspirateConfiguration); + + HandleContainerBuildContext(aspirateConfiguration); + HandleTemplateDirectory(aspirateConfiguration); AddTemplatesToTemplateDirectoryIfRequired(aspirateConfiguration); @@ -37,6 +41,24 @@ private AspirateSettings PerformConfigurationBootstrapping() return aspirateConfiguration; } + private void HandleContainerBuildContext(AspirateSettings aspirateConfiguration) + { + if (!string.IsNullOrEmpty(CurrentState.ContainerBuildContext)) + { + aspirateConfiguration.ContainerSettings.Context = CurrentState.ContainerBuildContext; + Logger.MarkupLine($"[green]({EmojiLiterals.CheckMark}) Done:[/] Set [blue]'Container Build Context'[/] to [blue]'{aspirateConfiguration.ContainerSettings.Context}'[/]."); + } + } + + private void HandleContainerBuildArgs(AspirateSettings aspirateConfiguration) + { + if (CurrentState.ContainerBuildArgs?.Count > 0) + { + aspirateConfiguration.ContainerSettings.BuildArgs = CurrentState.ContainerBuildArgs; + Logger.MarkupLine($"[green]({EmojiLiterals.CheckMark}) Done:[/] Set [blue]'Container Build Args'[/] to [blue]'{string.Join(' ', aspirateConfiguration.ContainerSettings.BuildArgs)}'[/]."); + } + } + private void HandleContainerBuilder(AspirateSettings aspirateConfiguration) { if (!string.IsNullOrEmpty(CurrentState.ContainerBuilder)) diff --git a/src/Aspirate.Commands/Actions/Containers/BuildAndPushContainersFromProjectsAction.cs b/src/Aspirate.Commands/Actions/Containers/BuildAndPushContainersFromProjectsAction.cs index 247c31a..f83abde 100644 --- a/src/Aspirate.Commands/Actions/Containers/BuildAndPushContainersFromProjectsAction.cs +++ b/src/Aspirate.Commands/Actions/Containers/BuildAndPushContainersFromProjectsAction.cs @@ -30,7 +30,10 @@ public override async Task ExecuteAsync() { ContainerBuilder = CurrentState.ContainerBuilder.ToLower(), Prefix = CurrentState.ContainerRepositoryPrefix, - }, CurrentState.NonInteractive, CurrentState.RuntimeIdentifier); + Registry = CurrentState.ContainerRegistry, + BuildArgs = CurrentState.ContainerBuildArgs.ToDictionary(arg => arg.Split('=')[0], arg => arg.Split('=')[1]), + BuildContext = CurrentState.ContainerBuildContext + }, CurrentState.NonInteractive, CurrentState.RuntimeIdentifier, CurrentState.PreferDockerfile); } Logger.MarkupLine("[bold]Building and push completed for all selected project components.[/]"); diff --git a/src/Aspirate.Commands/Commands/Build/BuildCommand.cs b/src/Aspirate.Commands/Commands/Build/BuildCommand.cs index ea9c035..e0a0292 100644 --- a/src/Aspirate.Commands/Commands/Build/BuildCommand.cs +++ b/src/Aspirate.Commands/Commands/Build/BuildCommand.cs @@ -9,7 +9,10 @@ public BuildCommand() : base("build", "Builds and pushes containers") AddOption(ProjectPathOption.Instance); AddOption(AspireManifestOption.Instance); AddOption(ContainerBuilderOption.Instance); + AddOption(ContainerBuildContextOption.Instance); AddOption(ContainerImageTagOption.Instance); + AddOption(ContainerBuildArgsOption.Instance); + AddOption(PreferDockerfileOption.Instance); AddOption(ContainerRegistryOption.Instance); AddOption(ContainerRepositoryPrefixOption.Instance); AddOption(RuntimeIdentifierOption.Instance); diff --git a/src/Aspirate.Commands/Commands/Build/BuildOptions.cs b/src/Aspirate.Commands/Commands/Build/BuildOptions.cs index c2f14e0..3adda26 100644 --- a/src/Aspirate.Commands/Commands/Build/BuildOptions.cs +++ b/src/Aspirate.Commands/Commands/Build/BuildOptions.cs @@ -5,11 +5,12 @@ public sealed class BuildOptions : BaseCommandOptions, IBuildOptions, IContainer public string? ProjectPath { get; set; } public string? AspireManifest { get; set; } public string? ContainerBuilder { get; set; } + public string? ContainerBuildContext { get; set; } public string? ContainerRegistry { get; set; } - + public List? ContainerBuildArgs { get; set; } public string? ContainerRepositoryPrefix { get; set; } - public List? ContainerImageTags { get; set; } public string? RuntimeIdentifier { get; set; } public List? ComposeBuilds { get; set; } + public bool PreferDockerfile { get; set; } } diff --git a/src/Aspirate.Commands/Commands/Generate/GenerateCommand.cs b/src/Aspirate.Commands/Commands/Generate/GenerateCommand.cs index dcaa082..8182d75 100644 --- a/src/Aspirate.Commands/Commands/Generate/GenerateCommand.cs +++ b/src/Aspirate.Commands/Commands/Generate/GenerateCommand.cs @@ -12,7 +12,10 @@ public GenerateCommand() : base("generate", "Builds, pushes containers, generate AddOption(SkipBuildOption.Instance); AddOption(SkipFinalKustomizeGenerationOption.Instance); AddOption(ContainerBuilderOption.Instance); + AddOption(ContainerBuildContextOption.Instance); AddOption(ContainerImageTagOption.Instance); + AddOption(ContainerBuildArgsOption.Instance); + AddOption(PreferDockerfileOption.Instance); AddOption(ContainerRegistryOption.Instance); AddOption(ContainerRepositoryPrefixOption.Instance); AddOption(ImagePullPolicyOption.Instance); diff --git a/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs b/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs index d49c326..866e643 100644 --- a/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs +++ b/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs @@ -16,6 +16,8 @@ public sealed class GenerateOptions : BaseCommandOptions, public bool? SkipBuild { get; set; } public bool? SkipFinalKustomizeGeneration { get; set; } public string? ContainerBuilder { get; set; } + public List? ContainerBuildArgs { get; set; } + public string? ContainerBuildContext { get; set; } public string? ContainerRegistry { get; set; } public string? ContainerRepositoryPrefix { get; set; } public List? ContainerImageTags { get; set; } @@ -23,6 +25,7 @@ public sealed class GenerateOptions : BaseCommandOptions, public string? OutputFormat { get; set; } public string? RuntimeIdentifier { get; set; } public List? ComposeBuilds { get; set; } + public bool PreferDockerfile { get; set; } public string? PrivateRegistryUrl { get; set; } public string? PrivateRegistryUsername { get; set; } public string? PrivateRegistryPassword { get; set; } diff --git a/src/Aspirate.Commands/Commands/Init/InitCommand.cs b/src/Aspirate.Commands/Commands/Init/InitCommand.cs index 31f3e29..18b2a8d 100644 --- a/src/Aspirate.Commands/Commands/Init/InitCommand.cs +++ b/src/Aspirate.Commands/Commands/Init/InitCommand.cs @@ -9,6 +9,8 @@ public InitCommand() : base("init", "Initializes aspirate settings within your A { AddOption(ProjectPathOption.Instance); AddOption(ContainerBuilderOption.Instance); + AddOption(ContainerBuildArgsOption.Instance); + AddOption(ContainerBuildContextOption.Instance); AddOption(ContainerRegistryOption.Instance); AddOption(ContainerRepositoryPrefixOption.Instance); AddOption(ContainerImageTagOption.Instance); diff --git a/src/Aspirate.Commands/Commands/Init/InitOptions.cs b/src/Aspirate.Commands/Commands/Init/InitOptions.cs index afa5395..a8c50f6 100644 --- a/src/Aspirate.Commands/Commands/Init/InitOptions.cs +++ b/src/Aspirate.Commands/Commands/Init/InitOptions.cs @@ -5,11 +5,13 @@ public sealed class InitOptions : BaseCommandOptions, IInitOptions, IContainerOp public string? ProjectPath { get; set; } public string? ContainerBuilder { get; set; } + public string? ContainerBuildContext { get; set; } public string? ContainerRegistry { get; set; } public string? ContainerRepositoryPrefix { get; set; } public List? ContainerImageTags { get; set; } + public List? ContainerBuildArgs { get; set; } public string? TemplatePath { get; set; } } diff --git a/src/Aspirate.Commands/Commands/Run/RunCommand.cs b/src/Aspirate.Commands/Commands/Run/RunCommand.cs index 12d7f77..ffe9a6c 100644 --- a/src/Aspirate.Commands/Commands/Run/RunCommand.cs +++ b/src/Aspirate.Commands/Commands/Run/RunCommand.cs @@ -10,7 +10,10 @@ public RunCommand() : base("run", "Builds, pushes containers, and runs the curre AddOption(AspireManifestOption.Instance); AddOption(SkipBuildOption.Instance); AddOption(ContainerBuilderOption.Instance); + AddOption(ContainerBuildContextOption.Instance); AddOption(ContainerImageTagOption.Instance); + AddOption(ContainerBuildArgsOption.Instance); + AddOption(PreferDockerfileOption.Instance); AddOption(ContainerRegistryOption.Instance); AddOption(ContainerRepositoryPrefixOption.Instance); AddOption(ImagePullPolicyOption.Instance); diff --git a/src/Aspirate.Commands/Commands/Run/RunOptions.cs b/src/Aspirate.Commands/Commands/Run/RunOptions.cs index 7b198dd..bdfd287 100644 --- a/src/Aspirate.Commands/Commands/Run/RunOptions.cs +++ b/src/Aspirate.Commands/Commands/Run/RunOptions.cs @@ -13,6 +13,8 @@ public sealed class RunOptions : BaseCommandOptions, public string? Namespace { get; set; } public bool? SkipBuild { get; set; } public string? ContainerBuilder { get; set; } + public List? ContainerBuildArgs { get; set; } + public string? ContainerBuildContext { get; set; } public string? ContainerRegistry { get; set; } public string? ContainerRepositoryPrefix { get; set; } public List? ContainerImageTags { get; set; } diff --git a/src/Aspirate.Commands/Options/ContainerBuildArgsOption.cs b/src/Aspirate.Commands/Options/ContainerBuildArgsOption.cs new file mode 100644 index 0000000..e747105 --- /dev/null +++ b/src/Aspirate.Commands/Options/ContainerBuildArgsOption.cs @@ -0,0 +1,20 @@ +namespace Aspirate.Commands.Options; + +public sealed class ContainerBuildArgsOption : BaseOption?> +{ + private static readonly string[] _aliases = + [ + "-cba", + "--container-build-arg" + ]; + + private ContainerBuildArgsOption() : base(_aliases, "ASPIRATE_CONTAINER_BUILD_ARGS", null) + { + Name = nameof(IContainerOptions.ContainerBuildArgs); + Description = "The Container Build Arguments to use for all containers. In \"key\"=\"value\" format. Can include multiple times."; + Arity = ArgumentArity.ZeroOrMore; + IsRequired = false; + } + + public static ContainerBuildArgsOption Instance { get; } = new(); +} diff --git a/src/Aspirate.Commands/Options/ContainerBuildContextOption.cs b/src/Aspirate.Commands/Options/ContainerBuildContextOption.cs new file mode 100644 index 0000000..932f0bb --- /dev/null +++ b/src/Aspirate.Commands/Options/ContainerBuildContextOption.cs @@ -0,0 +1,20 @@ +namespace Aspirate.Commands.Options; + +public sealed class ContainerBuildContextOption : BaseOption +{ + private static readonly string[] _aliases = + [ + "-cbc", + "--container-build-context" + ]; + + private ContainerBuildContextOption() : base(_aliases, "ASPIRATE_CONTAINER_BUILD_CONTEXT", null) + { + Name = nameof(IContainerOptions.ContainerBuildContext); + Description = "The Container Build Context to use when Dockerfile is used to build projects"; + Arity = ArgumentArity.ExactlyOne; + IsRequired = false; + } + + public static ContainerBuildContextOption Instance { get; } = new(); +} diff --git a/src/Aspirate.Commands/Options/PreferDockerfileOption.cs b/src/Aspirate.Commands/Options/PreferDockerfileOption.cs new file mode 100644 index 0000000..bab4dae --- /dev/null +++ b/src/Aspirate.Commands/Options/PreferDockerfileOption.cs @@ -0,0 +1,16 @@ +namespace Aspirate.Commands.Options; + +public sealed class PreferDockerfileOption : BaseOption +{ + private static readonly string[] _aliases = ["--prefer-dockerfile"]; + + private PreferDockerfileOption() : base(_aliases, "ASPIRATE_PREFER_DOCKERFILE", null) + { + Name = nameof(IBuildOptions.PreferDockerfile); + Description = "Instructs to use Dockerfile when available to build project images"; + Arity = ArgumentArity.ZeroOrOne; + IsRequired = false; + } + + public static PreferDockerfileOption Instance { get; } = new(); +} diff --git a/src/Aspirate.Processors/Resources/Project/ProjectProcessor.cs b/src/Aspirate.Processors/Resources/Project/ProjectProcessor.cs index c7c31e9..ae392be 100644 --- a/src/Aspirate.Processors/Resources/Project/ProjectProcessor.cs +++ b/src/Aspirate.Processors/Resources/Project/ProjectProcessor.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace Aspirate.Processors.Resources.Project; /// @@ -68,7 +70,7 @@ private KubernetesDeploymentData PopulateKubernetesDeploymentData(BaseKubernetes .SetWithPrivateRegistry(options.WithPrivateRegistry.GetValueOrDefault()) .Validate(); - public async Task BuildAndPushProjectContainer(KeyValuePair resource, ContainerOptions options, bool nonInteractive, string? runtimeIdentifier) + public async Task BuildAndPushProjectContainer(KeyValuePair resource, ContainerOptions options, bool nonInteractive, string? runtimeIdentifier, bool preferDockerfile) { var project = resource.Value as ProjectResource; @@ -77,7 +79,29 @@ public async Task BuildAndPushProjectContainer(KeyValuePair re throw new InvalidOperationException($"Container details for project {resource.Key} not found."); } - await containerCompositionService.BuildAndPushContainerForProject(project, containerDetails, options, nonInteractive, runtimeIdentifier); + var dockerfileFile = !string.IsNullOrEmpty(containerDetails.DockerfileFile) ? containerDetails.DockerfileFile : Path.Combine(Path.GetDirectoryName(project.Path), "Dockerfile"); + + if (preferDockerfile && File.Exists(dockerfileFile)) + { + _console.MarkupLine($"[bold yellow]Using custom Dockerfile to build project {resource.Key}.[/]"); + + var dockerfileResource = new DockerfileResource() + { + Path = dockerfileFile, + Context = !string.IsNullOrEmpty(containerDetails.DockerfileContext) ? containerDetails.DockerfileContext : options.BuildContext, + Annotations = project.Annotations, + Bindings = project.Bindings, + Env = project.Env, + Name = project.Name, + BuildArgs = options.BuildArgs + }; + + await containerCompositionService.BuildAndPushContainerForDockerfile(dockerfileResource, options, nonInteractive); + } + else + { + await containerCompositionService.BuildAndPushContainerForProject(project, containerDetails, options, nonInteractive, runtimeIdentifier); + } _console.MarkupLine($"[green]({EmojiLiterals.CheckMark}) Done: [/] Building and Pushing container for project [blue]{resource.Key}[/]"); } diff --git a/src/Aspirate.Services/Implementations/ContainerDetailsService.cs b/src/Aspirate.Services/Implementations/ContainerDetailsService.cs index 9b654ad..6462eb1 100644 --- a/src/Aspirate.Services/Implementations/ContainerDetailsService.cs +++ b/src/Aspirate.Services/Implementations/ContainerDetailsService.cs @@ -13,7 +13,9 @@ public async Task GetContainerDetails( ContainerBuilderLiterals.ContainerRegistry, ContainerBuilderLiterals.ContainerRepository, ContainerBuilderLiterals.ContainerImageName, - ContainerBuilderLiterals.ContainerImageTag); + ContainerBuilderLiterals.ContainerImageTag, + ContainerBuilderLiterals.DockerfileFile, + ContainerBuilderLiterals.DockerfileContext); var msBuildProperties = JsonSerializer.Deserialize>(containerPropertiesJson ?? "{}"); diff --git a/src/Aspirate.Shared/Inputs/ContainerOptions.cs b/src/Aspirate.Shared/Inputs/ContainerOptions.cs index cb45008..97525ec 100644 --- a/src/Aspirate.Shared/Inputs/ContainerOptions.cs +++ b/src/Aspirate.Shared/Inputs/ContainerOptions.cs @@ -3,6 +3,8 @@ namespace Aspirate.Shared.Inputs; public class ContainerOptions { public string ContainerBuilder { get; set; } = default!; + public Dictionary? BuildArgs { get; set; } + public string? BuildContext { get; set; } public string ImageName { get; set; } = default!; public string Registry { get; set; } = default!; public string? Prefix { get; set; } diff --git a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IBuildOptions.cs b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IBuildOptions.cs index 271eafe..3aee5c5 100644 --- a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IBuildOptions.cs +++ b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IBuildOptions.cs @@ -4,4 +4,5 @@ public interface IBuildOptions { string? RuntimeIdentifier { get; set; } List? ComposeBuilds { get; set; } + bool PreferDockerfile { get; set; } } diff --git a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IContainerOptions.cs b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IContainerOptions.cs index 755d289..157bbf6 100644 --- a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IContainerOptions.cs +++ b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IContainerOptions.cs @@ -3,8 +3,9 @@ namespace Aspirate.Shared.Interfaces.Commands.Contracts; public interface IContainerOptions { string? ContainerBuilder { get; set; } - + string? ContainerBuildContext { get; set; } string? ContainerRegistry { get; set; } string? ContainerRepositoryPrefix { get; set; } List? ContainerImageTags { get; set; } + List? ContainerBuildArgs { get; set; } } diff --git a/src/Aspirate.Shared/Literals/ContainerBuilderLiterals.cs b/src/Aspirate.Shared/Literals/ContainerBuilderLiterals.cs index 2434d7e..dc1d0ca 100644 --- a/src/Aspirate.Shared/Literals/ContainerBuilderLiterals.cs +++ b/src/Aspirate.Shared/Literals/ContainerBuilderLiterals.cs @@ -7,4 +7,6 @@ public static class ContainerBuilderLiterals public const string ContainerRepository = "ContainerRepository"; public const string ContainerImageName = "ContainerImageName"; public const string ContainerImageTag = "ContainerImageTag"; + public const string DockerfileFile = "DockerfileFile"; + public const string DockerfileContext = "DockerfileContext"; } diff --git a/src/Aspirate.Shared/Literals/MsBuildPropertiesLiterals.cs b/src/Aspirate.Shared/Literals/MsBuildPropertiesLiterals.cs index 7ce5ee6..d648892 100644 --- a/src/Aspirate.Shared/Literals/MsBuildPropertiesLiterals.cs +++ b/src/Aspirate.Shared/Literals/MsBuildPropertiesLiterals.cs @@ -12,4 +12,6 @@ public static class MsBuildPropertiesLiterals public const string ContainerImageTagArgument = "ContainerImageTag"; public const string ContainerImageTagArguments = "ContainerImageTags"; public const string ErrorOnDuplicatePublishOutputFilesArgument = "ErrorOnDuplicatePublishOutputFiles"; + public const string DockerfileFileArgument = "DockerfileFile"; + public const string DockerfileContextArgument = "DockerfileContext"; } diff --git a/src/Aspirate.Shared/Models/Aspirate/AspirateContainerSettings.cs b/src/Aspirate.Shared/Models/Aspirate/AspirateContainerSettings.cs index cdd3bb5..8084e66 100644 --- a/src/Aspirate.Shared/Models/Aspirate/AspirateContainerSettings.cs +++ b/src/Aspirate.Shared/Models/Aspirate/AspirateContainerSettings.cs @@ -6,4 +6,6 @@ public class AspirateContainerSettings public string? RepositoryPrefix { get; set; } public List? Tags { get; set; } public string? Builder { get; set; } + public List? BuildArgs { get; set; } + public string? Context { get; set; } } diff --git a/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs b/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs index 91eb387..cd63151 100644 --- a/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs +++ b/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs @@ -37,6 +37,10 @@ public class AspirateState : [JsonPropertyName("namespace")] public string? Namespace { get; set; } + [RestorableStateProperty] + [JsonPropertyName("containerBuildContext")] + public string? ContainerBuildContext { get; set; } + [RestorableStateProperty] [JsonPropertyName("containerRegistry")] public string? ContainerRegistry { get; set; } @@ -45,6 +49,10 @@ public class AspirateState : [JsonPropertyName("containerImageTags")] public List? ContainerImageTags { get; set; } = ["latest"]; + [RestorableStateProperty] + [JsonPropertyName("containerBuildArgs")] + public List? ContainerBuildArgs { get; set; } + [RestorableStateProperty] [JsonPropertyName("runtimeIdentifier")] public string? RuntimeIdentifier { get; set; } @@ -128,6 +136,9 @@ public class AspirateState : [JsonIgnore] public bool? SkipBuild { get; set; } + [JsonIgnore] + public bool PreferDockerfile { get; set; } + [JsonIgnore] public string? AspireManifest { get; set; } diff --git a/src/Aspirate.Shared/Models/MsBuild/MsBuildContainerProperties.cs b/src/Aspirate.Shared/Models/MsBuild/MsBuildContainerProperties.cs index 4b15d7d..4a2ba05 100644 --- a/src/Aspirate.Shared/Models/MsBuild/MsBuildContainerProperties.cs +++ b/src/Aspirate.Shared/Models/MsBuild/MsBuildContainerProperties.cs @@ -15,6 +15,12 @@ public sealed class MsBuildContainerProperties : BaseMsBuildProperties [JsonPropertyName(MsBuildPropertiesLiterals.ContainerImageTagArgument)] public string? ContainerImageTag { get; set; } + [JsonPropertyName(MsBuildPropertiesLiterals.DockerfileFileArgument)] + public string? DockerfileFile { get; set; } + + [JsonPropertyName(MsBuildPropertiesLiterals.DockerfileContextArgument)] + public string? DockerfileContext { get; set; } + [JsonIgnore] public string? FullContainerImage { get; set; } }