Skip to content

Commit

Permalink
Add callback to WithPgAdmin to allow customization. (#3801)
Browse files Browse the repository at this point in the history
* Add callback to WithPgAdmin to allow customization.

* Fix const names.
  • Loading branch information
mitchdenny authored Apr 18, 2024
1 parent 3d98124 commit 06d8886
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 24 deletions.
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
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)
{
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

0 comments on commit 06d8886

Please sign in to comment.