diff --git a/playground/PostgresEndToEnd/PostgresEndToEnd.AppHost/Program.cs b/playground/PostgresEndToEnd/PostgresEndToEnd.AppHost/Program.cs index 7c17e4c45e..1ba7337655 100644 --- a/playground/PostgresEndToEnd/PostgresEndToEnd.AppHost/Program.cs +++ b/playground/PostgresEndToEnd/PostgresEndToEnd.AppHost/Program.cs @@ -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 diff --git a/src/Aspire.Dashboard/Resources/Logs.Designer.cs b/src/Aspire.Dashboard/Resources/Logs.Designer.cs index 96e82b6ee1..0e3cd707ba 100644 --- a/src/Aspire.Dashboard/Resources/Logs.Designer.cs +++ b/src/Aspire.Dashboard/Resources/Logs.Designer.cs @@ -1,6 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs b/src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs index 5f582dfa98..781f91e003 100644 --- a/src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs +++ b/src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs @@ -5,9 +5,10 @@ namespace Aspire.Hosting.Postgres; -internal sealed class PgAdminContainerResource : ContainerResource +/// +/// Represents a container resource for PGAdmin. +/// +/// The name of the container resource. +public sealed class PgAdminContainerResource(string name) : ContainerResource(name) { - public PgAdminContainerResource(string name) : base(name) - { - } } diff --git a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs index 894d1d05d8..c9b6ccd4cc 100644 --- a/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs +++ b/src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs @@ -68,32 +68,52 @@ public static IResourceBuilder 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 /// /// The PostgreSQL server resource builder. - /// The host port for the application ui. + /// Resource builder for the /// The name of the container (Optional). /// A reference to the . - public static IResourceBuilder WithPgAdmin(this IResourceBuilder builder, int? hostPort = null, string? containerName = null) where T : PostgresServerResource + public static IResourceBuilder WithPgAdmin(this IResourceBuilder builder, Action>? configureContainer = null, string? containerName = null) where T : PostgresServerResource { - if (builder.ApplicationBuilder.Resources.OfType().Any()) + if (builder.ApplicationBuilder.Resources.OfType().SingleOrDefault() is { } existingPgAdminResource) { + var builderForExistingResource = builder.ApplicationBuilder.CreateResourceBuilder(existingPgAdminResource); + configureContainer?.Invoke(builderForExistingResource); return builder; } + else + { + builder.ApplicationBuilder.Services.TryAddLifecycleHook(); - builder.ApplicationBuilder.Services.TryAddLifecycleHook(); + 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; } + /// + /// Configures the host port that the PGAdmin resource is exposed on instead of using randomly assigned port. + /// + /// The resource builder for PGAdmin. + /// The port to bind on the host. If null is used random port will be assigned. + /// The resource builder for PGAdmin. + public static IResourceBuilder UseHostPort(this IResourceBuilder builder, int? port) + { + return builder.WithEndpoint("http", endpoint => + { + endpoint.Port = port; + }); + } + private static void SetPgAdminEnvironmentVariables(EnvironmentCallbackContext context) { // Disables pgAdmin authentication. diff --git a/src/Aspire.Hosting.PostgreSQL/PostgresContainerImageTags.cs b/src/Aspire.Hosting.PostgreSQL/PostgresContainerImageTags.cs index 446f8f4cff..70c8a662ab 100644 --- a/src/Aspire.Hosting.PostgreSQL/PostgresContainerImageTags.cs +++ b/src/Aspire.Hosting.PostgreSQL/PostgresContainerImageTags.cs @@ -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"; } diff --git a/src/Aspire.Hosting.PostgreSQL/PublicAPI.Unshipped.txt b/src/Aspire.Hosting.PostgreSQL/PublicAPI.Unshipped.txt index 0c6eb4d6d6..1ad6b97b34 100644 --- a/src/Aspire.Hosting.PostgreSQL/PublicAPI.Unshipped.txt +++ b/src/Aspire.Hosting.PostgreSQL/PublicAPI.Unshipped.txt @@ -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! builder, string! name, string? databaseName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.PostgresBuilderExtensions.AddPostgres(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, Aspire.Hosting.ApplicationModel.IResourceBuilder? userName = null, Aspire.Hosting.ApplicationModel.IResourceBuilder? password = null, int? port = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.PostgresBuilderExtensions.UseHostPort(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, int? port) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.PostgresBuilderExtensions.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.PostgresBuilderExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string? name = null, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! static Aspire.Hosting.PostgresBuilderExtensions.WithInitBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source, bool isReadOnly = true) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! -static Aspire.Hosting.PostgresBuilderExtensions.WithPgAdmin(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, int? hostPort = null, string? containerName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.PostgresBuilderExtensions.WithPgAdmin(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.Action!>? configureContainer = null, string? containerName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! diff --git a/tests/Aspire.Hosting.Tests/Postgres/AddPostgresTests.cs b/tests/Aspire.Hosting.Tests/Postgres/AddPostgresTests.cs index 40273ba807..3b14f0d199 100644 --- a/tests/Aspire.Hosting.Tests/Postgres/AddPostgresTests.cs +++ b/tests/Aspire.Hosting.Tests/Postgres/AddPostgresTests.cs @@ -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().Single(); @@ -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().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")); } @@ -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));