From 53d6bdcdd1f869d7cf30b1d6659bdeea054c946b Mon Sep 17 00:00:00 2001 From: David Sekula Date: Tue, 7 May 2024 15:22:09 +0100 Subject: [PATCH] Allow specifying container tag multiple times - fixes #116 (#180) --- .../Properties/launchSettings.json | 4 +- .../InitializeConfigurationAction.cs | 14 +++---- .../Configuration/LoadConfigurationAction.cs | 5 +-- ...dAndPushContainersFromDockerfilesAction.cs | 4 +- ...pulateContainerDetailsForProjectsAction.cs | 2 +- .../Commands/Build/BuildOptions.cs | 2 +- .../Commands/Generate/GenerateOptions.cs | 3 +- .../Commands/Init/InitOptions.cs | 2 +- .../Options/ContainerImageTagOption.cs | 8 ++-- .../Dockerfile/DockerfileProcessor.cs | 12 +++--- src/Aspirate.Services/GlobalUsings.cs | 1 + .../ContainerCompositionService.cs | 34 ++++++++++----- .../ContainerDetailsService.cs | 18 ++++---- .../DockerfileParametersExtensions.cs | 41 +++++++++++-------- .../Inputs/ContainerOptions.cs | 2 +- .../Commands/Contracts/IContainerOptions.cs | 3 +- .../Literals/DotNetSdkLiterals.cs | 1 + .../Literals/MsBuildPropertiesLiterals.cs | 1 + .../Aspirate/AspirateContainerSettings.cs | 2 +- .../Models/Aspirate/AspirateState.cs | 4 +- ...ction_InteractiveMode_Success.verified.txt | 5 ++- tests/Aspirate.Tests/AspirateTestBase.cs | 2 +- .../AspirateConfigurationServiceTest.cs | 2 +- .../ServiceTests/ContainerOptionsTests.cs | 4 +- ...t_WhenConfigurationFileExists.verified.txt | 4 +- ...ly_testOptions=FullParameters.verified.txt | 5 ++- ...ectly_testOptions=ImageAndTag.verified.txt | 5 ++- ...ons=RegistryAndPrefixAndImage.verified.txt | 5 ++- 28 files changed, 116 insertions(+), 79 deletions(-) diff --git a/src/Aspirate.Cli/Properties/launchSettings.json b/src/Aspirate.Cli/Properties/launchSettings.json index 7ea5446..5592314 100644 --- a/src/Aspirate.Cli/Properties/launchSettings.json +++ b/src/Aspirate.Cli/Properties/launchSettings.json @@ -14,7 +14,7 @@ }, "generate": { "commandName": "Project", - "commandLineArgs": "generate --skip-build --output-format compose", + "commandLineArgs": "generate --output-format helm", "workingDirectory": "/Users/prom3theu5/git/test-compose/AspireSample/AspireSample.AppHost", "hotReloadEnabled": false }, @@ -26,7 +26,7 @@ }, "build": { "commandName": "Project", - "commandLineArgs": "build", + "commandLineArgs": "build -ct hamburger -ct fries -ct mountaindew -ct icecream -ct pizza -ct hotdog -ct taco -ct burrito -ct sandwich -ct salad -ct soup -ct sushi -ct ramen -ct curry", "workingDirectory": "/Users/prom3theu5/git/test-compose/AspireSample/AspireSample.AppHost", "hotReloadEnabled": false }, diff --git a/src/Aspirate.Commands/Actions/Configuration/InitializeConfigurationAction.cs b/src/Aspirate.Commands/Actions/Configuration/InitializeConfigurationAction.cs index 0e0cd58..6660319 100644 --- a/src/Aspirate.Commands/Actions/Configuration/InitializeConfigurationAction.cs +++ b/src/Aspirate.Commands/Actions/Configuration/InitializeConfigurationAction.cs @@ -111,10 +111,10 @@ private void HandleContainerRepositoryPrefix(AspirateSettings aspirateConfigurat private void HandleContainerTag(AspirateSettings aspirateConfiguration) { - if (!string.IsNullOrEmpty(CurrentState.ContainerImageTag)) + if (CurrentState.ContainerImageTags?.Count > 0) { - aspirateConfiguration.ContainerSettings.Tag = CurrentState.ContainerImageTag; - Logger.MarkupLine($"[green]({EmojiLiterals.CheckMark}) Done:[/] Set [blue]'Container fallback tag'[/] to [blue]'{aspirateConfiguration.ContainerSettings.Tag}'[/]."); + aspirateConfiguration.ContainerSettings.Tags = CurrentState.ContainerImageTags; + Logger.MarkupLine($"[green]({EmojiLiterals.CheckMark}) Done:[/] Set [blue]'Container fallback tag'[/] to [blue]'{string.Join(';', aspirateConfiguration.ContainerSettings.Tags)}'[/]."); return; } @@ -126,9 +126,9 @@ private void HandleContainerTag(AspirateSettings aspirateConfiguration) return; } - var containerTag = Logger.Prompt(new TextPrompt("Please enter the container tag to use as a fall-back value:").PromptStyle("blue")); - aspirateConfiguration.ContainerSettings.Tag = containerTag; - Logger.MarkupLine($"[green]({EmojiLiterals.CheckMark}) Done:[/] Set [blue]'Container fallback tag'[/] to [blue]'{aspirateConfiguration.ContainerSettings.Tag}'[/]."); + var containerTag = Logger.Prompt(new TextPrompt("Please enter the container tags to use as a fall-back value, you can enter multiple values split via semi-colon ';' :").PromptStyle("blue")); + aspirateConfiguration.ContainerSettings.Tags = containerTag.Split(';').ToList(); + Logger.MarkupLine($"[green]({EmojiLiterals.CheckMark}) Done:[/] Set [blue]'Container fallback tag'[/] to [blue]'{aspirateConfiguration.ContainerSettings.Tags}'[/]."); } private void HandleTemplateDirectory(AspirateSettings aspirateConfiguration) @@ -242,7 +242,7 @@ public override void ValidateNonInteractiveState() Logger.ValidationFailed("Container Registry must be supplied when running in non-interactive mode."); } - if (string.IsNullOrEmpty(CurrentState.ContainerImageTag)) + if (CurrentState.ContainerImageTags?.Count == 0) { Logger.ValidationFailed("Container image tag must be supplied when running in non-interactive mode."); } diff --git a/src/Aspirate.Commands/Actions/Configuration/LoadConfigurationAction.cs b/src/Aspirate.Commands/Actions/Configuration/LoadConfigurationAction.cs index 9d2588c..cc97ca2 100644 --- a/src/Aspirate.Commands/Actions/Configuration/LoadConfigurationAction.cs +++ b/src/Aspirate.Commands/Actions/Configuration/LoadConfigurationAction.cs @@ -40,10 +40,7 @@ public override Task ExecuteAsync() CurrentState.ContainerRepositoryPrefix = aspirateSettings.ContainerSettings?.RepositoryPrefix ?? null; } - if (string.IsNullOrEmpty(CurrentState.ContainerImageTag)) - { - CurrentState.ContainerImageTag = aspirateSettings.ContainerSettings?.Tag ?? null; - } + CurrentState.ContainerImageTags ??= aspirateSettings.ContainerSettings?.Tags ?? null; Logger.MarkupLine($"[bold]Successfully loaded existing aspirate bootstrap settings from [blue]'{CurrentState.ProjectPath}'[/].[/]"); diff --git a/src/Aspirate.Commands/Actions/Containers/BuildAndPushContainersFromDockerfilesAction.cs b/src/Aspirate.Commands/Actions/Containers/BuildAndPushContainersFromDockerfilesAction.cs index d6f5b77..fb0d1d7 100644 --- a/src/Aspirate.Commands/Actions/Containers/BuildAndPushContainersFromDockerfilesAction.cs +++ b/src/Aspirate.Commands/Actions/Containers/BuildAndPushContainersFromDockerfilesAction.cs @@ -59,7 +59,7 @@ private void CacheContainerDetails(DockerfileProcessor? dockerfileProcessor) { Registry = CurrentState.ContainerRegistry, Prefix = CurrentState.ContainerRepositoryPrefix, - Tag = CurrentState.ContainerImageTag, + Tags = CurrentState.ContainerImageTags, }); } } @@ -121,7 +121,7 @@ private async Task PerformBuildAndPushes(DockerfileProcessor? dockerfileProcesso ImageName = resource.Key, Registry = CurrentState.ContainerRegistry, Prefix = CurrentState.ContainerRepositoryPrefix, - Tag = CurrentState.ContainerImageTag + Tags = CurrentState.ContainerImageTags }, CurrentState.NonInteractive); } } diff --git a/src/Aspirate.Commands/Actions/Containers/PopulateContainerDetailsForProjectsAction.cs b/src/Aspirate.Commands/Actions/Containers/PopulateContainerDetailsForProjectsAction.cs index 9fc1470..a458e6e 100644 --- a/src/Aspirate.Commands/Actions/Containers/PopulateContainerDetailsForProjectsAction.cs +++ b/src/Aspirate.Commands/Actions/Containers/PopulateContainerDetailsForProjectsAction.cs @@ -34,7 +34,7 @@ private async Task HandleProjects() { Registry = CurrentState.ContainerRegistry, Prefix = CurrentState.ContainerRepositoryPrefix, - Tag = CurrentState.ContainerImageTag, + Tags = CurrentState.ContainerImageTags, }); } } diff --git a/src/Aspirate.Commands/Commands/Build/BuildOptions.cs b/src/Aspirate.Commands/Commands/Build/BuildOptions.cs index 723a561..c2f14e0 100644 --- a/src/Aspirate.Commands/Commands/Build/BuildOptions.cs +++ b/src/Aspirate.Commands/Commands/Build/BuildOptions.cs @@ -9,7 +9,7 @@ public sealed class BuildOptions : BaseCommandOptions, IBuildOptions, IContainer public string? ContainerRepositoryPrefix { get; set; } - public string? ContainerImageTag { get; set; } + public List? ContainerImageTags { get; set; } public string? RuntimeIdentifier { get; set; } public List? ComposeBuilds { get; set; } } diff --git a/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs b/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs index 7e3c01d..3b0b927 100644 --- a/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs +++ b/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs @@ -14,11 +14,10 @@ public sealed class GenerateOptions : BaseCommandOptions, public string? Namespace { get; set; } public bool? SkipBuild { get; set; } public bool? SkipFinalKustomizeGeneration { get; set; } - public bool? SkipHelmGeneration { get; set; } public string? ContainerBuilder { get; set; } public string? ContainerRegistry { get; set; } public string? ContainerRepositoryPrefix { get; set; } - public string? ContainerImageTag { get; set; } + public List? ContainerImageTags { get; set; } public string? ImagePullPolicy { get; set; } public string? OutputFormat { get; set; } public string? RuntimeIdentifier { get; set; } diff --git a/src/Aspirate.Commands/Commands/Init/InitOptions.cs b/src/Aspirate.Commands/Commands/Init/InitOptions.cs index ee2abc6..afa5395 100644 --- a/src/Aspirate.Commands/Commands/Init/InitOptions.cs +++ b/src/Aspirate.Commands/Commands/Init/InitOptions.cs @@ -10,6 +10,6 @@ public sealed class InitOptions : BaseCommandOptions, IInitOptions, IContainerOp public string? ContainerRepositoryPrefix { get; set; } - public string? ContainerImageTag { get; set; } + public List? ContainerImageTags { get; set; } public string? TemplatePath { get; set; } } diff --git a/src/Aspirate.Commands/Options/ContainerImageTagOption.cs b/src/Aspirate.Commands/Options/ContainerImageTagOption.cs index 9c9bca7..e73a967 100644 --- a/src/Aspirate.Commands/Options/ContainerImageTagOption.cs +++ b/src/Aspirate.Commands/Options/ContainerImageTagOption.cs @@ -1,6 +1,6 @@ namespace Aspirate.Commands.Options; -public sealed class ContainerImageTagOption : BaseOption +public sealed class ContainerImageTagOption : BaseOption?> { private static readonly string[] _aliases = { @@ -10,9 +10,9 @@ public sealed class ContainerImageTagOption : BaseOption private ContainerImageTagOption() : base(_aliases, "ASPIRATE_CONTAINER_IMAGE_TAG", null) { - Name = nameof(IContainerOptions.ContainerImageTag); - Description = "The Container Image Tag to use as the fall-back value for all containers"; - Arity = ArgumentArity.ExactlyOne; + Name = nameof(IContainerOptions.ContainerImageTags); + Description = "The Container Image Tags to use for all containers. Can include multiple times."; + Arity = ArgumentArity.ZeroOrMore; IsRequired = false; } diff --git a/src/Aspirate.Processors/Resources/Dockerfile/DockerfileProcessor.cs b/src/Aspirate.Processors/Resources/Dockerfile/DockerfileProcessor.cs index 0ec3e5c..1016afe 100644 --- a/src/Aspirate.Processors/Resources/Dockerfile/DockerfileProcessor.cs +++ b/src/Aspirate.Processors/Resources/Dockerfile/DockerfileProcessor.cs @@ -21,7 +21,7 @@ public class DockerfileProcessor( $"{TemplateLiterals.ServiceType}.yaml", ]; - private readonly Dictionary _containerImageCache = []; + private readonly Dictionary> _containerImageCache = []; /// public override Resource? Deserialize(ref Utf8JsonReader reader) => @@ -35,12 +35,12 @@ public override Task CreateManifests(CreateManifestsOptions options) var dockerFile = options.Resource.Value as DockerfileResource; - if (!_containerImageCache.TryGetValue(options.Resource.Key, out var containerImage)) + if (!_containerImageCache.TryGetValue(options.Resource.Key, out var containerImages)) { throw new InvalidOperationException($"Container Image for dockerfile {options.Resource.Key} not found."); } - var data = PopulateKubernetesDeploymentData(options, containerImage, dockerFile); + var data = PopulateKubernetesDeploymentData(options, containerImages.First(), dockerFile); _manifestWriter.CreateDeployment(resourceOutputPath, data, options.TemplatePath); _manifestWriter.CreateService(resourceOutputPath, data, options.TemplatePath); @@ -78,7 +78,7 @@ public async Task BuildAndPushContainerForDockerfile(KeyValuePair resource, ContainerOptions options) { - _containerImageCache.Add(resource.Key, options.ToImageName(resource.Key)); + _containerImageCache.Add(resource.Key, options.ToImageNames(resource.Key)); _console.MarkupLine($"[green]({EmojiLiterals.CheckMark}) Done: [/] Setting container details for Dockerfile [blue]{resource.Key}[/]"); } @@ -111,7 +111,7 @@ public override ComposeService CreateComposeEntry(CreateComposeEntryOptions opti throw new InvalidOperationException($"Container Image for dockerfile {options.Resource.Key} not found."); } - newService = newService.WithImage(containerImage.ToLowerInvariant()); + newService = newService.WithImage(containerImage[0].ToLowerInvariant()); } response.Service = newService.Build(); @@ -128,7 +128,7 @@ public override List CreateKubernetesObjects(CreateKubernetesObjectsOpti throw new InvalidOperationException($"Container Image for dockerfile {options.Resource.Key} not found."); } - var data = PopulateKubernetesDeploymentData(options, containerImage, dockerFile); + var data = PopulateKubernetesDeploymentData(options, containerImage[0], dockerFile); return data.ToKubernetesObjects(); } diff --git a/src/Aspirate.Services/GlobalUsings.cs b/src/Aspirate.Services/GlobalUsings.cs index 38eceb1..c635aa0 100644 --- a/src/Aspirate.Services/GlobalUsings.cs +++ b/src/Aspirate.Services/GlobalUsings.cs @@ -1,4 +1,5 @@ global using System.IO.Abstractions; +global using System.Runtime.InteropServices; global using System.Security.Cryptography; global using System.Text; global using System.Text.Json; diff --git a/src/Aspirate.Services/Implementations/ContainerCompositionService.cs b/src/Aspirate.Services/Implementations/ContainerCompositionService.cs index e8397ba..4f2b3ea 100644 --- a/src/Aspirate.Services/Implementations/ContainerCompositionService.cs +++ b/src/Aspirate.Services/Implementations/ContainerCompositionService.cs @@ -1,5 +1,3 @@ -using System.Runtime.InteropServices; - namespace Aspirate.Services.Implementations; public sealed class ContainerCompositionService( @@ -49,27 +47,31 @@ public async Task BuildAndPushContainerForDockerfile(DockerfileResource do var fullDockerfilePath = filesystem.GetFullPath(dockerfileResource.Path); - var fullImage = options.ToImageName(dockerfileResource.Name); + var fullImages = options.ToImageNames(dockerfileResource.Name); - var result = await BuildContainer(dockerfileResource, options.ContainerBuilder, nonInteractive, fullImage, fullDockerfilePath); + var result = await BuildContainer(dockerfileResource, options.ContainerBuilder, nonInteractive, fullImages, fullDockerfilePath); CheckSuccess(result); - result = await PushContainer(options.ContainerBuilder, options.Registry, fullImage, nonInteractive); + result = await PushContainer(options.ContainerBuilder, options.Registry, fullImages, nonInteractive); CheckSuccess(result); return true; } - private async Task PushContainer(string builder, string? registry, string fullImage, bool? nonInteractive) + private async Task PushContainer(string builder, string? registry, List fullImages, bool? nonInteractive) { if (!string.IsNullOrEmpty(registry)) { var pushArgumentBuilder = ArgumentsBuilder .Create() - .AppendArgument(DockerLiterals.PushCommand, string.Empty, quoteValue: false) - .AppendArgument(fullImage.ToLower(), string.Empty, quoteValue: false); + .AppendArgument(DockerLiterals.PushCommand, string.Empty, quoteValue: false); + + foreach (var fullImage in fullImages) + { + pushArgumentBuilder.AppendArgument(fullImage.ToLower(), string.Empty, quoteValue: false, allowDuplicates: true); + } return await shellExecutionService.ExecuteCommand( new() @@ -84,13 +86,16 @@ private async Task PushContainer(string builder, string? reg return new ShellCommandResult(true, string.Empty, string.Empty, 0); } - private Task BuildContainer(DockerfileResource dockerfileResource, string builder, bool? nonInteractive, string tag, string fullDockerfilePath) + private Task BuildContainer(DockerfileResource dockerfileResource, string builder, bool? nonInteractive, List tags, string fullDockerfilePath) { var buildArgumentBuilder = ArgumentsBuilder .Create() - .AppendArgument(DockerLiterals.BuildCommand, string.Empty, quoteValue: false) - .AppendArgument(DockerLiterals.TagArgument, tag.ToLower()); + .AppendArgument(DockerLiterals.BuildCommand, string.Empty, quoteValue: false); + foreach (var tag in tags) + { + buildArgumentBuilder.AppendArgument(DockerLiterals.TagArgument, tag.ToLower(), allowDuplicates: true); + } if (dockerfileResource.Env is not null) { @@ -215,6 +220,13 @@ private static void AddContainerDetailsToArguments(ArgumentsBuilder argumentsBui argumentsBuilder.AppendArgument(DotNetSdkLiterals.ContainerImageNameArgument, containerDetails.ContainerImageName); } + if (containerDetails.ContainerImageTag is not null && containerDetails.ContainerImageTag.Contains(';')) + { + argumentsBuilder.AppendArgument(DotNetSdkLiterals.ContainerImageTagArguments, + $"\\\"{containerDetails.ContainerImageTag}\\\""); + return; + } + argumentsBuilder.AppendArgument(DotNetSdkLiterals.ContainerImageTagArgument, containerDetails.ContainerImageTag); } diff --git a/src/Aspirate.Services/Implementations/ContainerDetailsService.cs b/src/Aspirate.Services/Implementations/ContainerDetailsService.cs index 2195791..eb70112 100644 --- a/src/Aspirate.Services/Implementations/ContainerDetailsService.cs +++ b/src/Aspirate.Services/Implementations/ContainerDetailsService.cs @@ -27,7 +27,7 @@ public async Task GetContainerDetails( } // Fallback to latest tag if tag not specified. - HandleTag(msBuildProperties, options.Tag); + HandleTags(msBuildProperties, options.Tags); msBuildProperties.Properties.FullContainerImage = GetFullImage(msBuildProperties.Properties, options.Prefix); @@ -50,7 +50,7 @@ private static string GetFullImage(MsBuildContainerProperties containerDetails, } private static void HandleTag(MsBuildContainerProperties containerDetails) => - _imageBuilder.Append($":{containerDetails.ContainerImageTag}"); + _imageBuilder.Append($":{containerDetails.ContainerImageTag.Split(";").First()}"); private static void HandleImage(MsBuildContainerProperties containerDetails) { @@ -107,16 +107,15 @@ private void EnsureContainerRegistryIsNotEmpty( if (!string.IsNullOrEmpty(containerRegistry)) { details.ContainerRegistry = containerRegistry; - return; } // // console.MarkupLine($"[red bold]Required MSBuild property [blue]'ContainerRegistry'[/] not set in project [blue]'{project.Path}'. Cannot continue[/].[/]"); // throw new ActionCausesExitException(1); } - private static void HandleTag( + private static void HandleTags( MsBuildProperties msBuildProperties, - string containerImageTag) + List? containerImageTag) { if (!string.IsNullOrEmpty(msBuildProperties.Properties.ContainerImageTag)) { @@ -124,9 +123,14 @@ private static void HandleTag( } // Use our custom fall-back value if it exists - if (!string.IsNullOrEmpty(containerImageTag)) + if (containerImageTag is not null) { - msBuildProperties.Properties.ContainerImageTag = containerImageTag; + if (!containerImageTag.Contains("latest", StringComparer.OrdinalIgnoreCase)) + { + containerImageTag.Insert(0, "latest"); + } + + msBuildProperties.Properties.ContainerImageTag = string.Join(';', containerImageTag); return; } diff --git a/src/Aspirate.Shared/Extensions/DockerfileParametersExtensions.cs b/src/Aspirate.Shared/Extensions/DockerfileParametersExtensions.cs index 58e7b95..918569a 100644 --- a/src/Aspirate.Shared/Extensions/DockerfileParametersExtensions.cs +++ b/src/Aspirate.Shared/Extensions/DockerfileParametersExtensions.cs @@ -4,30 +4,39 @@ public static class DockerfileParametersExtensions { private static readonly StringBuilder _tagBuilder = new(); - public static string ToImageName(this ContainerOptions options, string resourceKey) + public static List ToImageNames(this ContainerOptions options, string resourceKey) { - _tagBuilder.Clear(); + var images = new List(); + options.Tags.Remove("latest"); + options.Tags.Insert(0, "latest"); - if (!string.IsNullOrEmpty(options.Registry)) + foreach (var tag in options.Tags) { - _tagBuilder.Append($"{options.Registry}/"); - } + _tagBuilder.Clear(); - if (!string.IsNullOrEmpty(options.Prefix)) - { - _tagBuilder.Append($"{options.Prefix}/"); - } + if (!string.IsNullOrEmpty(options.Registry)) + { + _tagBuilder.Append($"{options.Registry}/"); + } - if (string.IsNullOrEmpty(options.ImageName)) - { - options.ImageName = resourceKey; - } + if (!string.IsNullOrEmpty(options.Prefix)) + { + _tagBuilder.Append($"{options.Prefix}/"); + } + + if (string.IsNullOrEmpty(options.ImageName)) + { + options.ImageName = resourceKey; + } - _tagBuilder.Append(options.ImageName); + _tagBuilder.Append(options.ImageName); - AppendTag(options.Tag); + AppendTag(tag); + + images.Add(_tagBuilder.ToString()); + } - return _tagBuilder.ToString(); + return images; } private static void AppendTag(string? tag) diff --git a/src/Aspirate.Shared/Inputs/ContainerOptions.cs b/src/Aspirate.Shared/Inputs/ContainerOptions.cs index 7850ea9..cb45008 100644 --- a/src/Aspirate.Shared/Inputs/ContainerOptions.cs +++ b/src/Aspirate.Shared/Inputs/ContainerOptions.cs @@ -6,5 +6,5 @@ public class ContainerOptions public string ImageName { get; set; } = default!; public string Registry { get; set; } = default!; public string? Prefix { get; set; } - public string? Tag { get; set; } + public List? Tags { get; set; } = ["latest"]; } diff --git a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IContainerOptions.cs b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IContainerOptions.cs index c062c6d..755d289 100644 --- a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IContainerOptions.cs +++ b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IContainerOptions.cs @@ -6,6 +6,5 @@ public interface IContainerOptions string? ContainerRegistry { get; set; } string? ContainerRepositoryPrefix { get; set; } - - string? ContainerImageTag { get; set; } + List? ContainerImageTags { get; set; } } diff --git a/src/Aspirate.Shared/Literals/DotNetSdkLiterals.cs b/src/Aspirate.Shared/Literals/DotNetSdkLiterals.cs index 53cb6ea..141a391 100644 --- a/src/Aspirate.Shared/Literals/DotNetSdkLiterals.cs +++ b/src/Aspirate.Shared/Literals/DotNetSdkLiterals.cs @@ -30,6 +30,7 @@ public static class DotNetSdkLiterals public const string ContainerRepositoryArgument = $"-p:{MsBuildPropertiesLiterals.ContainerRepositoryArgument}"; public const string ContainerImageNameArgument = $"-p:{MsBuildPropertiesLiterals.ContainerImageNameArgument}"; public const string ContainerImageTagArgument = $"-p:{MsBuildPropertiesLiterals.ContainerImageTagArgument}"; + public const string ContainerImageTagArguments = $"-p:{MsBuildPropertiesLiterals.ContainerImageTagArguments}"; public const string ErrorOnDuplicatePublishOutputFilesArgument = $"-p:{MsBuildPropertiesLiterals.ErrorOnDuplicatePublishOutputFilesArgument}"; public const string ContainerPublishProfile = "DefaultContainer"; diff --git a/src/Aspirate.Shared/Literals/MsBuildPropertiesLiterals.cs b/src/Aspirate.Shared/Literals/MsBuildPropertiesLiterals.cs index ea29bfb..7ce5ee6 100644 --- a/src/Aspirate.Shared/Literals/MsBuildPropertiesLiterals.cs +++ b/src/Aspirate.Shared/Literals/MsBuildPropertiesLiterals.cs @@ -10,5 +10,6 @@ public static class MsBuildPropertiesLiterals public const string ContainerRepositoryArgument = "ContainerRepository"; public const string ContainerImageNameArgument = "ContainerImageName"; public const string ContainerImageTagArgument = "ContainerImageTag"; + public const string ContainerImageTagArguments = "ContainerImageTags"; public const string ErrorOnDuplicatePublishOutputFilesArgument = "ErrorOnDuplicatePublishOutputFiles"; } diff --git a/src/Aspirate.Shared/Models/Aspirate/AspirateContainerSettings.cs b/src/Aspirate.Shared/Models/Aspirate/AspirateContainerSettings.cs index aaa6efc..cdd3bb5 100644 --- a/src/Aspirate.Shared/Models/Aspirate/AspirateContainerSettings.cs +++ b/src/Aspirate.Shared/Models/Aspirate/AspirateContainerSettings.cs @@ -4,6 +4,6 @@ public class AspirateContainerSettings { public string? Registry { get; set; } public string? RepositoryPrefix { get; set; } - public string? Tag { get; set; } + public List? Tags { get; set; } public string? Builder { get; set; } } diff --git a/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs b/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs index c9daa5b..c69aead 100644 --- a/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs +++ b/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs @@ -32,8 +32,8 @@ public class AspirateState : public string? ContainerRegistry { get; set; } [RestorableStateProperty] - [JsonPropertyName("containerImageTag")] - public string? ContainerImageTag { get; set; } + [JsonPropertyName("containerImageTags")] + public List? ContainerImageTags { get; set; } = ["latest"]; [RestorableStateProperty] [JsonPropertyName("runtimeIdentifier")] diff --git a/tests/Aspirate.Tests/ActionsTests/Configuration/VerifyResults/InitializeConfigurationActionTests.ExecuteInitializeConfigurationAction_InteractiveMode_Success.verified.txt b/tests/Aspirate.Tests/ActionsTests/Configuration/VerifyResults/InitializeConfigurationActionTests.ExecuteInitializeConfigurationAction_InteractiveMode_Success.verified.txt index fca8ee0..1a42ba6 100644 --- a/tests/Aspirate.Tests/ActionsTests/Configuration/VerifyResults/InitializeConfigurationActionTests.ExecuteInitializeConfigurationAction_InteractiveMode_Success.verified.txt +++ b/tests/Aspirate.Tests/ActionsTests/Configuration/VerifyResults/InitializeConfigurationActionTests.ExecuteInitializeConfigurationAction_InteractiveMode_Success.verified.txt @@ -1,8 +1,11 @@ { + TemplatePath: /tests, ContainerSettings: { Registry: localhost:5001, RepositoryPrefix: prefix, - Tag: tests, + Tags: [ + null + ], Builder: docker } } \ No newline at end of file diff --git a/tests/Aspirate.Tests/AspirateTestBase.cs b/tests/Aspirate.Tests/AspirateTestBase.cs index 3f5ccf9..5c1bdf1 100644 --- a/tests/Aspirate.Tests/AspirateTestBase.cs +++ b/tests/Aspirate.Tests/AspirateTestBase.cs @@ -29,7 +29,7 @@ protected static AspirateState CreateAspirateState(bool nonInteractive = false, { NonInteractive = nonInteractive, ContainerRegistry = containerRegistry, - ContainerImageTag = containerImageTag, + ContainerImageTags = [containerImageTag], ContainerBuilder = containerBuilder, ContainerRepositoryPrefix = containerPrefix, TemplatePath = templatePath, diff --git a/tests/Aspirate.Tests/ServiceTests/AspirateConfigurationServiceTest.cs b/tests/Aspirate.Tests/ServiceTests/AspirateConfigurationServiceTest.cs index 707efe0..7ca2f55 100644 --- a/tests/Aspirate.Tests/ServiceTests/AspirateConfigurationServiceTest.cs +++ b/tests/Aspirate.Tests/ServiceTests/AspirateConfigurationServiceTest.cs @@ -28,7 +28,7 @@ private static MockFileSystem SetupTestFilesystem() ContainerSettings = new() { Registry = "SomeRegistry", - Tag = "SomeTag", + Tags = ["SomeTag"], }, }; diff --git a/tests/Aspirate.Tests/ServiceTests/ContainerOptionsTests.cs b/tests/Aspirate.Tests/ServiceTests/ContainerOptionsTests.cs index 6d61310..26fc661 100644 --- a/tests/Aspirate.Tests/ServiceTests/ContainerOptionsTests.cs +++ b/tests/Aspirate.Tests/ServiceTests/ContainerOptionsTests.cs @@ -15,7 +15,7 @@ public async Task ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly // Act - var fullImageName = containerParameters.ToImageName(testOptions.Options.ImageName); + var fullImageName = containerParameters.ToImageNames(testOptions.Options.ImageName); // Assert @@ -50,7 +50,7 @@ private static ContainerOptions CreateContainerParameters(string? testRegistry, Registry = testRegistry, Prefix = testRepositoryPrefix, ImageName = testImage, - Tag = testTag, + Tags = [testTag], }; public record TestContainerOptions(string Value, ContainerOptions Options); diff --git a/tests/Aspirate.Tests/ServiceTests/VerifyResults/AspirateConfigurationServiceTest.LoadConfigurationFile_ReturnsExpectedObject_WhenConfigurationFileExists.verified.txt b/tests/Aspirate.Tests/ServiceTests/VerifyResults/AspirateConfigurationServiceTest.LoadConfigurationFile_ReturnsExpectedObject_WhenConfigurationFileExists.verified.txt index 52732e2..4a79d5c 100644 --- a/tests/Aspirate.Tests/ServiceTests/VerifyResults/AspirateConfigurationServiceTest.LoadConfigurationFile_ReturnsExpectedObject_WhenConfigurationFileExists.verified.txt +++ b/tests/Aspirate.Tests/ServiceTests/VerifyResults/AspirateConfigurationServiceTest.LoadConfigurationFile_ReturnsExpectedObject_WhenConfigurationFileExists.verified.txt @@ -2,6 +2,8 @@ TemplatePath: SomeTemplatePath, ContainerSettings: { Registry: SomeRegistry, - Tag: SomeTag + Tags: [ + SomeTag + ] } } \ No newline at end of file diff --git a/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=FullParameters.verified.txt b/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=FullParameters.verified.txt index 844f25a..77e0332 100644 --- a/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=FullParameters.verified.txt +++ b/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=FullParameters.verified.txt @@ -1 +1,4 @@ -test-registry/test-repository/test-image:test-tag \ No newline at end of file +[ + test-registry/test-repository/test-image:latest, + test-registry/test-repository/test-image:test-tag +] \ No newline at end of file diff --git a/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=ImageAndTag.verified.txt b/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=ImageAndTag.verified.txt index e8c4459..355ec71 100644 --- a/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=ImageAndTag.verified.txt +++ b/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=ImageAndTag.verified.txt @@ -1 +1,4 @@ -test-image:test-tag \ No newline at end of file +[ + test-image:latest, + test-image:test-tag +] \ No newline at end of file diff --git a/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=RegistryAndPrefixAndImage.verified.txt b/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=RegistryAndPrefixAndImage.verified.txt index 430d90f..3f00bbd 100644 --- a/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=RegistryAndPrefixAndImage.verified.txt +++ b/tests/Aspirate.Tests/ServiceTests/VerifyResults/ContainerOptionsTests.ContainerParametersFullyPopulated_ShouldPopulateImageCorrectly_testOptions=RegistryAndPrefixAndImage.verified.txt @@ -1 +1,4 @@ -test-registry/test-repository/test-image:latest \ No newline at end of file +[ + test-registry/test-repository/test-image:latest, + test-registry/test-repository/test-image:latest +] \ No newline at end of file