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

Remove ConnectionString on With methods in Hosting App Model #84

Merged
merged 2 commits into from
Oct 6, 2023
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 @@ -5,7 +5,7 @@

namespace Aspire.Hosting;

public interface IDistributedApplicationComponentBuilder<T> where T : IDistributedApplicationComponent
public interface IDistributedApplicationComponentBuilder<out T> where T : IDistributedApplicationComponent
{
IDistributedApplicationBuilder ApplicationBuilder { get; }
T Component { get; }
Expand Down
11 changes: 11 additions & 0 deletions src/Aspire.Hosting/Postgres/IPostgresComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.Postgres;

public interface IPostgresComponent : IDistributedApplicationComponent
{
string? GetConnectionString(string? databaseName = null);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Aspire.Hosting.Postgres;

public static class PostgresContainerBuilderExtensions
public static class PostgresBuilderExtensions
{
private const string PasswordEnvVarName = "POSTGRES_PASSWORD";
private const string ConnectionStringEnvironmentName = "ConnectionStrings__";
Expand All @@ -32,50 +32,53 @@ public static IDistributedApplicationComponentBuilder<PostgresContainerComponent
});
}

private static async Task WritePostgresComponentToManifest(Utf8JsonWriter jsonWriter, CancellationToken cancellationToken)
public static IDistributedApplicationComponentBuilder<PostgresComponent> AddPostgres(this IDistributedApplicationBuilder builder, string name, string? connectionString)
{
var postgres = new PostgresComponent(name, connectionString);

return builder.AddComponent(postgres)
.WithAnnotation(new ManifestPublishingCallbackAnnotation((jsonWriter, cancellationToken) =>
WritePostgresComponentToManifest(jsonWriter, postgres.GetConnectionString(), cancellationToken)));
}

private static Task WritePostgresComponentToManifest(Utf8JsonWriter jsonWriter, CancellationToken cancellationToken) =>
WritePostgresComponentToManifest(jsonWriter, null, cancellationToken);

private static async Task WritePostgresComponentToManifest(Utf8JsonWriter jsonWriter, string? connectionString, CancellationToken cancellationToken)
{
jsonWriter.WriteString("type", "postgres.v1");
if (!string.IsNullOrEmpty(connectionString))
{
jsonWriter.WriteString("connectionString", connectionString);
}
await jsonWriter.FlushAsync(cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Sets a connection string for this service. The connection string will be available in the service's environment.
/// </summary>
public static IDistributedApplicationComponentBuilder<T> WithPostgresDatabase<T>(this IDistributedApplicationComponentBuilder<T> builder, IDistributedApplicationComponentBuilder<PostgresContainerComponent> postgresBuilder, string? databaseName = null, string? connectionName = null)
public static IDistributedApplicationComponentBuilder<T> WithPostgresDatabase<T>(this IDistributedApplicationComponentBuilder<T> builder, IDistributedApplicationComponentBuilder<IPostgresComponent> postgresBuilder, string? databaseName = null, string? connectionName = null)
where T : IDistributedApplicationComponentWithEnvironment
{
var postgres = postgresBuilder.Component;
connectionName = connectionName ?? postgresBuilder.Component.Name;

return builder.WithEnvironment((context) =>
{
var connectionStringName = $"{ConnectionStringEnvironmentName}{connectionName}";

if (context.PublisherName == "manifest")
{
context.EnvironmentVariables[$"{ConnectionStringEnvironmentName}{connectionName}"] = $"{{{postgresBuilder.Component.Name}.connectionString}}";
context.EnvironmentVariables[connectionStringName] = $"{{{postgres.Name}.connectionString}}";
return;
}

if (!postgresBuilder.Component.TryGetAllocatedEndPoints(out var allocatedEndpoints))
var connectionString = postgres.GetConnectionString(databaseName);
if (string.IsNullOrEmpty(connectionString))
{
throw new InvalidOperationException("Expected allocated endpoints!");
throw new DistributedApplicationException($"A connection string for Postgres '{postgres.Name}' could not be retrieved.");
}

if (!postgresBuilder.Component.TryGetLastAnnotation<PostgresPasswordAnnotation>(out var passwordAnnotation))
{
throw new InvalidOperationException($"Postgres does not have a password set!");
}

var allocatedEndpoint = allocatedEndpoints.Single(); // We should only have one endpoint for Postgres.

var baseConnectionString = $"Host={allocatedEndpoint.Address};Port={allocatedEndpoint.Port};Username=postgres;Password={passwordAnnotation.Password};";
var connectionString = databaseName == null ? baseConnectionString : $"{baseConnectionString}Database={databaseName};";

context.EnvironmentVariables[$"{ConnectionStringEnvironmentName}{connectionName}"] = connectionString;
context.EnvironmentVariables[connectionStringName] = connectionString;
});
}

public static IDistributedApplicationComponentBuilder<T> WithPostgresDatabase<T>(this IDistributedApplicationComponentBuilder<T> builder, string connectionName, string connectionString)
where T : IDistributedApplicationComponentWithEnvironment
{
return builder.WithEnvironment(ConnectionStringEnvironmentName + connectionName, connectionString);
}
}
18 changes: 18 additions & 0 deletions src/Aspire.Hosting/Postgres/PostgresComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.Postgres;

public class PostgresComponent(string name, string? connectionString) : DistributedApplicationComponent(name), IPostgresComponent
{
public string? GetConnectionString(string? databaseName = null) =>
connectionString is null ? null :
databaseName is null ?
connectionString :
connectionString.EndsWith(';') ?
$"{connectionString}Database={databaseName}" :
$"{connectionString};Database={databaseName}";

}
20 changes: 19 additions & 1 deletion src/Aspire.Hosting/Postgres/PostgresContainerComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@

namespace Aspire.Hosting.Postgres;

public class PostgresContainerComponent(string name) : ContainerComponent(name)
public class PostgresContainerComponent(string name) : ContainerComponent(name), IPostgresComponent
{
public string GetConnectionString(string? databaseName = null)
{
if (!this.TryGetAllocatedEndPoints(out var allocatedEndpoints))
{
throw new DistributedApplicationException("Expected allocated endpoints!");
}

if (!this.TryGetLastAnnotation<PostgresPasswordAnnotation>(out var passwordAnnotation))
{
throw new DistributedApplicationException($"Postgres does not have a password set!");
}

var allocatedEndpoint = allocatedEndpoints.Single(); // We should only have one endpoint for Postgres.

var baseConnectionString = $"Host={allocatedEndpoint.Address};Port={allocatedEndpoint.Port};Username=postgres;Password={passwordAnnotation.Password};";
var connectionString = databaseName is null ? baseConnectionString : $"{baseConnectionString}Database={databaseName};";
return connectionString;
}
}
11 changes: 11 additions & 0 deletions src/Aspire.Hosting/Redis/IRedisComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.Redis;

public interface IRedisComponent : IDistributedApplicationComponent
{
string? GetConnectionString();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Aspire.Hosting.Redis;

public static class RedisContainerBuilderExtensions
public static class RedisBuilderExtensions
{
private const string ConnectionStringEnvironmentName = "ConnectionStrings__";

Expand All @@ -22,41 +22,50 @@ public static IDistributedApplicationComponentBuilder<RedisContainerComponent> A
return componentBuilder;
}

private static async Task WriteRedisComponentToManifest(Utf8JsonWriter jsonWriter, CancellationToken cancellationToken)
public static IDistributedApplicationComponentBuilder<RedisComponent> AddRedis(this IDistributedApplicationBuilder builder, string name, string? connectionString)
{
var redis = new RedisComponent(name, connectionString);

return builder.AddComponent(redis)
.WithAnnotation(new ManifestPublishingCallbackAnnotation((jsonWriter, cancellationToken) =>
WriteRedisComponentToManifest(jsonWriter, redis.GetConnectionString(), cancellationToken)));
}

private static Task WriteRedisComponentToManifest(Utf8JsonWriter jsonWriter, CancellationToken cancellationToken) =>
WriteRedisComponentToManifest(jsonWriter, null, cancellationToken);

private static async Task WriteRedisComponentToManifest(Utf8JsonWriter jsonWriter, string? connectionString, CancellationToken cancellationToken)
{
jsonWriter.WriteString("type", "redis.v1");
if (!string.IsNullOrEmpty(connectionString))
{
jsonWriter.WriteString("connectionString", connectionString);
}
await jsonWriter.FlushAsync(cancellationToken).ConfigureAwait(false);
}

public static IDistributedApplicationComponentBuilder<T> WithRedis<T>(this IDistributedApplicationComponentBuilder<T> builder, IDistributedApplicationComponentBuilder<RedisContainerComponent> redisBuilder, string? connectionName = null)
public static IDistributedApplicationComponentBuilder<T> WithRedis<T>(this IDistributedApplicationComponentBuilder<T> builder, IDistributedApplicationComponentBuilder<IRedisComponent> redisBuilder, string? connectionName = null)
where T : IDistributedApplicationComponentWithEnvironment
{
connectionName = connectionName ?? redisBuilder.Component.Name;
var redis = redisBuilder.Component;
connectionName = connectionName ?? redis.Name;

return builder.WithEnvironment((context) =>
{
var connectionStringName = $"{ConnectionStringEnvironmentName}{connectionName}";

if (context.PublisherName == "manifest")
{
context.EnvironmentVariables[connectionStringName] = $"{{{redisBuilder.Component.Name}.connectionString}}";
context.EnvironmentVariables[connectionStringName] = $"{{{redis.Name}.connectionString}}";
return;
}

if (!redisBuilder.Component.TryGetAnnotationsOfType<AllocatedEndpointAnnotation>(out var allocatedEndpoints))
var connectionString = redis.GetConnectionString();
if (string.IsNullOrEmpty(connectionString))
{
throw new DistributedApplicationException("Redis component does not have endpoint annotation.");
throw new DistributedApplicationException($"A connection string for Redis '{redis.Name}' could not be retrieved.");
}

// We should only have one endpoint for Redis for local scenarios.
var endpoint = allocatedEndpoints.Single();
context.EnvironmentVariables[connectionStringName] = endpoint.EndPointString;
context.EnvironmentVariables[connectionStringName] = connectionString;
});
}

public static IDistributedApplicationComponentBuilder<T> WithRedis<T>(this IDistributedApplicationComponentBuilder<T> projectBuilder, string connectionName, string connectionString)
where T : IDistributedApplicationComponentWithEnvironment
{
return projectBuilder.WithEnvironment(ConnectionStringEnvironmentName + connectionName, connectionString);
}
}
11 changes: 11 additions & 0 deletions src/Aspire.Hosting/Redis/RedisComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.Redis;

public class RedisComponent(string name, string? connectionString) : DistributedApplicationComponent(name), IRedisComponent
{
public string? GetConnectionString() => connectionString;
}
13 changes: 12 additions & 1 deletion src/Aspire.Hosting/Redis/RedisContainerComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@

namespace Aspire.Hosting.Redis;

public class RedisContainerComponent(string name) : ContainerComponent(name)
public class RedisContainerComponent(string name) : ContainerComponent(name), IRedisComponent
{
public string GetConnectionString()
{
if (!this.TryGetAnnotationsOfType<AllocatedEndpointAnnotation>(out var allocatedEndpoints))
{
throw new DistributedApplicationException("Redis component does not have endpoint annotation.");
}

// We should only have one endpoint for Redis for local scenarios.
var endpoint = allocatedEndpoints.Single();
return endpoint.EndPointString;
}
}
11 changes: 11 additions & 0 deletions src/Aspire.Hosting/SqlServer/ISqlServerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.SqlServer;

public interface ISqlServerComponent : IDistributedApplicationComponent
{
string? GetConnectionString(string? databaseName = null);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Aspire.Hosting.SqlServer;

public static class SqlServerCloudApplicationBuilderExtensions
public static class SqlServerBuilderExtensions
{
private const string ConnectionStringEnvironmentName = "ConnectionStrings__";

Expand All @@ -24,15 +24,32 @@ public static IDistributedApplicationComponentBuilder<SqlServerContainerComponen
return componentBuilder;
}

private static async Task WriteSqlServerComponentToManifest(Utf8JsonWriter jsonWriter, CancellationToken cancellationToken)
public static IDistributedApplicationComponentBuilder<SqlServerComponent> AddSqlServer(this IDistributedApplicationBuilder builder, string name, string? connectionString)
{
var sqlServer = new SqlServerComponent(name, connectionString);

return builder.AddComponent(sqlServer)
.WithAnnotation(new ManifestPublishingCallbackAnnotation((jsonWriter, cancellationToken) =>
WriteSqlServerComponentToManifest(jsonWriter, sqlServer.GetConnectionString(), cancellationToken)));
}

private static Task WriteSqlServerComponentToManifest(Utf8JsonWriter jsonWriter, CancellationToken cancellationToken) =>
WriteSqlServerComponentToManifest(jsonWriter, null, cancellationToken);

private static async Task WriteSqlServerComponentToManifest(Utf8JsonWriter jsonWriter, string? connectionString, CancellationToken cancellationToken)
{
jsonWriter.WriteString("type", "sqlserver.v1");
if (!string.IsNullOrEmpty(connectionString))
{
jsonWriter.WriteString("connectionString", connectionString);
}
await jsonWriter.FlushAsync(cancellationToken).ConfigureAwait(false);
}

public static IDistributedApplicationComponentBuilder<T> WithSqlServer<T>(this IDistributedApplicationComponentBuilder<T> builder, IDistributedApplicationComponentBuilder<SqlServerContainerComponent> sqlBuilder, string? databaseName, string? connectionName = null)
where T : IDistributedApplicationComponentWithEnvironment
{
var sql = sqlBuilder.Component;
connectionName = connectionName ?? sqlBuilder.Component.Name;

return builder.WithEnvironment((context) =>
Expand All @@ -41,26 +58,16 @@ public static IDistributedApplicationComponentBuilder<T> WithSqlServer<T>(this I

if (context.PublisherName == "manifest")
{
context.EnvironmentVariables[connectionStringName] = $"{{{sqlBuilder.Component.Name}.connectionString}}";
context.EnvironmentVariables[connectionStringName] = $"{{{sql.Name}.connectionString}}";
return;
}

if (!sqlBuilder.Component.TryGetAnnotationsOfType<AllocatedEndpointAnnotation>(out var allocatedEndpoints))
var connectionString = sql.GetConnectionString(databaseName);
if (string.IsNullOrEmpty(connectionString))
{
throw new DistributedApplicationException("Sql component does not have endpoint annotation.");
throw new DistributedApplicationException($"A connection string for SqlServer '{sql.Name}' could not be retrieved.");
}

var endpoint = allocatedEndpoints.Single();

// HACK: Use the 127.0.0.1 address because localhost is resolving to [::1] following
// up with DCP on this issue.
context.EnvironmentVariables[connectionStringName] = $"Server=127.0.0.1,{endpoint.Port};Database={databaseName ?? "master"};User ID=sa;Password={sqlBuilder.Component.GeneratedPassword};TrustServerCertificate=true;";
context.EnvironmentVariables[connectionStringName] = connectionString;
});
}

public static IDistributedApplicationComponentBuilder<T> WithSqlServer<T>(this IDistributedApplicationComponentBuilder<T> projectBuilder, string connectionName, string connectionString)
where T : IDistributedApplicationComponentWithEnvironment
{
return projectBuilder.WithEnvironment(ConnectionStringEnvironmentName + connectionName, connectionString);
}
}
17 changes: 17 additions & 0 deletions src/Aspire.Hosting/SqlServer/SqlServerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.SqlServer;

public class SqlServerComponent(string name, string? connectionString) : DistributedApplicationComponent(name), ISqlServerComponent
{
public string? GetConnectionString(string? databaseName = null) =>
connectionString is null ? null :
databaseName is null ?
connectionString :
connectionString.EndsWith(';') ?
$"{connectionString}Database={databaseName}" :
$"{connectionString};Database={databaseName}";
}
Loading