Skip to content

Commit

Permalink
Make registry optional, but ask image pull policy
Browse files Browse the repository at this point in the history
  • Loading branch information
prom3theu5 committed Dec 6, 2023
1 parent 7352930 commit 9042b4c
Show file tree
Hide file tree
Showing 30 changed files with 141 additions and 41 deletions.
1 change: 1 addition & 0 deletions src/Aspirate.Cli/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ private static IServiceCollection AddActions(this IServiceCollection services) =
services
.RegisterAction<InitializeConfigurationAction>()
.RegisterAction<LoadConfigurationAction>()
.RegisterAction<AskImagePullPolicyAction>()
.RegisterAction<BuildAndPushContainersFromProjectsAction>()
.RegisterAction<BuildAndPushContainersFromDockerfilesAction>()
.RegisterAction<PopulateContainerDetailsForProjectsAction>()
Expand Down
2 changes: 1 addition & 1 deletion src/Aspirate.Cli/Templates/deployment.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ spec:
containers:
- name: {{name}}
image: {{containerImage}}
imagePullPolicy: Always
imagePullPolicy: {{imagePullPolicy}}
{{#if isDockerfile}}
{{#if hasPorts}}
ports:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace Aspirate.Commands.Actions.Configuration;

public class AskImagePullPolicyAction(
IAspirateConfigurationService configurationService,
IAnsiConsole console,
IServiceProvider serviceProvider) : BaseActionWithNonInteractiveValidation(serviceProvider)
{
public override Task<bool> ExecuteAsync()
{
if (CurrentState.NonInteractive)
{
return Task.FromResult(true);
}

if (!string.IsNullOrEmpty(CurrentState.ImagePullPolicy))
{
return Task.FromResult(true);
}

var choices = new List<string>
{
"IfNotPresent",
"Always",
"Never",
};

console.WriteLine();

var choice = console.Prompt(
new SelectionPrompt<string>()
.Title("Select image pull policy for manifests")
.PageSize(10)
.MoreChoicesText("[grey](Move up and down to reveal more choices)[/]")
.AddChoices(choices));

CurrentState.ImagePullPolicy = choice;

return Task.FromResult(true);
}

public override void ValidateNonInteractiveState()
{
if (string.IsNullOrEmpty(CurrentState.ImagePullPolicy))
{
NonInteractiveValidationFailed("Image pull policy is required when running in non-interactive mode.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ namespace Aspirate.Commands.Actions.Configuration;

public class LoadConfigurationAction(
IAspirateConfigurationService configurationService,
ISecretProvider secretProvider,
IServiceProvider serviceProvider) : BaseAction(serviceProvider)
{
public override Task<bool> ExecuteAsync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private async Task ProcessIndividualResourceManifests(KeyValuePair<string, Resou
return;
}

var success = await handler.CreateManifests(resource, CurrentState.OutputPath, CurrentState.TemplatePath);
var success = await handler.CreateManifests(resource, CurrentState.OutputPath, CurrentState.ImagePullPolicy, CurrentState.TemplatePath);

if (success && !CurrentState.IsDatabase(resource.Value))
{
Expand Down
3 changes: 3 additions & 0 deletions src/Aspirate.Commands/AspirateState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class AspirateState :
[JsonPropertyName("containerImageTag")]
public string? ContainerImageTag { get; set; }

[JsonPropertyName("imagePullPolicy")]
public string? ImagePullPolicy { get; set; }

[JsonPropertyName("containerBuilder")]
public string? ContainerBuilder { get; set; }

Expand Down
4 changes: 3 additions & 1 deletion src/Aspirate.Commands/Commands/BaseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ protected BaseCommand(string name, string description)

private static Task<int> ConstructCommand(TOptions options, IServiceCollection services)
{
services.RegisterAspirateSecretProvider(options.SecretProvider);
// todo: Implement Secrets.
// services.RegisterAspirateSecretProvider(options.SecretProvider);

var handler = ActivatorUtilities.CreateInstance<TOptionsHandler>(services.BuildServiceProvider());

Expand All @@ -36,5 +37,6 @@ private static Task<int> ConstructCommand(TOptions options, IServiceCollection s
Description = "Sets the secret provider. Default is 'Password'",
Arity = ArgumentArity.ExactlyOne,
IsRequired = false,
IsHidden = true,
};
}
1 change: 1 addition & 0 deletions src/Aspirate.Commands/Commands/Generate/GenerateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public GenerateCommand() : base("generate", "Builds, pushes containers, generate
AddOption(SharedOptions.ContainerBuilder);
AddOption(SharedOptions.ContainerImageTag);
AddOption(SharedOptions.ContainerRegistry);
AddOption(SharedOptions.ImagePullPolicy);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public override Task<int> HandleAsync(GenerateOptions options) =>
.QueueAction(nameof(PopulateContainerDetailsForProjectsAction))
.QueueAction(nameof(BuildAndPushContainersFromProjectsAction))
.QueueAction(nameof(BuildAndPushContainersFromDockerfilesAction))
.QueueAction(nameof(AskImagePullPolicyAction))
.QueueAction(nameof(GenerateKustomizeManifestsAction))
.QueueAction(nameof(GenerateFinalKustomizeManifestAction))
.ExecuteCommandsAsync();
Expand Down
2 changes: 2 additions & 0 deletions src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ public sealed class GenerateOptions : BaseCommandOptions, IGenerateOptions
public string? ContainerBuilder { get; set; } = "docker";
public string? ContainerRegistry { get; set; }
public string? ContainerImageTag { get; set; }

public string? ImagePullPolicy { get; set; }
}
2 changes: 2 additions & 0 deletions src/Aspirate.Commands/Commands/Generate/IGenerateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public interface IGenerateOptions

string? ContainerImageTag { get; set; }

string? ImagePullPolicy { get; set; }

bool NonInteractive { get; set; }

ProviderType SecretProvider { get; set; }
Expand Down
7 changes: 7 additions & 0 deletions src/Aspirate.Commands/Commands/SharedOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ public static class SharedOptions
IsRequired = false,
};

public static Option<string> ImagePullPolicy => new(new[] {"--image-pull-policy" })
{
Description = "The Image pull policy to use when generating manifests.",
Arity = ArgumentArity.ExactlyOne,
IsRequired = false,
};

public static Option<string> KubernetesContext => new(new[] { "-k", "--kube-context" })
{
Description = "The name of the kubernetes context to use",
Expand Down
2 changes: 1 addition & 1 deletion src/Aspirate.Processors/BaseProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ protected BaseProcessor(IFileSystem fileSystem, IAnsiConsole console)
public abstract Resource? Deserialize(ref Utf8JsonReader reader);

/// <inheritdoc />
public virtual Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string? templatePath = null)
public virtual Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string imagePullPolicy, string? templatePath = null)
{
_console.LogCreateManifestNotOverridden(GetType().Name);

Expand Down
2 changes: 1 addition & 1 deletion src/Aspirate.Processors/Dockerfile/DockerfileProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class DockerfileProcessor(
public override Resource? Deserialize(ref Utf8JsonReader reader) =>
JsonSerializer.Deserialize<AspireDockerfile>(ref reader);

public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string? templatePath = null)
public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string imagePullPolicy, string? templatePath = null)
{
var resourceOutputPath = Path.Combine(outputPath, resource.Key);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class PostgresDatabaseProcessor(IFileSystem fileSystem, IAnsiConsole cons
public override Resource? Deserialize(ref Utf8JsonReader reader) =>
JsonSerializer.Deserialize<PostgresDatabase>(ref reader);

public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string? templatePath = null) =>
public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string imagePullPolicy, string? templatePath = null) =>
// Do nothing for databases, they are there for configuration.
Task.FromResult(true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class PostgresServerProcessor(IFileSystem fileSystem, IAnsiConsole consol
public override Resource? Deserialize(ref Utf8JsonReader reader) =>
JsonSerializer.Deserialize<PostgresServer>(ref reader);

public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string? templatePath = null)
public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string imagePullPolicy, string? templatePath = null)
{
var resourceOutputPath = Path.Combine(outputPath, resource.Key);

Expand Down
5 changes: 3 additions & 2 deletions src/Aspirate.Processors/Project/ProjectProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class ProjectProcessor(
public override Resource? Deserialize(ref Utf8JsonReader reader) =>
JsonSerializer.Deserialize<AspireProject>(ref reader);

public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string? templatePath = null)
public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string imagePullPolicy, string? templatePath = null)
{
var resourceOutputPath = Path.Combine(outputPath, resource.Key);

Expand All @@ -42,7 +42,8 @@ public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resour
resource.Key,
containerDetails.FullContainerImage,
project.Env,
_manifests);
_manifests,
imagePullPolicy);

CreateDeployment(resourceOutputPath, data, templatePath);
CreateService(resourceOutputPath, data, templatePath);
Expand Down
4 changes: 3 additions & 1 deletion src/Aspirate.Processors/Project/ProjectTemplateData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ public class ProjectTemplateData(
string name,
string containerImage,
Dictionary<string, string> env,
IReadOnlyCollection<string> manifests)
IReadOnlyCollection<string> manifests,
string imagePullPolicy)
: BaseTemplateData(name, env, manifests)
{
public string ContainerImage { get; set; } = containerImage;
public bool IsProject { get; set; } = true;
public string ServiceType { get; set; } = "ClusterIP";
public string ImagePullPolicy { get; set; } = imagePullPolicy;
}
2 changes: 1 addition & 1 deletion src/Aspirate.Processors/RabbitMQ/RabbitMQProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class RabbitMqProcessor(IFileSystem fileSystem, IAnsiConsole console) : B
public override Resource? Deserialize(ref Utf8JsonReader reader) =>
JsonSerializer.Deserialize<AspireRabbit>(ref reader);

public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string? templatePath = null)
public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string imagePullPolicy, string? templatePath = null)
{
var resourceOutputPath = Path.Combine(outputPath, resource.Key);

Expand Down
2 changes: 1 addition & 1 deletion src/Aspirate.Processors/Redis/RedisProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class RedisProcessor(IFileSystem fileSystem, IAnsiConsole console) : Base
public override Resource? Deserialize(ref Utf8JsonReader reader) =>
JsonSerializer.Deserialize<AspireRedis>(ref reader);

public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string? templatePath)
public override Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string imagePullPolicy, string? templatePath)
{
var resourceOutputPath = Path.Combine(outputPath, resource.Key);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,10 @@ private async Task AddProjectPublishArguments(ArgumentsBuilder argumentsBuilder,

private static void AddContainerDetailsToArguments(ArgumentsBuilder argumentsBuilder, MsBuildContainerProperties containerDetails)
{
argumentsBuilder.AppendArgument(DotNetSdkLiterals.ContainerRegistryArgument, containerDetails.ContainerRegistry);
if (!string.IsNullOrEmpty(containerDetails.ContainerRegistry))
{
argumentsBuilder.AppendArgument(DotNetSdkLiterals.ContainerRegistryArgument, containerDetails.ContainerRegistry);
}

if (!string.IsNullOrEmpty(containerDetails.ContainerRepository))
{
Expand Down
40 changes: 30 additions & 10 deletions src/Aspirate.Services/Implementations/ContainerDetailsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,44 @@ private static void HandleTag(MsBuildContainerProperties containerDetails) =>

private static void HandleImage(MsBuildContainerProperties containerDetails)
{
if (!string.IsNullOrEmpty(containerDetails.ContainerImageName))
if (HasImageName(containerDetails))
{
_imageBuilder.Append($"/{containerDetails.ContainerImageName}");
_imageBuilder.Append($"{containerDetails.ContainerImageName}");
}
}

private static void HandleRepository(MsBuildContainerProperties containerDetails)
{
if (!string.IsNullOrEmpty(containerDetails.ContainerRepository))
if (HasRepository(containerDetails))
{
_imageBuilder.Append($"/{containerDetails.ContainerRepository}");
_imageBuilder.Append($"{containerDetails.ContainerRepository}");
}

if (HasImageName(containerDetails))
{
_imageBuilder.Append('/');
}
}

private static void HandleRegistry(MsBuildContainerProperties containerDetails) =>
_imageBuilder.Append($"{containerDetails.ContainerRegistry}");
private static void HandleRegistry(MsBuildContainerProperties containerDetails)
{
if (HasRegistry(containerDetails))
{
_imageBuilder.Append($"{containerDetails.ContainerRegistry}");
}

if (HasRepository(containerDetails))
{
_imageBuilder.Append('/');
}
}

private void EnsureContainerRegistryIsNotEmpty(
MsBuildContainerProperties details,
Project project,
string? containerRegistry)
{
if (!string.IsNullOrEmpty(details.ContainerRegistry))
if (HasRegistry(details))
{
return;
}
Expand All @@ -88,9 +103,9 @@ private void EnsureContainerRegistryIsNotEmpty(
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);
//
// 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(
Expand All @@ -111,4 +126,9 @@ private static void HandleTag(

msBuildProperties.Properties.ContainerImageTag = "latest";
}


private static bool HasImageName(MsBuildContainerProperties? containerDetails) => !string.IsNullOrEmpty(containerDetails?.ContainerImageName);
private static bool HasRepository(MsBuildContainerProperties? containerDetails) => !string.IsNullOrEmpty(containerDetails?.ContainerRepository);
private static bool HasRegistry(MsBuildContainerProperties? containerDetails) => !string.IsNullOrEmpty(containerDetails?.ContainerRegistry);
}
17 changes: 11 additions & 6 deletions src/Aspirate.Services/Implementations/ProjectPropertyService.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
namespace Aspirate.Services.Implementations;

public sealed class ProjectPropertyService(IFileSystem filesystem, IShellExecutionService shellExecutionService) : IProjectPropertyService
public sealed class ProjectPropertyService(IFileSystem filesystem, IShellExecutionService shellExecutionService, IAnsiConsole console) : IProjectPropertyService
{
public async Task<string?> GetProjectPropertiesAsync(string projectPath, params string[] propertyNames)
{
var fullProjectPath = filesystem.NormalizePath(projectPath);
var projectDirectory = filesystem.Path.GetDirectoryName(fullProjectPath);
var propertyValues = await ExecuteDotnetMsBuildGetPropertyCommand(projectDirectory, propertyNames);
var propertyValues = await ExecuteDotnetMsBuildGetPropertyCommand(fullProjectPath, propertyNames);

return propertyValues ?? null;
}

private async Task<string?> ExecuteDotnetMsBuildGetPropertyCommand(string workingDirectory, params string[] propertyNames)
private async Task<string?> ExecuteDotnetMsBuildGetPropertyCommand(string projectPath, params string[] propertyNames)
{
var argumentsBuilder = ArgumentsBuilder.Create()
.AppendArgument(DotNetSdkLiterals.MsBuildArgument, string.Empty, quoteValue: false);
.AppendArgument(DotNetSdkLiterals.MsBuildArgument, string.Empty, quoteValue: false)
.AppendArgument($"\"{projectPath}\"", string.Empty, quoteValue: false);

foreach (var propertyName in propertyNames)
{
Expand All @@ -25,10 +25,15 @@ public sealed class ProjectPropertyService(IFileSystem filesystem, IShellExecuti
{
Command = DotNetSdkLiterals.DotNetCommand,
ArgumentsBuilder = argumentsBuilder,
WorkingDirectory = workingDirectory,
PropertyKeySeparator = ':',
});

if (!result.Success)
{
console.MarkupLine($"[red]Failed to get project properties for '{projectPath}'.[/]");
throw new ActionCausesExitException(result.ExitCode);
}

return result.Success ? result.Output : null;
}
}
2 changes: 1 addition & 1 deletion src/Aspirate.Shared/Processors/IProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ public interface IProcessor
/// <summary>
/// Produces the output manifest file.
/// </summary>
Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string? aspirateSettings = null);
Task<bool> CreateManifests(KeyValuePair<string, Resource> resource, string outputPath, string imagePullPolicy, string? aspirateSettings = null);
}
1 change: 1 addition & 0 deletions tests/Aspirate.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
global using Aspirate.Secrets.Providers.Base64;
global using Aspirate.Secrets.Providers.Password;
global using Aspirate.Services;
global using Aspirate.Services.Implementations;
global using Aspirate.Services.Interfaces;
global using Aspirate.Shared.Exceptions;
global using Aspirate.Shared.Extensions;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using Aspirate.Services.Implementations;

namespace Aspirate.Tests.ServiceTests;

[UsesVerify]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using Aspirate.Services.Implementations;

namespace Aspirate.Tests.ServiceTests;

public class ContainerCompositionServiceTest
Expand Down
Loading

0 comments on commit 9042b4c

Please sign in to comment.