Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add callback to WithPgAdmin to allow customization. #3801

Merged
merged 2 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// Containerized resources.
var db5 = builder.AddPostgres("pg4").WithPgAdmin().PublishAsContainer().AddDatabase("db5");
var db6 = builder.AddPostgres("pg5").WithPgAdmin().PublishAsContainer().AddDatabase("db6");
var pg6 = builder.AddPostgres("pg6").WithPgAdmin().PublishAsContainer();
var pg6 = builder.AddPostgres("pg6").WithPgAdmin(c => c.UseHostPort(8999).WithImageTag("8.3")).PublishAsContainer();
var db7 = pg6.AddDatabase("db7");
var db8 = pg6.AddDatabase("db8");
var db9 = pg6.AddDatabase("db9", "db8"); // different connection string (db9) on same database as db8
Expand Down
1 change: 1 addition & 0 deletions src/Aspire.Dashboard/Resources/Logs.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

namespace Aspire.Hosting.Postgres;

internal sealed class PgAdminContainerResource : ContainerResource
/// <summary>
/// Represents a container resource for PGAdmin.
/// </summary>
/// <param name="name">The name of the container resource.</param>
public sealed class PgAdminContainerResource(string name) : ContainerResource(name)
{
public PgAdminContainerResource(string name) : base(name)
{
}
}
46 changes: 33 additions & 13 deletions src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,32 +68,52 @@ public static IResourceBuilder<PostgresDatabaseResource> AddDatabase(this IResou
/// Adds a pgAdmin 4 administration and development platform for PostgreSQL to the application model. This version the package defaults to the 8.3 tag of the dpage/pgadmin4 container image
/// </summary>
/// <param name="builder">The PostgreSQL server resource builder.</param>
/// <param name="hostPort">The host port for the application ui.</param>
/// <param name="configureContainer">Resource builder for the </param>
/// <param name="containerName">The name of the container (Optional).</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithPgAdmin<T>(this IResourceBuilder<T> builder, int? hostPort = null, string? containerName = null) where T : PostgresServerResource
public static IResourceBuilder<T> WithPgAdmin<T>(this IResourceBuilder<T> builder, Action<IResourceBuilder<PgAdminContainerResource>>? configureContainer = null, string? containerName = null) where T : PostgresServerResource
{
if (builder.ApplicationBuilder.Resources.OfType<PgAdminContainerResource>().Any())
if (builder.ApplicationBuilder.Resources.OfType<PgAdminContainerResource>().SingleOrDefault() is { } existingPgAdminResource)
{
var builderForExistingResource = builder.ApplicationBuilder.CreateResourceBuilder(existingPgAdminResource);
configureContainer?.Invoke(builderForExistingResource);
return builder;
}
else
{
builder.ApplicationBuilder.Services.TryAddLifecycleHook<PgAdminConfigWriterHook>();

builder.ApplicationBuilder.Services.TryAddLifecycleHook<PgAdminConfigWriterHook>();
containerName ??= $"{builder.Resource.Name}-pgadmin";

containerName ??= $"{builder.Resource.Name}-pgadmin";
var pgAdminContainer = new PgAdminContainerResource(containerName);
var pgAdminContainerBuilder = builder.ApplicationBuilder.AddResource(pgAdminContainer)
.WithImage(PostgresContainerImageTags.PgAdminImage, PostgresContainerImageTags.PgAdminTag)
.WithImageRegistry(PostgresContainerImageTags.PgAdminRegistry)
.WithHttpEndpoint(targetPort: 80, name: "http")
.WithEnvironment(SetPgAdminEnvironmentVariables)
.WithBindMount(Path.GetTempFileName(), "/pgadmin4/servers.json")
.ExcludeFromManifest();

var pgAdminContainer = new PgAdminContainerResource(containerName);
builder.ApplicationBuilder.AddResource(pgAdminContainer)
.WithImage("dpage/pgadmin4", "8.3")
.WithImageRegistry(PostgresContainerImageTags.Registry)
.WithHttpEndpoint(targetPort: 80, port: hostPort, name: containerName)
.WithEnvironment(SetPgAdminEnvironmentVariables)
.WithBindMount(Path.GetTempFileName(), "/pgadmin4/servers.json")
.ExcludeFromManifest();
configureContainer?.Invoke(pgAdminContainerBuilder);
}

return builder;
}

/// <summary>
/// Configures the host port that the PGAdmin resource is exposed on instead of using randomly assigned port.
/// </summary>
/// <param name="builder">The resource builder for PGAdmin.</param>
/// <param name="port">The port to bind on the host. If null is used random port will be assigned.</param>
/// <returns>The resource builder for PGAdmin.</returns>
public static IResourceBuilder<PgAdminContainerResource> UseHostPort(this IResourceBuilder<PgAdminContainerResource> builder, int? port)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new right? Are we sure Use is the right prefix here? Seems odd that this method is only available for this resource type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's what we do with the other azure resources that have emulators:

public static IResourceBuilder<AzureStorageEmulatorResource> UseBlobPort(this IResourceBuilder<AzureStorageEmulatorResource> builder, int port)

{
return builder.WithEndpoint("http", endpoint =>
{
endpoint.Port = port;
});
}

private static void SetPgAdminEnvironmentVariables(EnvironmentCallbackContext context)
{
// Disables pgAdmin authentication.
Expand Down
3 changes: 3 additions & 0 deletions src/Aspire.Hosting.PostgreSQL/PostgresContainerImageTags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ internal static class PostgresContainerImageTags
public const string Registry = "docker.io";
public const string Image = "library/postgres";
public const string Tag = "16.2";
public const string PgAdminRegistry = "docker.io";
public const string PgAdminImage = "dpage/pgadmin4";
public const string PgAdminTag = "8.5";
}
5 changes: 4 additions & 1 deletion src/Aspire.Hosting.PostgreSQL/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ Aspire.Hosting.ApplicationModel.PostgresServerResource.PasswordParameter.get ->
Aspire.Hosting.ApplicationModel.PostgresServerResource.PostgresServerResource(string! name, Aspire.Hosting.ApplicationModel.ParameterResource? userName, Aspire.Hosting.ApplicationModel.ParameterResource! password) -> void
Aspire.Hosting.ApplicationModel.PostgresServerResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference!
Aspire.Hosting.ApplicationModel.PostgresServerResource.UserNameParameter.get -> Aspire.Hosting.ApplicationModel.ParameterResource?
Aspire.Hosting.Postgres.PgAdminContainerResource
Aspire.Hosting.Postgres.PgAdminContainerResource.PgAdminContainerResource(string! name) -> void
Aspire.Hosting.PostgresBuilderExtensions
static Aspire.Hosting.PostgresBuilderExtensions.AddDatabase(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.PostgresServerResource!>! builder, string! name, string? databaseName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.PostgresDatabaseResource!>!
static Aspire.Hosting.PostgresBuilderExtensions.AddPostgres(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.ParameterResource!>? userName = null, Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.ParameterResource!>? password = null, int? port = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.PostgresServerResource!>!
static Aspire.Hosting.PostgresBuilderExtensions.UseHostPort(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Postgres.PgAdminContainerResource!>! builder, int? port) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Postgres.PgAdminContainerResource!>!
static Aspire.Hosting.PostgresBuilderExtensions.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.PostgresServerResource!>! builder, string! source, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.PostgresServerResource!>!
static Aspire.Hosting.PostgresBuilderExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.PostgresServerResource!>! builder, string? name = null, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.PostgresServerResource!>!
static Aspire.Hosting.PostgresBuilderExtensions.WithInitBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.PostgresServerResource!>! builder, string! source, bool isReadOnly = true) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.PostgresServerResource!>!
static Aspire.Hosting.PostgresBuilderExtensions.WithPgAdmin<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>! builder, int? hostPort = null, string? containerName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>!
static Aspire.Hosting.PostgresBuilderExtensions.WithPgAdmin<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>! builder, System.Action<Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.Postgres.PgAdminContainerResource!>!>? configureContainer = null, string? containerName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>!
23 changes: 18 additions & 5 deletions tests/Aspire.Hosting.Tests/Postgres/AddPostgresTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,9 @@ public async Task VerifyManifestWithParameters()
[Fact]
public void WithPgAdminAddsContainer()
{

using var builder = TestDistributedApplicationBuilder.Create();
builder.AddPostgres("mypostgres").WithPgAdmin(8081);
builder.AddPostgres("mypostgres").WithPgAdmin(pga => pga.UseHostPort(8081));

var container = builder.Resources.Single(r => r.Name == "mypostgres-pgadmin");
var volume = container.Annotations.OfType<ContainerMountAnnotation>().Single();
Expand All @@ -354,12 +355,24 @@ public void WithPgAdminAddsContainer()
Assert.Equal("/pgadmin4/servers.json", volume.Target);
}

[Fact]
public void WithPgAdminWithCallbackMutatesImage()
{
using var builder = TestDistributedApplicationBuilder.Create();
builder.AddPostgres("mypostgres").WithPgAdmin(pga => pga.WithImageTag("8.3"));

var container = builder.Resources.Single(r => r.Name == "mypostgres-pgadmin");
var imageAnnotation = container.Annotations.OfType<ContainerImageAnnotation>().Single();

Assert.Equal("8.3", imageAnnotation.Tag);
}

[Fact]
public void WithPostgresTwiceEndsUpWithOneContainer()
{
using var builder = TestDistributedApplicationBuilder.Create();
builder.AddPostgres("mypostgres1").WithPgAdmin(8081);
builder.AddPostgres("mypostgres2").WithPgAdmin(8081);
builder.AddPostgres("mypostgres1").WithPgAdmin(pga => pga.UseHostPort(8081));
builder.AddPostgres("mypostgres2").WithPgAdmin(pga => pga.UseHostPort(8081));

builder.Resources.Single(r => r.Name.EndsWith("-pgadmin"));
}
Expand All @@ -370,8 +383,8 @@ public void WithPostgresTwiceEndsUpWithOneContainer()
public void WithPostgresProducesValidServersJsonFile(string containerHost)
{
var builder = DistributedApplication.CreateBuilder();
var pg1 = builder.AddPostgres("mypostgres1").WithPgAdmin(8081);
var pg2 = builder.AddPostgres("mypostgres2").WithPgAdmin(8081);
var pg1 = builder.AddPostgres("mypostgres1").WithPgAdmin(pga => pga.UseHostPort(8081));
var pg2 = builder.AddPostgres("mypostgres2").WithPgAdmin(pga => pga.UseHostPort(8081));

// Add fake allocated endpoints.
pg1.WithEndpoint("tcp", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 5001, containerHost));
Expand Down