|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
using Aspire.Hosting.Lifecycle;
using Aspire.TestProject;
using Microsoft.Extensions.DependencyInjection;
public class TestProgram : IDisposable
{
private TestProgram(
string[] args,
string assemblyName,
bool disableDashboard,
bool includeIntegrationServices,
bool includeNodeApp,
bool allowUnsecuredTransport,
bool randomizePorts)
{
TestResourceNames resourcesToSkip = TestResourceNames.None;
for (int i = 0; i < args.Length; i++)
{
if (args[i].StartsWith("--skip-resources", StringComparison.InvariantCultureIgnoreCase))
{
if (args.Length > i + 1)
{
resourcesToSkip = TestResourceNamesExtensions.Parse(args[i + 1].Split(','));
break;
}
else
{
throw new ArgumentException("Missing argument to --skip-resources option.");
}
}
}
if (resourcesToSkip.HasFlag(TestResourceNames.dashboard))
{
disableDashboard = true;
}
var builder = DistributedApplication.CreateBuilder(new DistributedApplicationOptions()
{
Args = args,
DisableDashboard = disableDashboard,
AssemblyName = assemblyName,
AllowUnsecuredTransport = allowUnsecuredTransport,
});
builder.Configuration["DcpPublisher:DeleteResourcesOnShutdown"] = "true";
builder.Configuration["DcpPublisher:ResourceNameSuffix"] = $"{Random.Shared.Next():x}";
builder.Configuration["DcpPublisher:RandomizePorts"] = randomizePorts.ToString(CultureInfo.InvariantCulture);
AppBuilder = builder;
var serviceAPath = Path.Combine(Projects.TestProject_AppHost.ProjectPath, @"..\TestProject.ServiceA\TestProject.ServiceA.csproj");
ServiceABuilder = AppBuilder.AddProject("servicea", serviceAPath, launchProfileName: "http");
ServiceBBuilder = AppBuilder.AddProject<Projects.ServiceB>("serviceb", launchProfileName: "http");
ServiceCBuilder = AppBuilder.AddProject<Projects.ServiceC>("servicec", launchProfileName: "http");
WorkerABuilder = AppBuilder.AddProject<Projects.WorkerA>("workera");
if (includeNodeApp)
{
// Relative to this project so that it doesn't changed based on
// where this code is referenced from.
var path = Path.Combine(Projects.TestProject_AppHost.ProjectPath, "..", "nodeapp");
var scriptPath = Path.Combine(path, "app.js");
NodeAppBuilder = AppBuilder.AddNodeApp("nodeapp", scriptPath)
.WithHttpEndpoint(port: 5031, env: "PORT");
NpmAppBuilder = AppBuilder.AddNpmApp("npmapp", path)
.WithHttpEndpoint(port: 5032, env: "PORT");
}
if (includeIntegrationServices)
{
IntegrationServiceABuilder = AppBuilder.AddProject<Projects.IntegrationServiceA>("integrationservicea");
IntegrationServiceABuilder = IntegrationServiceABuilder.WithEnvironment("SKIP_RESOURCES", string.Join(',', resourcesToSkip));
if (!resourcesToSkip.HasFlag(TestResourceNames.sqlserver) || !resourcesToSkip.HasFlag(TestResourceNames.efsqlserver))
{
var sqlserverDbName = "tempdb";
var sqlserver = AppBuilder.AddSqlServer("sqlserver")
.AddDatabase(sqlserverDbName);
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(sqlserver);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.mysql) || !resourcesToSkip.HasFlag(TestResourceNames.efmysql))
{
var mysqlDbName = "mysqldb";
var mysql = AppBuilder.AddMySql("mysql")
.WithEnvironment("MYSQL_DATABASE", mysqlDbName)
.AddDatabase(mysqlDbName);
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(mysql);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.redis))
{
var redis = AppBuilder.AddRedis("redis");
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(redis);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.garnet))
{
var garnet = AppBuilder.AddGarnet("garnet");
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(garnet);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.valkey))
{
var valkey = AppBuilder.AddValkey("valkey");
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(valkey);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.postgres) || !resourcesToSkip.HasFlag(TestResourceNames.efnpgsql))
{
var postgresDbName = "postgresdb";
var postgres = AppBuilder.AddPostgres("postgres")
.WithEnvironment("POSTGRES_DB", postgresDbName)
.AddDatabase(postgresDbName);
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(postgres);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.rabbitmq))
{
var rabbitmq = AppBuilder.AddRabbitMQ("rabbitmq");
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(rabbitmq);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.mongodb))
{
var mongoDbName = "mymongodb";
var mongodb = AppBuilder.AddMongoDB("mongodb")
.AddDatabase(mongoDbName);
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(mongodb);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.oracledatabase))
{
var oracleDbName = "freepdb1";
var oracleDatabase = AppBuilder.AddOracle("oracledatabase")
.AddDatabase(oracleDbName);
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(oracleDatabase);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.kafka))
{
var kafka = AppBuilder.AddKafka("kafka");
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(kafka);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.cosmos) || !resourcesToSkip.HasFlag(TestResourceNames.efcosmos))
{
var cosmos = AppBuilder.AddAzureCosmosDB("cosmos").RunAsEmulator();
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(cosmos);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.eventhubs))
{
var eventHub = AppBuilder.AddAzureEventHubs("eventhubns").RunAsEmulator().AddEventHub("hub");
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(eventHub);
}
if (!resourcesToSkip.HasFlag(TestResourceNames.milvus))
{
builder.Configuration["Parameters:milvusApiKey"] = "root:Milvus";
var milvusApiKey = builder.AddParameter("milvusApiKey");
var milvus = AppBuilder.AddMilvus("milvus", milvusApiKey);
IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(milvus);
}
}
AppBuilder.Services.AddLifecycleHook<EndPointWriterHook>();
AppBuilder.Services.AddHttpClient();
}
public static TestProgram Create<T>(
string[]? args = null,
bool includeIntegrationServices = false,
bool includeNodeApp = false,
bool disableDashboard = true,
bool allowUnsecuredTransport = true,
bool randomizePorts = true)
{
return new TestProgram(
args ?? [],
assemblyName: typeof(T).Assembly.FullName!,
disableDashboard: disableDashboard,
includeIntegrationServices: includeIntegrationServices,
includeNodeApp: includeNodeApp,
allowUnsecuredTransport: allowUnsecuredTransport,
randomizePorts: randomizePorts);
}
public IDistributedApplicationBuilder AppBuilder { get; private set; }
public IResourceBuilder<ProjectResource> ServiceABuilder { get; private set; }
public IResourceBuilder<ProjectResource> ServiceBBuilder { get; private set; }
public IResourceBuilder<ProjectResource> ServiceCBuilder { get; private set; }
public IResourceBuilder<ProjectResource> WorkerABuilder { get; private set; }
public IResourceBuilder<ProjectResource>? IntegrationServiceABuilder { get; private set; }
public IResourceBuilder<NodeAppResource>? NodeAppBuilder { get; private set; }
public IResourceBuilder<NodeAppResource>? NpmAppBuilder { get; private set; }
public DistributedApplication? App { get; private set; }
public List<IResourceBuilder<ProjectResource>> ServiceProjectBuilders => [ServiceABuilder, ServiceBBuilder, ServiceCBuilder];
public async Task RunAsync(CancellationToken cancellationToken = default)
{
var app = Build();
await app.RunAsync(cancellationToken);
}
public DistributedApplication Build()
{
return App ??= AppBuilder.Build();
}
public void Run()
{
var app = Build();
app.Run();
}
public void Dispose() => App?.Dispose();
/// <summary>
/// Writes the allocated endpoints to the console in JSON format.
/// This allows for easier consumption by the external test process.
/// </summary>
private sealed class EndPointWriterHook : IDistributedApplicationLifecycleHook
{
public async Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken)
{
var root = new JsonObject();
foreach (var project in appModel.Resources.OfType<ProjectResource>())
{
var projectJson = new JsonObject();
root[project.Name] = projectJson;
var endpointsJsonArray = new JsonArray();
projectJson["Endpoints"] = endpointsJsonArray;
foreach (var endpoint in project.Annotations.OfType<EndpointAnnotation>())
{
var allocatedEndpoint = endpoint.AllocatedEndpoint;
if (allocatedEndpoint is null)
{
continue;
}
var endpointJsonObject = new JsonObject
{
["Name"] = endpoint.Name,
["Uri"] = allocatedEndpoint.UriString
};
endpointsJsonArray.Add(endpointJsonObject);
}
}
// write the whole json in a single line so it's easier to parse by the external process
await Console.Out.WriteLineAsync("$ENDPOINTS: " + JsonSerializer.Serialize(root, JsonSerializerOptions.Default));
}
}
}
|