From a92663d39bfa1b7fb1dd4f897fdda1897c590444 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Sat, 26 Oct 2024 00:00:05 -0500 Subject: [PATCH 01/24] Working on migrating to aspire --- Exceptionless.sln | 13 +- docker/docker-compose.dcproj | 11 -- .../Exceptionless.AppHost.csproj | 26 ++++ .../Extensions/ElasticsearchExtensions.cs | 121 ++++++++++++++++++ .../Extensions/ElasticsearchResource.cs | 72 +++++++++++ .../Extensions/KibanaConfigWriterHook.cs | 37 ++++++ .../Extensions/KibanaResource.cs | 9 ++ .../Extensions/RedisExtensions.cs | 28 ++++ .../Extensions/VolumeNameGenerator.cs | 65 ++++++++++ src/Exceptionless.AppHost/Program.cs | 43 +++++++ .../Properties/launchSettings.json | 29 +++++ .../appsettings.Development.json | 8 ++ src/Exceptionless.AppHost/appsettings.json | 9 ++ .../Configuration/ElasticsearchOptions.cs | 2 + src/Exceptionless.Web/ApmExtensions.cs | 1 + .../ClientApp/vite.config.ts | 5 +- .../Exceptionless.Web.csproj | 55 -------- .../Exceptionless.Tests/AppWebHostFactory.cs | 40 +++++- .../Controllers/EventControllerTests.cs | 6 +- .../Exceptionless.Tests.csproj | 2 + .../IntegrationTestsBase.cs | 46 ++++--- 21 files changed, 533 insertions(+), 95 deletions(-) delete mode 100644 docker/docker-compose.dcproj create mode 100644 src/Exceptionless.AppHost/Exceptionless.AppHost.csproj create mode 100644 src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs create mode 100644 src/Exceptionless.AppHost/Extensions/ElasticsearchResource.cs create mode 100644 src/Exceptionless.AppHost/Extensions/KibanaConfigWriterHook.cs create mode 100644 src/Exceptionless.AppHost/Extensions/KibanaResource.cs create mode 100644 src/Exceptionless.AppHost/Extensions/RedisExtensions.cs create mode 100644 src/Exceptionless.AppHost/Extensions/VolumeNameGenerator.cs create mode 100644 src/Exceptionless.AppHost/Program.cs create mode 100644 src/Exceptionless.AppHost/Properties/launchSettings.json create mode 100644 src/Exceptionless.AppHost/appsettings.Development.json create mode 100644 src/Exceptionless.AppHost/appsettings.json diff --git a/Exceptionless.sln b/Exceptionless.sln index 34cba061b0..04b99ad6db 100644 --- a/Exceptionless.sln +++ b/Exceptionless.sln @@ -11,7 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\build.yaml = .github\workflows\build.yaml CONTRIBUTING.md = CONTRIBUTING.md src\Directory.Build.props = src\Directory.Build.props - docker\docker-compose.yml = docker\docker-compose.yml Dockerfile = Dockerfile exceptionless.http = exceptionless.http global.json = global.json @@ -28,8 +27,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exceptionless.Tests", "test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exceptionless.Job", "src\Exceptionless.Job\Exceptionless.Job.csproj", "{788BA00C-FFBE-42A9-92A3-89E24FC137B5}" EndProject -Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker\docker-compose.dcproj", "{9F933018-9E8B-4649-8C9A-D217B5E1C184}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "http", "http", "{97ED03A0-8C49-4B15-8D93-C56AF4DDC30F}" ProjectSection(SolutionItems) = preProject tests\http\admin.http = tests\http\admin.http @@ -44,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "http", "http", "{97ED03A0-8 tests\http\webhooks.http = tests\http\webhooks.http EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exceptionless.AppHost", "src\Exceptionless.AppHost\Exceptionless.AppHost.csproj", "{EB1AF004-A00D-4016-BA97-5E89177B0074}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,10 +69,10 @@ Global {788BA00C-FFBE-42A9-92A3-89E24FC137B5}.Debug|Any CPU.Build.0 = Debug|Any CPU {788BA00C-FFBE-42A9-92A3-89E24FC137B5}.Release|Any CPU.ActiveCfg = Release|Any CPU {788BA00C-FFBE-42A9-92A3-89E24FC137B5}.Release|Any CPU.Build.0 = Release|Any CPU - {9F933018-9E8B-4649-8C9A-D217B5E1C184}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9F933018-9E8B-4649-8C9A-D217B5E1C184}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9F933018-9E8B-4649-8C9A-D217B5E1C184}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9F933018-9E8B-4649-8C9A-D217B5E1C184}.Release|Any CPU.Build.0 = Release|Any CPU + {EB1AF004-A00D-4016-BA97-5E89177B0074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB1AF004-A00D-4016-BA97-5E89177B0074}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB1AF004-A00D-4016-BA97-5E89177B0074}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB1AF004-A00D-4016-BA97-5E89177B0074}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/docker/docker-compose.dcproj b/docker/docker-compose.dcproj deleted file mode 100644 index 1a5989b744..0000000000 --- a/docker/docker-compose.dcproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - 2.1 - Linux - 9f933018-9e8b-4649-8c9a-d217b5e1c184 - - - - - \ No newline at end of file diff --git a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj new file mode 100644 index 0000000000..aebba20ded --- /dev/null +++ b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj @@ -0,0 +1,26 @@ + + + + + + Exe + net9.0 + enable + enable + true + a9c2ddcc-e51d-4cd1-9782-96e1d74eec87 + + + + + + + + + + + + + + + diff --git a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs new file mode 100644 index 0000000000..0460f4fe8e --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs @@ -0,0 +1,121 @@ +using Aspire.Hosting.Lifecycle; +using Aspire.Hosting.Utils; +using HealthChecks.Elasticsearch; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Aspire.Hosting; + +/// +/// Provides extension methods for adding Elasticsearch resources to the application model. +/// +public static class ElasticsearchBuilderExtensions +{ + private const int ElasticsearchPort = 9200; + private const int ElasticsearchInternalPort = 9300; + private const int KibanaPort = 5601; + + /// + /// Adds a Elasticsearch container to the application model. The default image is "docker.elastic.co/elasticsearch/elasticsearch". This version the package defaults to the 8.15.2 tag of the Elasticsearch container image + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The host port to bind the underlying container to. + /// A reference to the . + public static IResourceBuilder AddElasticsearch(this IDistributedApplicationBuilder builder, [ResourceName] string name, int? port = null) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(name); + + var elasticsearch = new ElasticsearchResource(name); + + string? connectionString = null; + ElasticsearchOptions? options = null; + + builder.Eventing.Subscribe(elasticsearch, async (@event, ct) => + { + connectionString = await elasticsearch.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); + if (connectionString is null) + { + throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{elasticsearch.Name}' resource but the connection string was null."); + } + + options = new ElasticsearchOptions(); + options.UseServer(connectionString); + }); + + var healthCheckKey = $"{name}_check"; + builder.Services.AddHealthChecks() + .Add(new HealthCheckRegistration( + healthCheckKey, + sp => new ElasticsearchHealthCheck(options!), + failureStatus: default, + tags: default, + timeout: default)); + + return builder.AddResource(elasticsearch) + .WithImage(ElasticsearchContainerImageTags.Image, ElasticsearchContainerImageTags.Tag) + .WithImageRegistry(ElasticsearchContainerImageTags.Registry) + .WithHttpEndpoint(targetPort: ElasticsearchPort, port: port, name: ElasticsearchResource.PrimaryEndpointName) + .WithEndpoint(targetPort: ElasticsearchInternalPort, name: ElasticsearchResource.InternalEndpointName) + .WithEnvironment("discovery.type", "single-node") + .WithEnvironment("xpack.security.enabled", "false") + .WithEnvironment("action.destructive_requires_name", "false") + .WithEnvironment("ES_JAVA_OPTS", "-Xms1g -Xmx1g") + .WithHealthCheck(healthCheckKey) + .PublishAsConnectionString(); + } + + public static IResourceBuilder WithKibana(this IResourceBuilder builder, Action>? configureContainer = null, string? containerName = null) + { + ArgumentNullException.ThrowIfNull(builder); + + if (builder.ApplicationBuilder.Resources.OfType().SingleOrDefault() is { } existingKibanaResource) + { + var builderForExistingResource = builder.ApplicationBuilder.CreateResourceBuilder(existingKibanaResource); + configureContainer?.Invoke(builderForExistingResource); + return builder; + } + else + { + containerName ??= $"{builder.Resource.Name}-kibana"; + + builder.ApplicationBuilder.Services.TryAddLifecycleHook(); + + var resource = new KibanaResource(containerName); + var resourceBuilder = builder.ApplicationBuilder.AddResource(resource) + .WithImage(ElasticsearchContainerImageTags.KibanaImage, ElasticsearchContainerImageTags.Tag) + .WithImageRegistry(ElasticsearchContainerImageTags.Registry) + .WithHttpEndpoint(targetPort: KibanaPort, name: containerName) + .WithEnvironment("xpack.security.enabled", "false") + .ExcludeFromManifest(); + + configureContainer?.Invoke(resourceBuilder); + + return builder; + } + } + + public static IResourceBuilder WithDataVolume(this IResourceBuilder builder, string? name = null) + { + ArgumentNullException.ThrowIfNull(builder); + + return builder.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "data"), "/usr/share/elasticsearch/data"); + } + + public static IResourceBuilder WithDataBindMount(this IResourceBuilder builder, string source) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(source); + + return builder.WithBindMount(source, "/usr/share/elasticsearch/data"); + } +} + +internal static class ElasticsearchContainerImageTags +{ + public const string Registry = "docker.elastic.co"; + public const string Image = "elasticsearch/elasticsearch"; + public const string KibanaImage = "kibana/kibana"; + public const string Tag = "8.15.2"; +} diff --git a/src/Exceptionless.AppHost/Extensions/ElasticsearchResource.cs b/src/Exceptionless.AppHost/Extensions/ElasticsearchResource.cs new file mode 100644 index 0000000000..ebdd1ea09d --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/ElasticsearchResource.cs @@ -0,0 +1,72 @@ +namespace Aspire.Hosting; + +/// +/// A resource that represents a Elasticsearch resource independent of the hosting model. +/// +public class ElasticsearchResource : ContainerResource, IResourceWithConnectionString +{ + // this endpoint is used for all API calls over HTTP. + // This includes search and aggregations, monitoring and anything else that uses a HTTP request. + // All client libraries will use this port to talk to Elasticsearch + internal const string PrimaryEndpointName = "http"; + + //this endpoint is a custom binary protocol used for communications between nodes in a cluster. + //For things like cluster updates, master elections, nodes joining/leaving, shard allocation + internal const string InternalEndpointName = "internal"; + + /// The name of the resource. + public ElasticsearchResource(string name) : base(name) + { + } + + private EndpointReference? _primaryEndpoint; + private EndpointReference? _internalEndpoint; + + /// + /// Gets the primary endpoint for the Elasticsearch. This endpoint is used for all API calls over HTTP. + /// + public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName); + + /// + /// Gets the internal endpoint for the Elasticsearch. This endpoint used for communications between nodes in a cluster + /// + public EndpointReference InternalEndpoint => _internalEndpoint ??= new(this, InternalEndpointName); + + /// + /// Gets the connection string expression for the Elasticsearch + /// + public ReferenceExpression ConnectionString => + ReferenceExpression.Create($"http://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + + + /// + /// Gets the connection string expression for the Elasticsearch server for the manifest. + /// + public ReferenceExpression ConnectionStringExpression + { + get + { + if (this.TryGetLastAnnotation(out var connectionStringAnnotation)) + { + return connectionStringAnnotation.Resource.ConnectionStringExpression; + } + + return ConnectionString; + } + } + + /// + /// Gets the connection string for the Elasticsearch server. + /// + /// A to observe while waiting for the task to complete. + /// A connection string for the Elasticsearch server in the form "http://host:port". + public ValueTask GetConnectionStringAsync(CancellationToken cancellationToken = default) + { + if (this.TryGetLastAnnotation(out var connectionStringAnnotation)) + { + return connectionStringAnnotation.Resource.GetConnectionStringAsync(cancellationToken); + } + + return ConnectionString.GetValueAsync(cancellationToken); + } +} diff --git a/src/Exceptionless.AppHost/Extensions/KibanaConfigWriterHook.cs b/src/Exceptionless.AppHost/Extensions/KibanaConfigWriterHook.cs new file mode 100644 index 0000000000..67bae87b43 --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/KibanaConfigWriterHook.cs @@ -0,0 +1,37 @@ +using System.Text; +using Aspire.Hosting.Lifecycle; +using Microsoft.Extensions.DependencyInjection; + +namespace Aspire.Hosting; + +internal class KibanaConfigWriterHook : IDistributedApplicationLifecycleHook +{ + public async Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken) + { + if (appModel.Resources.OfType().SingleOrDefault() is not { } kibanaResource) + return; + + var elasticsearchInstances = appModel.Resources.OfType(); + + if (!elasticsearchInstances.Any()) + return; + + var hostsVariableBuilder = new StringBuilder(); + + foreach (var elasticsearchInstance in elasticsearchInstances) + { + if (elasticsearchInstance.PrimaryEndpoint.IsAllocated) + { + var connectionString = await elasticsearchInstance.GetConnectionStringAsync(); + if (hostsVariableBuilder.Length > 0) + hostsVariableBuilder.Append(","); + hostsVariableBuilder.Append(elasticsearchInstance.PrimaryEndpoint.Scheme).Append("://").Append(elasticsearchInstance.PrimaryEndpoint.ContainerHost).Append(":").Append(elasticsearchInstance.PrimaryEndpoint.Port); + } + } + + kibanaResource.Annotations.Add(new EnvironmentCallbackAnnotation(context => + { + context.EnvironmentVariables.Add("ELASTICSEARCH_HOSTS", hostsVariableBuilder.ToString()); + })); + } +} diff --git a/src/Exceptionless.AppHost/Extensions/KibanaResource.cs b/src/Exceptionless.AppHost/Extensions/KibanaResource.cs new file mode 100644 index 0000000000..7e5031a46b --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/KibanaResource.cs @@ -0,0 +1,9 @@ +namespace Aspire.Hosting; + +/// +/// A resource that represents a Kibana container. +/// +/// The name of the resource. +public class KibanaResource(string name) : ContainerResource(name) +{ +} diff --git a/src/Exceptionless.AppHost/Extensions/RedisExtensions.cs b/src/Exceptionless.AppHost/Extensions/RedisExtensions.cs new file mode 100644 index 0000000000..71e75f94db --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/RedisExtensions.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; +using StackExchange.Redis; + +namespace Aspire.Hosting; + +public static class RedisExtensions +{ + public static IResourceBuilder WithClearCommand( + this IResourceBuilder builder) + { + builder.WithCommand( + "clear-cache", + "Clear Cache", + async _ => + { + var redisConnectionString = await builder.Resource.GetConnectionStringAsync() ?? + throw new InvalidOperationException("Unable to get the Redis connection string."); + + await using var connection = await ConnectionMultiplexer.ConnectAsync(redisConnectionString); + + await connection.GetDatabase().ExecuteAsync("FLUSHALL"); + + return CommandResults.Success(); + }, + context => context.ResourceSnapshot.HealthStatus is HealthStatus.Healthy ? ResourceCommandState.Enabled : ResourceCommandState.Disabled); + return builder; + } +} diff --git a/src/Exceptionless.AppHost/Extensions/VolumeNameGenerator.cs b/src/Exceptionless.AppHost/Extensions/VolumeNameGenerator.cs new file mode 100644 index 0000000000..6ecbc551f8 --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/VolumeNameGenerator.cs @@ -0,0 +1,65 @@ +namespace Aspire.Hosting.Utils; + +internal static class VolumeNameGenerator +{ + public static string CreateVolumeName(IResourceBuilder builder, string suffix) where T : IResource + { + if (!HasOnlyValidChars(suffix)) + { + throw new ArgumentException($"The suffix '{suffix}' contains invalid characters. Only [a-zA-Z0-9_.-] are allowed.", nameof(suffix)); + } + + // Creates a volume name with the form < c > $"{applicationName}-{sha256 of apphost path}-{resourceName}-{suffix}, e.g. "myapplication-a345f2451-postgres-data". + // Create volume name like "{Sanitize(appname).Lower()}-{sha256.Lower()}-postgres-data" + + // Compute a short hash of the content root path to differentiate between multiple AppHost projects with similar volume names + var safeApplicationName = Sanitize(builder.ApplicationBuilder.Environment.ApplicationName).ToLowerInvariant(); + var applicationHash = builder.ApplicationBuilder.Configuration["AppHost:Sha256"]![..10].ToLowerInvariant(); + var resourceName = builder.Resource.Name; + return $"{safeApplicationName}-{applicationHash}-{resourceName}-{suffix}"; + } + + public static string Sanitize(string name) + { + return string.Create(name.Length, name, static (s, name) => + { + // According to the error message from docker CLI, volume names must be of form "[a-zA-Z0-9][a-zA-Z0-9_.-]" + var nameSpan = name.AsSpan(); + + for (var i = 0; i < nameSpan.Length; i++) + { + var c = nameSpan[i]; + + s[i] = IsValidChar(i, c) ? c : '_'; + } + }); + } + + private static bool HasOnlyValidChars(string value) + { + for (var i = 0; i < value.Length; i++) + { + if (!IsValidChar(i, value[i])) + { + return false; + } + } + return true; + } + + private static bool IsValidChar(int i, char c) + { + if (i == 0 && !(char.IsAsciiLetter(c) || char.IsNumber(c))) + { + // First char must be a letter or number + return false; + } + else if (!(char.IsAsciiLetter(c) || char.IsNumber(c) || c == '_' || c == '.' || c == '-')) + { + // Subsequent chars must be a letter, number, underscore, period, or hyphen + return false; + } + + return true; + } +} diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs new file mode 100644 index 0000000000..9b65c0b8f1 --- /dev/null +++ b/src/Exceptionless.AppHost/Program.cs @@ -0,0 +1,43 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var elastic = builder.AddElasticsearch("Elasticsearch", port: 9200) + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerName("Exceptionless-Elasticsearch") + .WithDataVolume("exceptionless.data.v1") + .WithKibana(b => b.WithLifetime(ContainerLifetime.Persistent).WithContainerName("Exceptionless-Kibana")); + +var cache = builder.AddRedis("Redis", port: 6379) + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerName("Exceptionless-Redis") + .WithClearCommand() + .WithRedisInsight(b => b.WithLifetime(ContainerLifetime.Persistent).WithContainerName("Exceptionless-RedisInsight")); + +builder.AddContainer("Mail", "mailhog/mailhog") + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerName("Exceptionless-Mail") + .WithEndpoint(8025, 8025, "http") + .WithEndpoint(1025, 1025); + +var job = builder.AddProject("Jobs", "AllJobs") + .WithReference(cache) + .WithReference(elastic) + .WaitFor(elastic) + .WaitFor(cache) + .WithHttpHealthCheck("/health"); + +var api = builder.AddProject("Api", "Exceptionless API") + .WithReference(cache) + .WithReference(elastic) + .WaitFor(elastic) + .WaitFor(cache) + .WithHttpHealthCheck("/health"); + +builder.AddNpmApp("Web", "../../src/Exceptionless.Web/ClientApp", "dev") + .WithReference(api) + .WithEndpoint(scheme: "http", env: "PORT"); + +builder.AddNpmApp("AngularWeb", "../../src/Exceptionless.Web/ClientApp.angular", "serve") + .WithReference(api) + .WithEndpoint(scheme: "http", env: "PORT"); + +builder.Build().Run(); diff --git a/src/Exceptionless.AppHost/Properties/launchSettings.json b/src/Exceptionless.AppHost/Properties/launchSettings.json new file mode 100644 index 0000000000..a5fb2e64f3 --- /dev/null +++ b/src/Exceptionless.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17056;http://localhost:15161", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21210", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22299" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15161", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19113", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20111" + } + } + } +} diff --git a/src/Exceptionless.AppHost/appsettings.Development.json b/src/Exceptionless.AppHost/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/src/Exceptionless.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Exceptionless.AppHost/appsettings.json b/src/Exceptionless.AppHost/appsettings.json new file mode 100644 index 0000000000..31c092aa45 --- /dev/null +++ b/src/Exceptionless.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs b/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs index 38eabafe72..2340f21ae7 100644 --- a/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs +++ b/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs @@ -33,6 +33,8 @@ public static ElasticsearchOptions ReadFromConfiguration(IConfiguration config, options.ReindexCutOffDate = config.GetValue(nameof(options.ReindexCutOffDate), DateTime.MinValue); string? connectionString = config.GetConnectionString("Elasticsearch"); + if (connectionString !=null && (connectionString.StartsWith("http://") || connectionString.StartsWith("https://"))) + connectionString = "server=" + connectionString; ParseConnectionString(connectionString, options, appOptions.AppMode); string? connectionStringToMigrate = config.GetConnectionString("ElasticsearchToMigrate"); diff --git a/src/Exceptionless.Web/ApmExtensions.cs b/src/Exceptionless.Web/ApmExtensions.cs index c9d8f1bfbf..86522f6ea1 100644 --- a/src/Exceptionless.Web/ApmExtensions.cs +++ b/src/Exceptionless.Web/ApmExtensions.cs @@ -94,6 +94,7 @@ public static IHostBuilder AddApm(this IHostBuilder builder, ApmConfig config) b.AddHttpClientInstrumentation(); b.AddAspNetCoreInstrumentation(); b.AddMeter("Exceptionless", "Foundatio"); + b.AddMeter("System.Runtime"); b.AddRuntimeInstrumentation(); b.AddProcessInstrumentation(); diff --git a/src/Exceptionless.Web/ClientApp/vite.config.ts b/src/Exceptionless.Web/ClientApp/vite.config.ts index b52b08a762..83361087b4 100644 --- a/src/Exceptionless.Web/ClientApp/vite.config.ts +++ b/src/Exceptionless.Web/ClientApp/vite.config.ts @@ -17,7 +17,8 @@ export default defineConfig({ ], server: { hmr: aspNetConfig.hmr, - port: 5173, + host: true, + port: parseInt(process.env.PORT ?? '5173'), proxy: { '/_framework': { changeOrigin: true, @@ -77,7 +78,7 @@ function getAspNetConfig() { // get current aspnetcore port / url const aspnetHttpsPort = process.env.ASPNETCORE_HTTPS_PORT; - const aspnetUrls = process.env.ASPNETCORE_URLS; + const aspnetUrls = process.env.ASPNETCORE_URLS ?? process.env.services__Api__0;; const serverPort = 5173; const hmrRemoteHost = codespaceName ? `${codespaceName}-${serverPort}.${codespaceDomain}` : 'localhost'; diff --git a/src/Exceptionless.Web/Exceptionless.Web.csproj b/src/Exceptionless.Web/Exceptionless.Web.csproj index 4928a82cf6..b7198394ac 100644 --- a/src/Exceptionless.Web/Exceptionless.Web.csproj +++ b/src/Exceptionless.Web/Exceptionless.Web.csproj @@ -1,12 +1,4 @@ - - ClientApp\ - ClientApp.angular\ - $(DefaultItemExcludes);$(SpaRoot)node_modules\**;$(AngularSpaRoot)node_modules\**; - http://localhost:5173/next - npm run dev - false - @@ -18,7 +10,6 @@ - @@ -52,50 +43,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - wwwroot\next\%(RecursiveDir)%(FileName)%(Extension) - Always - true - - - - - - wwwroot\%(RecursiveDir)%(FileName)%(Extension) - Always - true - - - diff --git a/tests/Exceptionless.Tests/AppWebHostFactory.cs b/tests/Exceptionless.Tests/AppWebHostFactory.cs index 78ce6a99e8..c8a73de1ee 100644 --- a/tests/Exceptionless.Tests/AppWebHostFactory.cs +++ b/tests/Exceptionless.Tests/AppWebHostFactory.cs @@ -1,12 +1,42 @@ +using Aspire.Hosting; +using Aspire.Hosting.Testing; using Exceptionless.Insulation.Configuration; using Exceptionless.Web; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; +using Xunit; namespace Exceptionless.Tests; -public class AppWebHostFactory : WebApplicationFactory +public class AppWebHostFactory : WebApplicationFactory, IAsyncLifetime { + private DistributedApplication? _app; + + public DistributedApplication App => _app ?? throw new InvalidOperationException("The application is not initialized"); + + public string? ElasticsearchConnectionString { get; private set; } + public string? RedisConnectionString { get; private set; } + public string? MailConnectionString { get; private set; } + + public async Task InitializeAsync() + { + var options = new DistributedApplicationOptions { AssemblyName = typeof(ElasticsearchResource).Assembly.FullName, DisableDashboard = true }; + var builder = DistributedApplication.CreateBuilder(options); + + var elastic = builder.AddElasticsearch("Elasticsearch") + .WithContainerName("Exceptionless-Elasticsearch-Test"); + + var cache = builder.AddRedis("Redis") + .WithContainerName("Exceptionless-Redis-Test"); + + _app = builder.Build(); + + await _app.StartAsync(); + + ElasticsearchConnectionString = await _app.GetConnectionStringAsync("Elasticsearch"); + RedisConnectionString = await _app.GetConnectionStringAsync("Redis"); + } + protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseSolutionRelativeContentRoot("src/Exceptionless.Web"); @@ -21,4 +51,12 @@ protected override IHostBuilder CreateHostBuilder() return Program.CreateHostBuilder(config, Environments.Development); } + + async Task IAsyncLifetime.DisposeAsync() + { + if (_app is not null) + { + await _app.DisposeAsync(); + } + } } diff --git a/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs b/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs index 5da3fd8ce8..3a19ccada2 100644 --- a/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs @@ -1046,7 +1046,7 @@ await SendRequestAsync(r => r public async Task SpaFallbackWorks() { var response = await SendRequestAsync(r => r - .BaseUri(_server.BaseAddress) + .BaseUri(_server!.BaseAddress) .AppendPath("blah") .StatusCodeShouldBeOk() ); @@ -1054,13 +1054,13 @@ public async Task SpaFallbackWorks() Assert.Contains("exceptionless", content); await SendRequestAsync(r => r - .BaseUri(_server.BaseAddress) + .BaseUri(_server!.BaseAddress) .AppendPaths("api", "blah") .StatusCodeShouldBeNotFound() ); await SendRequestAsync(r => r - .BaseUri(_server.BaseAddress) + .BaseUri(_server!.BaseAddress) .AppendPaths("docs", "blah") .StatusCodeShouldBeNotFound() ); diff --git a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj index 0e94fc01f4..6e73839f6c 100644 --- a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj +++ b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj @@ -10,6 +10,7 @@ + @@ -21,6 +22,7 @@ + diff --git a/tests/Exceptionless.Tests/IntegrationTestsBase.cs b/tests/Exceptionless.Tests/IntegrationTestsBase.cs index 1705fa284c..12a2076027 100644 --- a/tests/Exceptionless.Tests/IntegrationTestsBase.cs +++ b/tests/Exceptionless.Tests/IntegrationTestsBase.cs @@ -35,12 +35,13 @@ public abstract class IntegrationTestsBase : TestWithLoggingBase, Xunit.IAsyncLi { private static bool _indexesHaveBeenConfigured = false; private static readonly SemaphoreSlim _semaphoreSlim = new(1, 1); - private readonly ExceptionlessElasticConfiguration _configuration; - protected readonly TestServer _server; - private readonly ProxyTimeProvider _timeProvider; + private ExceptionlessElasticConfiguration? _configuration; + private ProxyTimeProvider? _timeProvider; protected readonly IList _disposables = new List(); + protected readonly AppWebHostFactory _hostFixture; + protected TestServer? _server; - public IntegrationTestsBase(ITestOutputHelper output, AppWebHostFactory factory) : base(output) + public IntegrationTestsBase(ITestOutputHelper output, AppWebHostFactory hostFixture) : base(output) { Log.DefaultMinimumLevel = LogLevel.Information; Log.SetLogLevel(LogLevel.Warning); @@ -48,12 +49,26 @@ public IntegrationTestsBase(ITestOutputHelper output, AppWebHostFactory factory) Log.SetLogLevel(LogLevel.Warning); Log.SetLogLevel("StartupActions", LogLevel.Warning); Log.SetLogLevel(LogLevel.Warning); + _hostFixture = hostFixture; + } + + public virtual async Task InitializeAsync() + { + await _hostFixture.StartAsync(); - var configuredFactory = factory.Factories.Count > 0 ? factory.Factories[0] : null; + var configuredFactory = _hostFixture.Factories.Count > 0 ? _hostFixture.Factories[0] : null; if (configuredFactory is null) { - configuredFactory = factory.WithWebHostBuilder(builder => + configuredFactory = _hostFixture.WithWebHostBuilder(builder => { + builder.ConfigureAppConfiguration(c => + { + c.AddInMemoryCollection(new Dictionary + { + { "ConnectionStrings:Elasticsearch", _hostFixture.ElasticsearchConnectionString }, + { "ConnectionStrings:Redis", _hostFixture.RedisConnectionString } + }); + }); builder.ConfigureTestServices(RegisterServices); // happens after normal container configure and overrides services }); } @@ -73,22 +88,21 @@ public IntegrationTestsBase(ITestOutputHelper output, AppWebHostFactory factory) throw new InvalidOperationException("TimeProvider must be of type ProxyTimeProvider"); _disposables.Add(new DisposableAction(() => _timeProvider.Restore())); - } - public virtual async Task InitializeAsync() - { Log.SetLogLevel("Microsoft.AspNetCore.Hosting.Internal.WebHost", LogLevel.Warning); Log.SetLogLevel("Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService", LogLevel.None); + await _server.WaitForReadyAsync(); + Log.SetLogLevel("Microsoft.AspNetCore.Hosting.Internal.WebHost", LogLevel.Information); Log.SetLogLevel("Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService", LogLevel.Information); await ResetDataAsync(); } - protected ProxyTimeProvider TimeProvider => _timeProvider; + protected ProxyTimeProvider TimeProvider => _timeProvider!; - private IServiceProvider ServiceProvider { get; } + private IServiceProvider ServiceProvider { get; set; } = new ServiceCollection().BuildServiceProvider(); protected TService GetService() where TService : notnull { @@ -114,14 +128,14 @@ protected virtual void RegisterServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - services.ReplaceSingleton(s => _server.CreateHandler()); + services.ReplaceSingleton(s => _server!.CreateHandler()); } public async Task<(List Stacks, List Events)> CreateDataAsync(Action dataBuilderFunc) { var eventBuilders = new List(); - var dataBuilder = new DataBuilder(eventBuilders, ServiceProvider, _timeProvider); + var dataBuilder = new DataBuilder(eventBuilders, ServiceProvider, _timeProvider!); dataBuilderFunc(dataBuilder); var eventRepository = GetService(); @@ -154,13 +168,13 @@ protected virtual async Task ResetDataAsync() await RefreshDataAsync(); if (!_indexesHaveBeenConfigured) { - await _configuration.DeleteIndexesAsync(); + await _configuration!.DeleteIndexesAsync(); await _configuration.ConfigureIndexesAsync(); _indexesHaveBeenConfigured = true; } else { - string indexes = String.Join(',', _configuration.Indexes.Select(i => i.Name)); + string indexes = String.Join(',', _configuration!.Indexes.Select(i => i.Name)); await _configuration.Client.DeleteByQueryAsync(new DeleteByQueryRequest(indexes) { Query = new MatchAllQuery(), @@ -200,7 +214,7 @@ protected async Task RefreshDataAsync(Indices? indices = null) protected HttpClient CreateHttpClient() { - var client = _server.CreateClient(); + var client = _server!.CreateClient(); client.BaseAddress = new Uri(_server.BaseAddress + "api/v2/", UriKind.Absolute); return client; } From bcaaac7286f0bc409c06336988e96485450cc78c Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Sat, 26 Oct 2024 22:06:21 -0500 Subject: [PATCH 02/24] Progress --- src/Exceptionless.AppHost/Program.cs | 2 +- .../Properties/launchSettings.json | 63 +++++++++++-------- ...CustomEnvironmentVariablesConfiguration.cs | 56 +++++++++++++++++ src/Exceptionless.Job/Program.cs | 4 +- src/Exceptionless.Web/Program.cs | 6 +- .../Properties/launchSettings.json | 14 +---- .../IntegrationTestsBase.cs | 2 +- tests/Exceptionless.Tests/TestWithServices.cs | 3 +- 8 files changed, 104 insertions(+), 46 deletions(-) create mode 100644 src/Exceptionless.Core/Configuration/CustomEnvironmentVariablesConfiguration.cs diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index 9b65c0b8f1..82e7afdaf5 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -25,7 +25,7 @@ .WaitFor(cache) .WithHttpHealthCheck("/health"); -var api = builder.AddProject("Api", "Exceptionless API") +var api = builder.AddProject("Api", "Exceptionless") .WithReference(cache) .WithReference(elastic) .WaitFor(elastic) diff --git a/src/Exceptionless.AppHost/Properties/launchSettings.json b/src/Exceptionless.AppHost/Properties/launchSettings.json index a5fb2e64f3..a657132e91 100644 --- a/src/Exceptionless.AppHost/Properties/launchSettings.json +++ b/src/Exceptionless.AppHost/Properties/launchSettings.json @@ -1,29 +1,42 @@ { "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:17056;http://localhost:15161", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "DOTNET_ENVIRONMENT": "Development", - "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21210", - "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22299" - } - }, - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:15161", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "DOTNET_ENVIRONMENT": "Development", - "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19113", - "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20111" - } + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17056;http://localhost:15161", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21210", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22299" + } + }, + "https-all": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17056;http://localhost:15161", + "environmentVariables": { + "EX_ALL": "true", + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21210", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22299" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15161", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19113", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20111" + } + } } - } } diff --git a/src/Exceptionless.Core/Configuration/CustomEnvironmentVariablesConfiguration.cs b/src/Exceptionless.Core/Configuration/CustomEnvironmentVariablesConfiguration.cs new file mode 100644 index 0000000000..f626ff6788 --- /dev/null +++ b/src/Exceptionless.Core/Configuration/CustomEnvironmentVariablesConfiguration.cs @@ -0,0 +1,56 @@ +using System.Collections; +using Microsoft.Extensions.Configuration; + +namespace Exceptionless.Core.Configuration; + +public static class CustomEnvironmentVariablesExtensions +{ + public static IConfigurationBuilder AddCustomEnvironmentVariables(this IConfigurationBuilder configurationBuilder) + { + configurationBuilder.Add(new CustomEnvironmentVariablesConfigurationSource()); + return configurationBuilder; + } +} + +public class CustomEnvironmentVariablesConfigurationSource : IConfigurationSource +{ + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return new CustomEnvironmentVariablesConfigurationProvider(); + } +} + +public class CustomEnvironmentVariablesConfigurationProvider : ConfigurationProvider +{ + public override void Load() => Load(Environment.GetEnvironmentVariables()); + + internal void Load(IDictionary envVariables) + { + var data = new Dictionary(StringComparer.OrdinalIgnoreCase); + + IDictionaryEnumerator e = envVariables.GetEnumerator(); + try + { + while (e.MoveNext()) + { + string key = (string)e.Entry.Key; + string? value = (string?)e.Entry.Value; + + var normalizedKey = Normalize(key); + // remove EX_ prefix + if (normalizedKey.StartsWith("EX_")) + data[normalizedKey.Substring(3)] = value; + else + data[normalizedKey] = value; + } + } + finally + { + (e as IDisposable)?.Dispose(); + } + + Data = data; + } + + private static string Normalize(string key) => key.Replace("__", ConfigurationPath.KeyDelimiter); +} diff --git a/src/Exceptionless.Job/Program.cs b/src/Exceptionless.Job/Program.cs index 1a147545d1..8b3fca1c3b 100644 --- a/src/Exceptionless.Job/Program.cs +++ b/src/Exceptionless.Job/Program.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using Exceptionless.Core; +using Exceptionless.Core.Configuration; using Exceptionless.Core.Extensions; using Exceptionless.Core.Jobs; using Exceptionless.Core.Jobs.Elastic; @@ -49,8 +50,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) .SetBasePath(Directory.GetCurrentDirectory()) .AddYamlFile("appsettings.yml", optional: true, reloadOnChange: true) .AddYamlFile($"appsettings.{environment}.yml", optional: true, reloadOnChange: true) - .AddEnvironmentVariables("EX_") - .AddEnvironmentVariables("ASPNETCORE_") + .AddCustomEnvironmentVariables() .AddCommandLine(args) .Build(); diff --git a/src/Exceptionless.Web/Program.cs b/src/Exceptionless.Web/Program.cs index 38e5e2b483..2433bceeda 100644 --- a/src/Exceptionless.Web/Program.cs +++ b/src/Exceptionless.Web/Program.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using Exceptionless.Core; +using Exceptionless.Core.Configuration; using Exceptionless.Core.Extensions; using Exceptionless.Insulation.Configuration; using OpenTelemetry; @@ -43,8 +44,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) .SetBasePath(Directory.GetCurrentDirectory()) .AddYamlFile("appsettings.yml", optional: true, reloadOnChange: true) .AddYamlFile($"appsettings.{environment}.yml", optional: true, reloadOnChange: true) - .AddEnvironmentVariables("EX_") - .AddEnvironmentVariables("ASPNETCORE_") + .AddCustomEnvironmentVariables() .AddCommandLine(args) .Build(); @@ -64,7 +64,7 @@ public static IHostBuilder CreateHostBuilder(IConfigurationRoot config, string e var apmConfig = new ApmConfig(config, "web", options.InformationalVersion, options.CacheOptions.Provider == "redis"); var configDictionary = config.ToDictionary("Serilog"); - Log.Information("Bootstrapping Exceptionless Web in {AppMode} mode ({InformationalVersion}) on {MachineName} with settings {@Settings}", environment, options.InformationalVersion, Environment.MachineName, configDictionary); + Log.Information("Bootstrapping Exceptionless Web in {AppMode} mode ({InformationalVersion}) on {MachineName} with options {@Options}", environment, options.InformationalVersion, Environment.MachineName, options); SetClientEnvironmentVariablesInDevelopmentMode(options); diff --git a/src/Exceptionless.Web/Properties/launchSettings.json b/src/Exceptionless.Web/Properties/launchSettings.json index c660ee9846..56edae3669 100644 --- a/src/Exceptionless.Web/Properties/launchSettings.json +++ b/src/Exceptionless.Web/Properties/launchSettings.json @@ -1,25 +1,13 @@ { "profiles": { "Exceptionless": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "http://localhost:5200/next", - "environmentVariables": { - "EX_AppMode": "Development", - "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy", - "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:8200" - }, - "dotnetRunMessages": true, - "applicationUrl": "http://localhost:5200" - }, - "Exceptionless API": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "EX_AppMode": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "http://localhost:5200" + "applicationUrl": "http://localhost:5200/next" } } } diff --git a/tests/Exceptionless.Tests/IntegrationTestsBase.cs b/tests/Exceptionless.Tests/IntegrationTestsBase.cs index 12a2076027..e099145e3d 100644 --- a/tests/Exceptionless.Tests/IntegrationTestsBase.cs +++ b/tests/Exceptionless.Tests/IntegrationTestsBase.cs @@ -54,7 +54,7 @@ public IntegrationTestsBase(ITestOutputHelper output, AppWebHostFactory hostFixt public virtual async Task InitializeAsync() { - await _hostFixture.StartAsync(); + //await _hostFixture.StartAsync(); var configuredFactory = _hostFixture.Factories.Count > 0 ? _hostFixture.Factories[0] : null; if (configuredFactory is null) diff --git a/tests/Exceptionless.Tests/TestWithServices.cs b/tests/Exceptionless.Tests/TestWithServices.cs index cc6e124af6..8d6c7ca715 100644 --- a/tests/Exceptionless.Tests/TestWithServices.cs +++ b/tests/Exceptionless.Tests/TestWithServices.cs @@ -1,5 +1,6 @@ using Exceptionless.Core; using Exceptionless.Core.Authentication; +using Exceptionless.Core.Configuration; using Exceptionless.Core.Extensions; using Exceptionless.Core.Mail; using Exceptionless.Helpers; @@ -83,7 +84,7 @@ private IServiceProvider CreateContainer() var config = new ConfigurationBuilder() .SetBasePath(AppContext.BaseDirectory) .AddYamlFile("appsettings.yml", optional: false, reloadOnChange: false) - .AddEnvironmentVariables() + .AddCustomEnvironmentVariables() .Build(); services.AddSingleton(config); From ac9b8df5c663f7310fc0cea525d4315df5dff62b Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Thu, 31 Oct 2024 14:53:29 -0500 Subject: [PATCH 03/24] Some aspire progress --- src/Exceptionless.AppHost/Program.cs | 21 +++++++++++------ .../Configuration/CacheOptions.cs | 23 +++++++++++++++---- .../Configuration/ElasticsearchOptions.cs | 4 +--- .../Configuration/MessageBusOptions.cs | 21 ++++++++++++++--- .../Configuration/QueueOptions.cs | 21 ++++++++++++++--- 5 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index 82e7afdaf5..1979d6887d 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -1,18 +1,20 @@ var builder = DistributedApplication.CreateBuilder(args); var elastic = builder.AddElasticsearch("Elasticsearch", port: 9200) + .WithImageTag("8.15.2") .WithLifetime(ContainerLifetime.Persistent) .WithContainerName("Exceptionless-Elasticsearch") .WithDataVolume("exceptionless.data.v1") .WithKibana(b => b.WithLifetime(ContainerLifetime.Persistent).WithContainerName("Exceptionless-Kibana")); var cache = builder.AddRedis("Redis", port: 6379) + .WithImageTag("7.4") .WithLifetime(ContainerLifetime.Persistent) .WithContainerName("Exceptionless-Redis") .WithClearCommand() .WithRedisInsight(b => b.WithLifetime(ContainerLifetime.Persistent).WithContainerName("Exceptionless-RedisInsight")); -builder.AddContainer("Mail", "mailhog/mailhog") +var mail = builder.AddContainer("Mail", "mailhog/mailhog") .WithLifetime(ContainerLifetime.Persistent) .WithContainerName("Exceptionless-Mail") .WithEndpoint(8025, 8025, "http") @@ -21,23 +23,28 @@ var job = builder.AddProject("Jobs", "AllJobs") .WithReference(cache) .WithReference(elastic) + .WithEnvironment("ConnectionStrings:Email", "smtp://localhost:1025") .WaitFor(elastic) .WaitFor(cache) + .WaitFor(mail) .WithHttpHealthCheck("/health"); var api = builder.AddProject("Api", "Exceptionless") .WithReference(cache) .WithReference(elastic) + .WithEnvironment("ConnectionStrings:Email", "smtp://localhost:1025") + .WithEnvironment("RunJobsInProcess", "false") .WaitFor(elastic) .WaitFor(cache) + .WaitFor(mail) .WithHttpHealthCheck("/health"); -builder.AddNpmApp("Web", "../../src/Exceptionless.Web/ClientApp", "dev") - .WithReference(api) - .WithEndpoint(scheme: "http", env: "PORT"); +//builder.AddNpmApp("Web", "../../src/Exceptionless.Web/ClientApp", "dev") +// .WithReference(api) +// .WithEndpoint(scheme: "http", env: "PORT"); -builder.AddNpmApp("AngularWeb", "../../src/Exceptionless.Web/ClientApp.angular", "serve") - .WithReference(api) - .WithEndpoint(scheme: "http", env: "PORT"); +//builder.AddNpmApp("AngularWeb", "../../src/Exceptionless.Web/ClientApp.angular", "serve") +// .WithReference(api) +// .WithEndpoint(scheme: "http", env: "PORT"); builder.Build().Run(); diff --git a/src/Exceptionless.Core/Configuration/CacheOptions.cs b/src/Exceptionless.Core/Configuration/CacheOptions.cs index a0d788c482..4bde275782 100644 --- a/src/Exceptionless.Core/Configuration/CacheOptions.cs +++ b/src/Exceptionless.Core/Configuration/CacheOptions.cs @@ -20,14 +20,29 @@ public static CacheOptions ReadFromConfiguration(IConfiguration config, AppOptio options.ScopePrefix = !String.IsNullOrEmpty(options.Scope) ? $"{options.Scope}-" : String.Empty; string? cs = config.GetConnectionString("Cache"); - options.Data = cs.ParseConnectionString(); - options.Provider = options.Data.GetString(nameof(options.Provider)); + if (cs != null) + { + options.Data = cs.ParseConnectionString(); + options.Provider = options.Data.GetString(nameof(options.Provider)); + } + else + { + var redisConnectionString = config.GetConnectionString("Redis"); + if (!String.IsNullOrEmpty(redisConnectionString)) + { + options.Provider = "redis"; + } + } string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null; if (!String.IsNullOrEmpty(providerConnectionString)) - options.Data.AddRange(providerConnectionString.ParseConnectionString()); + { + var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); + options.Data ??= []; + options.Data.AddRange(providerOptions); + } - options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); + options.ConnectionString = options.Data.BuildConnectionString([ nameof(options.Provider) ]); return options; } diff --git a/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs b/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs index 2340f21ae7..bce6d41cd7 100644 --- a/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs +++ b/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs @@ -33,8 +33,6 @@ public static ElasticsearchOptions ReadFromConfiguration(IConfiguration config, options.ReindexCutOffDate = config.GetValue(nameof(options.ReindexCutOffDate), DateTime.MinValue); string? connectionString = config.GetConnectionString("Elasticsearch"); - if (connectionString !=null && (connectionString.StartsWith("http://") || connectionString.StartsWith("https://"))) - connectionString = "server=" + connectionString; ParseConnectionString(connectionString, options, appOptions.AppMode); string? connectionStringToMigrate = config.GetConnectionString("ElasticsearchToMigrate"); @@ -53,7 +51,7 @@ public static ElasticsearchOptions ReadFromConfiguration(IConfiguration config, private static void ParseConnectionString(string? connectionString, ElasticsearchOptions options, AppMode appMode) { - var pairs = connectionString.ParseConnectionString(); + var pairs = connectionString.ParseConnectionString(defaultKey: "server"); options.ServerUrl = pairs.GetString("server", "http://localhost:9200"); int shards = pairs.GetValueOrDefault("shards", 1); diff --git a/src/Exceptionless.Core/Configuration/MessageBusOptions.cs b/src/Exceptionless.Core/Configuration/MessageBusOptions.cs index b69407028f..d5f882e49c 100644 --- a/src/Exceptionless.Core/Configuration/MessageBusOptions.cs +++ b/src/Exceptionless.Core/Configuration/MessageBusOptions.cs @@ -22,12 +22,27 @@ public static MessageBusOptions ReadFromConfiguration(IConfiguration config, App options.Topic = config.GetValue(nameof(options.Topic), $"{options.ScopePrefix}messages")!; string? cs = config.GetConnectionString("MessageBus"); - options.Data = cs.ParseConnectionString(); - options.Provider = options.Data.GetString(nameof(options.Provider)); + if (cs != null) + { + options.Data = cs.ParseConnectionString(); + options.Provider = options.Data.GetString(nameof(options.Provider)); + } + else + { + var redisConnectionString = config.GetConnectionString("Redis"); + if (!String.IsNullOrEmpty(redisConnectionString)) + { + options.Provider = "redis"; + } + } string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null; if (!String.IsNullOrEmpty(providerConnectionString)) - options.Data.AddRange(providerConnectionString.ParseConnectionString()); + { + var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); + options.Data ??= []; + options.Data.AddRange(providerOptions); + } options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); diff --git a/src/Exceptionless.Core/Configuration/QueueOptions.cs b/src/Exceptionless.Core/Configuration/QueueOptions.cs index 9ab058ca66..48195a3b75 100644 --- a/src/Exceptionless.Core/Configuration/QueueOptions.cs +++ b/src/Exceptionless.Core/Configuration/QueueOptions.cs @@ -20,12 +20,27 @@ public static QueueOptions ReadFromConfiguration(IConfiguration config, AppOptio options.ScopePrefix = !String.IsNullOrEmpty(options.Scope) ? $"{options.Scope}-" : String.Empty; string? cs = config.GetConnectionString("Queue"); - options.Data = cs.ParseConnectionString(); - options.Provider = options.Data.GetString(nameof(options.Provider)); + if (cs != null) + { + options.Data = cs.ParseConnectionString(); + options.Provider = options.Data.GetString(nameof(options.Provider)); + } + else + { + var redisConnectionString = config.GetConnectionString("Redis"); + if (!String.IsNullOrEmpty(redisConnectionString)) + { + options.Provider = "redis"; + } + } string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null; if (!String.IsNullOrEmpty(providerConnectionString)) - options.Data.AddRange(providerConnectionString.ParseConnectionString()); + { + var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); + options.Data ??= []; + options.Data.AddRange(providerOptions); + } options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); From 1247d57a42a29f001478edbb4ab6c3d2601ba5fe Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Thu, 31 Oct 2024 15:34:20 -0500 Subject: [PATCH 04/24] Fix bad merge --- .../Exceptionless.Web.csproj | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/src/Exceptionless.Web/Exceptionless.Web.csproj b/src/Exceptionless.Web/Exceptionless.Web.csproj index 83f89595ff..2f1f856e44 100644 --- a/src/Exceptionless.Web/Exceptionless.Web.csproj +++ b/src/Exceptionless.Web/Exceptionless.Web.csproj @@ -54,39 +54,4 @@ - - - - - - - - - - - - - - - - - - - - - - wwwroot\next\%(RecursiveDir)%(FileName)%(Extension) - Always - true - - - - - - wwwroot\%(RecursiveDir)%(FileName)%(Extension) - Always - true - - - From 4892f5f5587a5cdfc7638ebfa759dc35be06e8cf Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 12 Nov 2024 20:11:49 -0600 Subject: [PATCH 05/24] Update to Aspire 9 --- .../Exceptionless.AppHost.csproj | 8 ++++---- src/Exceptionless.AppHost/Program.cs | 14 +++++++------- ...WebHostFactory.cs => AspireWebHostFactory.cs} | 16 ++++++++++------ .../Controllers/AuthControllerTests.cs | 2 +- .../Controllers/EventControllerTests.cs | 2 +- .../Controllers/ProjectControllerTests.cs | 2 +- .../Controllers/StackControllerTests.cs | 2 +- .../Controllers/StatusControllerTests.cs | 2 +- .../Controllers/TokenControllerTests.cs | 2 +- .../Controllers/WebHookControllerTests.cs | 2 +- .../Exceptionless.Tests.csproj | 2 +- .../Exceptionless.Tests/IntegrationTestsBase.cs | 6 +++--- .../Jobs/CleanupDataJobTests.cs | 2 +- .../Jobs/CloseInactiveSessionsJobTests.cs | 2 +- .../Jobs/EventPostJobTests.cs | 2 +- .../FixDuplicateStacksMigrationTests.cs | 2 +- .../Migrations/UpdateEventUsageMigrationTests.cs | 2 +- .../Pipeline/EventPipelineTests.cs | 2 +- .../Repositories/EventRepositoryTests.cs | 2 +- .../Repositories/OrganizationRepositoryTests.cs | 2 +- .../Repositories/ProjectRepositoryTests.cs | 2 +- .../Repositories/StackRepositoryTests.cs | 2 +- .../Repositories/TokenRepositoryTests.cs | 2 +- .../Repositories/WebHookRepositoryTests.cs | 2 +- .../Search/EventIndexTests.cs | 2 +- .../Search/EventStackFilterQueryTests.cs | 2 +- .../Search/EventStackFilterTests.cs | 2 +- .../Search/MoreEventIndexTests.cs | 2 +- .../Search/StackIndexTests.cs | 2 +- .../Services/StackServiceTests.cs | 2 +- .../Services/UsageServiceTests.cs | 2 +- .../Stats/AggregationTests.cs | 2 +- 32 files changed, 52 insertions(+), 48 deletions(-) rename tests/Exceptionless.Tests/{AppWebHostFactory.cs => AspireWebHostFactory.cs} (79%) diff --git a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj index aebba20ded..10a13c3a15 100644 --- a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj +++ b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -12,9 +12,9 @@ - - - + + + diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index 1979d6887d..9665fab105 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -20,7 +20,7 @@ .WithEndpoint(8025, 8025, "http") .WithEndpoint(1025, 1025); -var job = builder.AddProject("Jobs", "AllJobs") +builder.AddProject("Jobs", "AllJobs") .WithReference(cache) .WithReference(elastic) .WithEnvironment("ConnectionStrings:Email", "smtp://localhost:1025") @@ -39,12 +39,12 @@ .WaitFor(mail) .WithHttpHealthCheck("/health"); -//builder.AddNpmApp("Web", "../../src/Exceptionless.Web/ClientApp", "dev") -// .WithReference(api) -// .WithEndpoint(scheme: "http", env: "PORT"); +builder.AddNpmApp("Web", "../../src/Exceptionless.Web/ClientApp", "dev") + .WithReference(api) + .WithEndpoint(scheme: "http", env: "PORT"); -//builder.AddNpmApp("AngularWeb", "../../src/Exceptionless.Web/ClientApp.angular", "serve") -// .WithReference(api) -// .WithEndpoint(scheme: "http", env: "PORT"); +builder.AddNpmApp("AngularWeb", "../../src/Exceptionless.Web/ClientApp.angular", "serve") + .WithReference(api) + .WithEndpoint(scheme: "http", env: "PORT"); builder.Build().Run(); diff --git a/tests/Exceptionless.Tests/AppWebHostFactory.cs b/tests/Exceptionless.Tests/AspireWebHostFactory.cs similarity index 79% rename from tests/Exceptionless.Tests/AppWebHostFactory.cs rename to tests/Exceptionless.Tests/AspireWebHostFactory.cs index c8a73de1ee..30aec81cf2 100644 --- a/tests/Exceptionless.Tests/AppWebHostFactory.cs +++ b/tests/Exceptionless.Tests/AspireWebHostFactory.cs @@ -1,4 +1,5 @@ using Aspire.Hosting; +using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Testing; using Exceptionless.Insulation.Configuration; using Exceptionless.Web; @@ -8,7 +9,7 @@ namespace Exceptionless.Tests; -public class AppWebHostFactory : WebApplicationFactory, IAsyncLifetime +public class AspireWebHostFactory : WebApplicationFactory, IAsyncLifetime { private DistributedApplication? _app; @@ -16,18 +17,21 @@ public class AppWebHostFactory : WebApplicationFactory, IAsyncLifetime public string? ElasticsearchConnectionString { get; private set; } public string? RedisConnectionString { get; private set; } - public string? MailConnectionString { get; private set; } public async Task InitializeAsync() { var options = new DistributedApplicationOptions { AssemblyName = typeof(ElasticsearchResource).Assembly.FullName, DisableDashboard = true }; var builder = DistributedApplication.CreateBuilder(options); - var elastic = builder.AddElasticsearch("Elasticsearch") - .WithContainerName("Exceptionless-Elasticsearch-Test"); + builder.AddElasticsearch("Elasticsearch") + .WithContainerName("Exceptionless-Elasticsearch-Test") + .WithImageTag("8.15.2") + .WithLifetime(ContainerLifetime.Persistent); - var cache = builder.AddRedis("Redis") - .WithContainerName("Exceptionless-Redis-Test"); + builder.AddRedis("Redis") + .WithContainerName("Exceptionless-Redis-Test") + .WithImageTag("7.4") + .WithLifetime(ContainerLifetime.Persistent);; _app = builder.Build(); diff --git a/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs b/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs index 158c804757..54b5a0414c 100644 --- a/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs @@ -27,7 +27,7 @@ public class AuthControllerTests : IntegrationTestsBase private readonly IOrganizationRepository _organizationRepository; private readonly ITokenRepository _tokenRepository; - public AuthControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public AuthControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _authOptions = GetService(); _authOptions.EnableAccountCreation = true; diff --git a/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs b/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs index 3a19ccada2..015bf80187 100644 --- a/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs @@ -43,7 +43,7 @@ public class EventControllerTests : IntegrationTestsBase private readonly IQueue _eventUserDescriptionQueue; private readonly UserData _userData; - public EventControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public EventControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _organizationRepository = GetService(); _stackData = GetService(); diff --git a/tests/Exceptionless.Tests/Controllers/ProjectControllerTests.cs b/tests/Exceptionless.Tests/Controllers/ProjectControllerTests.cs index c3010d96a6..e3c614acdf 100644 --- a/tests/Exceptionless.Tests/Controllers/ProjectControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/ProjectControllerTests.cs @@ -13,7 +13,7 @@ namespace Exceptionless.Tests.Controllers; public sealed class ProjectControllerTests : IntegrationTestsBase { - public ProjectControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public ProjectControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { } diff --git a/tests/Exceptionless.Tests/Controllers/StackControllerTests.cs b/tests/Exceptionless.Tests/Controllers/StackControllerTests.cs index 1e3d431866..37d2b3e412 100644 --- a/tests/Exceptionless.Tests/Controllers/StackControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/StackControllerTests.cs @@ -21,7 +21,7 @@ public class StackControllerTests : IntegrationTestsBase private readonly IQueue _eventQueue; private readonly IQueue _workItemQueue; - public StackControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public StackControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _stackRepository = GetService(); _eventRepository = GetService(); diff --git a/tests/Exceptionless.Tests/Controllers/StatusControllerTests.cs b/tests/Exceptionless.Tests/Controllers/StatusControllerTests.cs index 5360717ee5..da0a288839 100644 --- a/tests/Exceptionless.Tests/Controllers/StatusControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/StatusControllerTests.cs @@ -10,7 +10,7 @@ namespace Exceptionless.Tests.Controllers; public class StatusControllerTests : IntegrationTestsBase { - public StatusControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public StatusControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { } diff --git a/tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs b/tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs index 2c5b46ea6a..b915f3e409 100644 --- a/tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs @@ -14,7 +14,7 @@ namespace Exceptionless.Tests.Controllers; public sealed class TokenControllerTests : IntegrationTestsBase { - public TokenControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { } + public TokenControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { } protected override async Task ResetDataAsync() { diff --git a/tests/Exceptionless.Tests/Controllers/WebHookControllerTests.cs b/tests/Exceptionless.Tests/Controllers/WebHookControllerTests.cs index 62e794d271..bcea584c1d 100644 --- a/tests/Exceptionless.Tests/Controllers/WebHookControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/WebHookControllerTests.cs @@ -10,7 +10,7 @@ namespace Exceptionless.Tests.Controllers; public sealed class WebHookControllerTests : IntegrationTestsBase { - public WebHookControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { } + public WebHookControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { } protected override async Task ResetDataAsync() { diff --git a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj index 6e73839f6c..3b1be478f0 100644 --- a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj +++ b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Exceptionless.Tests/IntegrationTestsBase.cs b/tests/Exceptionless.Tests/IntegrationTestsBase.cs index e099145e3d..af6a429cdb 100644 --- a/tests/Exceptionless.Tests/IntegrationTestsBase.cs +++ b/tests/Exceptionless.Tests/IntegrationTestsBase.cs @@ -31,17 +31,17 @@ namespace Exceptionless.Tests; -public abstract class IntegrationTestsBase : TestWithLoggingBase, Xunit.IAsyncLifetime, IClassFixture +public abstract class IntegrationTestsBase : TestWithLoggingBase, Xunit.IAsyncLifetime, IClassFixture { private static bool _indexesHaveBeenConfigured = false; private static readonly SemaphoreSlim _semaphoreSlim = new(1, 1); private ExceptionlessElasticConfiguration? _configuration; private ProxyTimeProvider? _timeProvider; protected readonly IList _disposables = new List(); - protected readonly AppWebHostFactory _hostFixture; + protected readonly AspireWebHostFactory _hostFixture; protected TestServer? _server; - public IntegrationTestsBase(ITestOutputHelper output, AppWebHostFactory hostFixture) : base(output) + public IntegrationTestsBase(ITestOutputHelper output, AspireWebHostFactory hostFixture) : base(output) { Log.DefaultMinimumLevel = LogLevel.Information; Log.SetLogLevel(LogLevel.Warning); diff --git a/tests/Exceptionless.Tests/Jobs/CleanupDataJobTests.cs b/tests/Exceptionless.Tests/Jobs/CleanupDataJobTests.cs index 44e262e5cd..1d649a045d 100644 --- a/tests/Exceptionless.Tests/Jobs/CleanupDataJobTests.cs +++ b/tests/Exceptionless.Tests/Jobs/CleanupDataJobTests.cs @@ -27,7 +27,7 @@ public class CleanupDataJobTests : IntegrationTestsBase private readonly BillingManager _billingManager; private readonly BillingPlans _plans; - public CleanupDataJobTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public CleanupDataJobTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _job = GetService(); _organizationData = GetService(); diff --git a/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs b/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs index 3fc9dd9b90..dcd60979a6 100644 --- a/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs +++ b/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs @@ -31,7 +31,7 @@ public class CloseInactiveSessionsJobTests : IntegrationTestsBase private readonly BillingManager _billingManager; private readonly BillingPlans _plans; - public CloseInactiveSessionsJobTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public CloseInactiveSessionsJobTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _job = GetService(); _cache = GetService(); diff --git a/tests/Exceptionless.Tests/Jobs/EventPostJobTests.cs b/tests/Exceptionless.Tests/Jobs/EventPostJobTests.cs index 6aae0f1c0a..8713a8bd60 100644 --- a/tests/Exceptionless.Tests/Jobs/EventPostJobTests.cs +++ b/tests/Exceptionless.Tests/Jobs/EventPostJobTests.cs @@ -39,7 +39,7 @@ public class EventPostJobTests : IntegrationTestsBase private readonly BillingPlans _plans; private readonly AppOptions _options; - public EventPostJobTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public EventPostJobTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _job = GetService(); _eventQueue = GetService>(); diff --git a/tests/Exceptionless.Tests/Migrations/FixDuplicateStacksMigrationTests.cs b/tests/Exceptionless.Tests/Migrations/FixDuplicateStacksMigrationTests.cs index 226264e83b..23aed60bf8 100644 --- a/tests/Exceptionless.Tests/Migrations/FixDuplicateStacksMigrationTests.cs +++ b/tests/Exceptionless.Tests/Migrations/FixDuplicateStacksMigrationTests.cs @@ -21,7 +21,7 @@ public class FixDuplicateStacksMigrationTests : IntegrationTestsBase private readonly EventData _eventData; private readonly IEventRepository _eventRepository; - public FixDuplicateStacksMigrationTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public FixDuplicateStacksMigrationTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _stackData = GetService(); _stackRepository = GetService(); diff --git a/tests/Exceptionless.Tests/Migrations/UpdateEventUsageMigrationTests.cs b/tests/Exceptionless.Tests/Migrations/UpdateEventUsageMigrationTests.cs index 70c0c99aa8..f19a33eef0 100644 --- a/tests/Exceptionless.Tests/Migrations/UpdateEventUsageMigrationTests.cs +++ b/tests/Exceptionless.Tests/Migrations/UpdateEventUsageMigrationTests.cs @@ -24,7 +24,7 @@ public class UpdateEventUsageMigrationTests : IntegrationTestsBase private readonly EventData _eventData; private readonly IEventRepository _eventRepository; - public UpdateEventUsageMigrationTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public UpdateEventUsageMigrationTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _organizationData = GetService(); _organizationRepository = GetService(); diff --git a/tests/Exceptionless.Tests/Pipeline/EventPipelineTests.cs b/tests/Exceptionless.Tests/Pipeline/EventPipelineTests.cs index b359cfa92e..a3fc14854a 100644 --- a/tests/Exceptionless.Tests/Pipeline/EventPipelineTests.cs +++ b/tests/Exceptionless.Tests/Pipeline/EventPipelineTests.cs @@ -40,7 +40,7 @@ public sealed class EventPipelineTests : IntegrationTestsBase private readonly BillingManager _billingManager; private readonly BillingPlans _plans; - public EventPipelineTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public EventPipelineTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _eventData = GetService(); _eventRepository = GetService(); diff --git a/tests/Exceptionless.Tests/Repositories/EventRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/EventRepositoryTests.cs index 79e2bd8066..842b9bf4db 100644 --- a/tests/Exceptionless.Tests/Repositories/EventRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/EventRepositoryTests.cs @@ -22,7 +22,7 @@ public sealed class EventRepositoryTests : IntegrationTestsBase private readonly StackData _stackData; private readonly IStackRepository _stackRepository; - public EventRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public EventRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _randomEventGenerator = GetService(); _eventData = GetService(); diff --git a/tests/Exceptionless.Tests/Repositories/OrganizationRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/OrganizationRepositoryTests.cs index 7b92db1318..5a91c156d5 100644 --- a/tests/Exceptionless.Tests/Repositories/OrganizationRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/OrganizationRepositoryTests.cs @@ -15,7 +15,7 @@ public sealed class OrganizationRepositoryTests : IntegrationTestsBase private readonly IOrganizationRepository _repository; private readonly BillingPlans _plans; - public OrganizationRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public OrganizationRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { Log.SetLogLevel(LogLevel.Trace); _cache = GetService() as InMemoryCacheClient ?? throw new InvalidOperationException(); diff --git a/tests/Exceptionless.Tests/Repositories/ProjectRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/ProjectRepositoryTests.cs index 6d08e2d171..f11ec8fdfb 100644 --- a/tests/Exceptionless.Tests/Repositories/ProjectRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/ProjectRepositoryTests.cs @@ -19,7 +19,7 @@ public sealed class ProjectRepositoryTests : IntegrationTestsBase private readonly ProjectData _projectData; private readonly IProjectRepository _repository; - public ProjectRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public ProjectRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _organizationData = GetService(); _projectData = GetService(); diff --git a/tests/Exceptionless.Tests/Repositories/StackRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/StackRepositoryTests.cs index 43aee476cf..f4c7961151 100644 --- a/tests/Exceptionless.Tests/Repositories/StackRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/StackRepositoryTests.cs @@ -19,7 +19,7 @@ public sealed class StackRepositoryTests : IntegrationTestsBase private readonly StackData _stackData; private readonly IStackRepository _repository; - public StackRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public StackRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _cache = GetService() as InMemoryCacheClient ?? throw new InvalidOperationException(); _stackData = GetService(); diff --git a/tests/Exceptionless.Tests/Repositories/TokenRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/TokenRepositoryTests.cs index 793418be24..3912bdde0a 100644 --- a/tests/Exceptionless.Tests/Repositories/TokenRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/TokenRepositoryTests.cs @@ -13,7 +13,7 @@ public sealed class TokenRepositoryTests : IntegrationTestsBase { private readonly ITokenRepository _repository; - public TokenRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public TokenRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _repository = GetService(); } diff --git a/tests/Exceptionless.Tests/Repositories/WebHookRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/WebHookRepositoryTests.cs index 7918fe2a0f..ac1e90c916 100644 --- a/tests/Exceptionless.Tests/Repositories/WebHookRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/WebHookRepositoryTests.cs @@ -11,7 +11,7 @@ public sealed class WebHookRepositoryTests : IntegrationTestsBase { private readonly IWebHookRepository _repository; - public WebHookRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public WebHookRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _repository = GetService(); } diff --git a/tests/Exceptionless.Tests/Search/EventIndexTests.cs b/tests/Exceptionless.Tests/Search/EventIndexTests.cs index 5a66fab4d9..6656cb2b15 100644 --- a/tests/Exceptionless.Tests/Search/EventIndexTests.cs +++ b/tests/Exceptionless.Tests/Search/EventIndexTests.cs @@ -16,7 +16,7 @@ public sealed class EventIndexTests : IntegrationTestsBase private readonly IEventRepository _repository; private readonly PersistentEventQueryValidator _validator; - public EventIndexTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public EventIndexTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { TimeProvider.SetUtcNow(new DateTime(2015, 2, 13, 0, 0, 0, DateTimeKind.Utc)); _eventData = GetService(); diff --git a/tests/Exceptionless.Tests/Search/EventStackFilterQueryTests.cs b/tests/Exceptionless.Tests/Search/EventStackFilterQueryTests.cs index 2f7b9e8305..ead126d6b3 100644 --- a/tests/Exceptionless.Tests/Search/EventStackFilterQueryTests.cs +++ b/tests/Exceptionless.Tests/Search/EventStackFilterQueryTests.cs @@ -16,7 +16,7 @@ public class EventStackFilterQueryTests : IntegrationTestsBase private readonly IEventRepository _eventRepository; private static bool _isTestDataGenerated; - public EventStackFilterQueryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public EventStackFilterQueryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _stackRepository = GetService(); _eventRepository = GetService(); diff --git a/tests/Exceptionless.Tests/Search/EventStackFilterTests.cs b/tests/Exceptionless.Tests/Search/EventStackFilterTests.cs index ceb0a311f3..a53c1c7698 100644 --- a/tests/Exceptionless.Tests/Search/EventStackFilterTests.cs +++ b/tests/Exceptionless.Tests/Search/EventStackFilterTests.cs @@ -15,7 +15,7 @@ public sealed class EventStackFilterTests : IntegrationTestsBase private readonly EventData _eventData; private readonly IEventRepository _eventRepository; - public EventStackFilterTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public EventStackFilterTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { TimeProvider.SetUtcNow(new DateTime(2015, 2, 13, 0, 0, 0, DateTimeKind.Utc)); _stackData = GetService(); diff --git a/tests/Exceptionless.Tests/Search/MoreEventIndexTests.cs b/tests/Exceptionless.Tests/Search/MoreEventIndexTests.cs index 0c1772f176..3b8f630467 100644 --- a/tests/Exceptionless.Tests/Search/MoreEventIndexTests.cs +++ b/tests/Exceptionless.Tests/Search/MoreEventIndexTests.cs @@ -15,7 +15,7 @@ public sealed class MoreEventIndexTests : IntegrationTestsBase private readonly IEventRepository _repository; private readonly PersistentEventQueryValidator _validator; - public MoreEventIndexTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public MoreEventIndexTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { TimeProvider.SetUtcNow(new DateTime(2015, 2, 13, 0, 0, 0, DateTimeKind.Utc)); _repository = GetService(); diff --git a/tests/Exceptionless.Tests/Search/StackIndexTests.cs b/tests/Exceptionless.Tests/Search/StackIndexTests.cs index b955960c00..6f3e74febe 100644 --- a/tests/Exceptionless.Tests/Search/StackIndexTests.cs +++ b/tests/Exceptionless.Tests/Search/StackIndexTests.cs @@ -13,7 +13,7 @@ public sealed class StackIndexTests : IntegrationTestsBase private readonly StackData _stackData; private readonly IStackRepository _repository; - public StackIndexTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public StackIndexTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _stackData = GetService(); _repository = GetService(); diff --git a/tests/Exceptionless.Tests/Services/StackServiceTests.cs b/tests/Exceptionless.Tests/Services/StackServiceTests.cs index 66e781ac19..039f97580f 100644 --- a/tests/Exceptionless.Tests/Services/StackServiceTests.cs +++ b/tests/Exceptionless.Tests/Services/StackServiceTests.cs @@ -17,7 +17,7 @@ public class StackServiceTests : IntegrationTestsBase private readonly StackService _stackService; private readonly IStackRepository _stackRepository; - public StackServiceTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public StackServiceTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { Log.SetLogLevel(LogLevel.Trace); _cache = GetService(); diff --git a/tests/Exceptionless.Tests/Services/UsageServiceTests.cs b/tests/Exceptionless.Tests/Services/UsageServiceTests.cs index 80dab5cb81..1a80de50f8 100644 --- a/tests/Exceptionless.Tests/Services/UsageServiceTests.cs +++ b/tests/Exceptionless.Tests/Services/UsageServiceTests.cs @@ -21,7 +21,7 @@ public sealed class UsageServiceTests : IntegrationTestsBase private readonly UsageService _usageService; private readonly BillingPlans _plans; - public UsageServiceTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public UsageServiceTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { TimeProvider.SetUtcNow(new DateTime(2015, 2, 13, 0, 0, 0, DateTimeKind.Utc)); Log.SetLogLevel(LogLevel.Information); diff --git a/tests/Exceptionless.Tests/Stats/AggregationTests.cs b/tests/Exceptionless.Tests/Stats/AggregationTests.cs index 459804b710..77f1971ea7 100644 --- a/tests/Exceptionless.Tests/Stats/AggregationTests.cs +++ b/tests/Exceptionless.Tests/Stats/AggregationTests.cs @@ -28,7 +28,7 @@ public sealed class AggregationTests : IntegrationTestsBase private readonly BillingManager _billingManager; private readonly BillingPlans _plans; - public AggregationTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) + public AggregationTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { _pipeline = GetService(); _eventData = GetService(); From 10ed60c473ce20eb364226c5537855cc50eeb95c Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Thu, 14 Nov 2024 09:19:59 -0600 Subject: [PATCH 06/24] Fix duplicate project refs --- src/Exceptionless.Web/Exceptionless.Web.csproj | 2 -- tests/Exceptionless.Tests/Exceptionless.Tests.csproj | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/Exceptionless.Web/Exceptionless.Web.csproj b/src/Exceptionless.Web/Exceptionless.Web.csproj index 2d545cf571..061acb4e1c 100644 --- a/src/Exceptionless.Web/Exceptionless.Web.csproj +++ b/src/Exceptionless.Web/Exceptionless.Web.csproj @@ -10,8 +10,6 @@ - - diff --git a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj index 089ca72b51..397f0172ae 100644 --- a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj +++ b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj @@ -9,9 +9,7 @@ - - From c2a626fb1b954d0a9fbac9226be75ddf1c346426 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 3 Dec 2024 15:41:46 -0600 Subject: [PATCH 07/24] Update elasticsearch to 8.16.1 --- .devcontainer/docker-compose.yml | 4 ++-- Dockerfile | 2 +- build/docker/elasticsearch/8.x/Dockerfile | 2 +- docker/docker-compose.apm.yml | 8 ++++---- docker/docker-compose.dev.yml | 4 ++-- docker/docker-compose.yml | 4 ++-- k8s/elastic-monitor.yaml | 8 ++++---- k8s/ex-dev-elasticsearch.yaml | 8 ++++---- k8s/ex-prod-elasticsearch.yaml | 6 +++--- k8s/ex-prod-tasks.ps1 | 6 +++--- k8s/ex-setup.ps1 | 4 ++-- k8s/exceptionless/values.yaml | 2 +- samples/docker-compose.all-in-one.yml | 2 +- samples/docker-compose.yml | 4 ++-- .../Extensions/ElasticsearchExtensions.cs | 4 ++-- src/Exceptionless.AppHost/Program.cs | 2 +- tests/Exceptionless.Tests/AspireWebHostFactory.cs | 2 +- 17 files changed, 36 insertions(+), 36 deletions(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index ad6845260e..f5a314384a 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -22,7 +22,7 @@ services: # (Adding the "ports" property to this file will not forward from a Codespace.) elasticsearch: - image: exceptionless/elasticsearch:8.15.2 + image: exceptionless/elasticsearch:8.16.1 environment: node.name: elasticsearch cluster.name: exceptionless @@ -38,7 +38,7 @@ services: kibana: depends_on: - elasticsearch - image: docker.elastic.co/kibana/kibana:8.15.2 + image: docker.elastic.co/kibana/kibana:8.16.1 environment: xpack.security.enabled: "false" ports: diff --git a/Dockerfile b/Dockerfile index 2ea29f16af..e07260c693 100644 --- a/Dockerfile +++ b/Dockerfile @@ -99,7 +99,7 @@ ENTRYPOINT ["/app/app-docker-entrypoint.sh"] # completely self-contained -FROM exceptionless/elasticsearch:8.15.2 AS exceptionless +FROM exceptionless/elasticsearch:8.16.1 AS exceptionless WORKDIR /app COPY --from=job-publish /app/src/Exceptionless.Job/out ./ diff --git a/build/docker/elasticsearch/8.x/Dockerfile b/build/docker/elasticsearch/8.x/Dockerfile index 50ccb12583..29277a8e56 100644 --- a/build/docker/elasticsearch/8.x/Dockerfile +++ b/build/docker/elasticsearch/8.x/Dockerfile @@ -1,5 +1,5 @@ # https://www.docker.elastic.co/ -FROM docker.elastic.co/elasticsearch/elasticsearch:8.15.2 +FROM docker.elastic.co/elasticsearch/elasticsearch:8.16.1 RUN elasticsearch-plugin install -b mapper-size diff --git a/docker/docker-compose.apm.yml b/docker/docker-compose.apm.yml index cf46a0193b..0a1bc5a5d5 100644 --- a/docker/docker-compose.apm.yml +++ b/docker/docker-compose.apm.yml @@ -2,7 +2,7 @@ version: "2.2" services: setup: - image: docker.elastic.co/elasticsearch/elasticsearch:8.15.2 + image: docker.elastic.co/elasticsearch/elasticsearch:8.16.1 volumes: - certs:/usr/share/elasticsearch/config/certs user: "0" @@ -53,7 +53,7 @@ services: depends_on: setup: condition: service_healthy - image: docker.elastic.co/elasticsearch/elasticsearch:8.15.2 + image: docker.elastic.co/elasticsearch/elasticsearch:8.16.1 volumes: - certs:/usr/share/elasticsearch/config/certs - esdata:/usr/share/elasticsearch/data @@ -98,7 +98,7 @@ services: depends_on: elasticsearch: condition: service_healthy - image: docker.elastic.co/kibana/kibana:8.15.2 + image: docker.elastic.co/kibana/kibana:8.16.1 volumes: - certs:/usr/share/kibana/config/certs ports: @@ -124,7 +124,7 @@ services: depends_on: elasticsearch: condition: service_healthy - image: docker.elastic.co/apm/apm-server:8.15.2 + image: docker.elastic.co/apm/apm-server:8.16.1 volumes: - certs:/usr/share/apm-server/certs ports: diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 6214935f37..afc7f5fa2c 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -50,7 +50,7 @@ services: - appdata:/app/storage elasticsearch: - image: exceptionless/elasticsearch:8.15.2 + image: exceptionless/elasticsearch:8.16.1 environment: discovery.type: single-node xpack.security.enabled: "false" @@ -64,7 +64,7 @@ services: kibana: depends_on: - elasticsearch - image: docker.elastic.co/kibana/kibana:8.15.2 + image: docker.elastic.co/kibana/kibana:8.16.1 ports: - 5601:5601 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 02efb70a4d..965df5930d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,6 +1,6 @@ services: elasticsearch: - image: exceptionless/elasticsearch:8.15.2 + image: exceptionless/elasticsearch:8.16.1 environment: node.name: elasticsearch cluster.name: exceptionless @@ -16,7 +16,7 @@ services: kibana: depends_on: - elasticsearch - image: docker.elastic.co/kibana/kibana:8.15.2 + image: docker.elastic.co/kibana/kibana:8.16.1 environment: xpack.security.enabled: "false" ports: diff --git a/k8s/elastic-monitor.yaml b/k8s/elastic-monitor.yaml index 7e9fec6e12..ed041e9aa8 100644 --- a/k8s/elastic-monitor.yaml +++ b/k8s/elastic-monitor.yaml @@ -4,7 +4,7 @@ metadata: name: elastic-monitor namespace: elastic-system spec: - version: 8.15.2 + version: 8.16.1 podDisruptionBudget: spec: minAvailable: 2 @@ -56,7 +56,7 @@ metadata: name: kibana-monitor namespace: elastic-system spec: - version: 8.15.2 + version: 8.16.1 count: 1 elasticsearchRef: name: elastic-monitor @@ -150,7 +150,7 @@ metadata: name: fleet-server namespace: elastic-system spec: - version: 8.15.2 + version: 8.16.1 kibanaRef: name: kibana-monitor elasticsearchRefs: @@ -174,7 +174,7 @@ metadata: name: elastic-agent namespace: elastic-system spec: - version: 8.15.2 + version: 8.16.1 kibanaRef: name: kibana-monitor fleetServerRef: diff --git a/k8s/ex-dev-elasticsearch.yaml b/k8s/ex-dev-elasticsearch.yaml index 8606ab3fae..5a66c9ece5 100644 --- a/k8s/ex-dev-elasticsearch.yaml +++ b/k8s/ex-dev-elasticsearch.yaml @@ -4,8 +4,8 @@ metadata: name: ex-dev namespace: ex-dev spec: - version: 8.15.2 - image: exceptionless/elasticsearch:8.15.2 # https://github.com/exceptionless/Exceptionless/tree/main/build/docker/elasticsearch + version: 8.16.1 + image: exceptionless/elasticsearch:8.16.1 # https://github.com/exceptionless/Exceptionless/tree/main/build/docker/elasticsearch secureSettings: - secretName: ex-dev-snapshots http: @@ -16,7 +16,7 @@ spec: - name: main count: 1 config: - node.roles: [data, ingest, master] + node.roles: [data, ingest, master, remote_cluster_client] action.destructive_requires_name: false podTemplate: spec: @@ -57,7 +57,7 @@ metadata: name: ex-dev namespace: ex-dev spec: - version: 8.15.2 + version: 8.16.1 count: 1 elasticsearchRef: name: ex-dev diff --git a/k8s/ex-prod-elasticsearch.yaml b/k8s/ex-prod-elasticsearch.yaml index 6aa7237eb1..c56ef6454e 100644 --- a/k8s/ex-prod-elasticsearch.yaml +++ b/k8s/ex-prod-elasticsearch.yaml @@ -4,8 +4,8 @@ metadata: name: ex-prod namespace: ex-prod spec: - version: 8.15.2 - image: exceptionless/elasticsearch:8.15.2 # https://github.com/exceptionless/Exceptionless/tree/main/build/docker/elasticsearch + version: 8.16.1 + image: exceptionless/elasticsearch:8.16.1 # https://github.com/exceptionless/Exceptionless/tree/main/build/docker/elasticsearch # monitoring: # metrics: # elasticsearchRefs: @@ -68,7 +68,7 @@ metadata: name: ex-prod namespace: ex-prod spec: - version: 8.15.2 + version: 8.16.1 count: 1 elasticsearchRef: name: ex-prod diff --git a/k8s/ex-prod-tasks.ps1 b/k8s/ex-prod-tasks.ps1 index 5b6cb0f006..85110fb684 100644 --- a/k8s/ex-prod-tasks.ps1 +++ b/k8s/ex-prod-tasks.ps1 @@ -80,9 +80,9 @@ helm upgrade vpa fairwinds-stable/vpa --namespace vpa -f vpa-values.yaml --reset # upgrade elasticsearch operator # https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html # https://github.com/elastic/cloud-on-k8s/releases -kubectl replace -f https://download.elastic.co/downloads/eck/2.14.0/crds.yaml -kubectl create -f https://download.elastic.co/downloads/eck/2.14.0/crds.yaml -kubectl apply -f https://download.elastic.co/downloads/eck/2.14.0/operator.yaml +kubectl replace -f https://download.elastic.co/downloads/eck/2.15.0/crds.yaml +kubectl create -f https://download.elastic.co/downloads/eck/2.15.0/crds.yaml +kubectl apply -f https://download.elastic.co/downloads/eck/2.15.0/operator.yaml # upgrade elasticsearch kubectl apply --namespace ex-prod -f ex-prod-elasticsearch.yaml diff --git a/k8s/ex-setup.ps1 b/k8s/ex-setup.ps1 index fc284bfa67..42a79657ef 100644 --- a/k8s/ex-setup.ps1 +++ b/k8s/ex-setup.ps1 @@ -60,8 +60,8 @@ kubectl config set-context --current --namespace=ex-$ENV # setup elasticsearch operator # https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html # https://github.com/elastic/cloud-on-k8s/releases -kubectl create -f https://download.elastic.co/downloads/eck/2.14.0/crds.yaml -kubectl apply -f https://download.elastic.co/downloads/eck/2.14.0/operator.yaml +kubectl create -f https://download.elastic.co/downloads/eck/2.15.0/crds.yaml +kubectl apply -f https://download.elastic.co/downloads/eck/2.15.0/operator.yaml # view ES operator logs kubectl -n elastic-system logs -f statefulset.apps/elastic-operator diff --git a/k8s/exceptionless/values.yaml b/k8s/exceptionless/values.yaml index 9340cbbf03..bf6d1af55e 100644 --- a/k8s/exceptionless/values.yaml +++ b/k8s/exceptionless/values.yaml @@ -51,7 +51,7 @@ elasticsearch: connectionString: image: repository: exceptionless/elasticsearch - tag: 8.15.2 + tag: 8.16.1 pullPolicy: IfNotPresent redis: diff --git a/samples/docker-compose.all-in-one.yml b/samples/docker-compose.all-in-one.yml index 202c4e00e7..fac6b62e1d 100644 --- a/samples/docker-compose.all-in-one.yml +++ b/samples/docker-compose.all-in-one.yml @@ -20,7 +20,7 @@ services: kibana: depends_on: - elasticsearch - image: docker.elastic.co/kibana/kibana:8.15.2 + image: docker.elastic.co/kibana/kibana:8.16.1 ports: - 5601:5601 diff --git a/samples/docker-compose.yml b/samples/docker-compose.yml index dabd27aa64..312c5388e6 100644 --- a/samples/docker-compose.yml +++ b/samples/docker-compose.yml @@ -44,7 +44,7 @@ services: - ex_appdata:/app/storage elasticsearch: - image: exceptionless/elasticsearch:8.15.2 + image: exceptionless/elasticsearch:8.16.1 environment: discovery.type: single-node xpack.security.enabled: "false" @@ -58,7 +58,7 @@ services: kibana: depends_on: - elasticsearch - image: docker.elastic.co/kibana/kibana:8.15.2 + image: docker.elastic.co/kibana/kibana:8.16.1 ports: - 5601:5601 diff --git a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs index 0460f4fe8e..d4e5a86fd3 100644 --- a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs +++ b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs @@ -16,7 +16,7 @@ public static class ElasticsearchBuilderExtensions private const int KibanaPort = 5601; /// - /// Adds a Elasticsearch container to the application model. The default image is "docker.elastic.co/elasticsearch/elasticsearch". This version the package defaults to the 8.15.2 tag of the Elasticsearch container image + /// Adds a Elasticsearch container to the application model. The default image is "docker.elastic.co/elasticsearch/elasticsearch". This version the package defaults to the 8.16.1 tag of the Elasticsearch container image /// /// The . /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. @@ -117,5 +117,5 @@ internal static class ElasticsearchContainerImageTags public const string Registry = "docker.elastic.co"; public const string Image = "elasticsearch/elasticsearch"; public const string KibanaImage = "kibana/kibana"; - public const string Tag = "8.15.2"; + public const string Tag = "8.16.1"; } diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index 9665fab105..f0052cc490 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -1,7 +1,7 @@ var builder = DistributedApplication.CreateBuilder(args); var elastic = builder.AddElasticsearch("Elasticsearch", port: 9200) - .WithImageTag("8.15.2") + .WithImageTag("8.16.1") .WithLifetime(ContainerLifetime.Persistent) .WithContainerName("Exceptionless-Elasticsearch") .WithDataVolume("exceptionless.data.v1") diff --git a/tests/Exceptionless.Tests/AspireWebHostFactory.cs b/tests/Exceptionless.Tests/AspireWebHostFactory.cs index 30aec81cf2..f4341de31e 100644 --- a/tests/Exceptionless.Tests/AspireWebHostFactory.cs +++ b/tests/Exceptionless.Tests/AspireWebHostFactory.cs @@ -25,7 +25,7 @@ public async Task InitializeAsync() builder.AddElasticsearch("Elasticsearch") .WithContainerName("Exceptionless-Elasticsearch-Test") - .WithImageTag("8.15.2") + .WithImageTag("8.16.1") .WithLifetime(ContainerLifetime.Persistent); builder.AddRedis("Redis") From 83a5df38cd77c3308ea4868b526df4c29033b6aa Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Thu, 5 Dec 2024 22:41:39 -0600 Subject: [PATCH 08/24] Add storage to Aspire --- .../Exceptionless.AppHost.csproj | 1 + .../Extensions/MinIoExtensions.cs | 138 ++++++++++++++++++ src/Exceptionless.AppHost/Program.cs | 5 + .../Exceptionless.Job.csproj | 2 +- src/Exceptionless.Job/Program.cs | 4 +- src/Exceptionless.Web/ApmExtensions.cs | 4 +- .../Exceptionless.Web.csproj | 2 +- src/Exceptionless.Web/Startup.cs | 5 +- 8 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs diff --git a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj index 10a13c3a15..74d6e01d75 100644 --- a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj +++ b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs b/src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs new file mode 100644 index 0000000000..6360caeb38 --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs @@ -0,0 +1,138 @@ +using Foundatio.Storage; + +namespace Aspire.Hosting; + +public static class MinIoExtensions +{ + public static IResourceBuilder AddMinIo( + this IDistributedApplicationBuilder builder, + string name, + Action? configure = null) + { + var options = new MinIoBuilder(); + configure?.Invoke(options); + + var resource = new MinIoResource(name, options.AccessKey, options.SecretKey, options.Bucket ?? "storage"); + + string? connectionString = null; + + builder.Eventing.Subscribe(resource, async (@event, ct) => + { + connectionString = await resource.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); + + if (connectionString == null) + throw new DistributedApplicationException($"ResourceReadyEvent was published for the '{resource.Name}' resource but the connection string was null."); + + var storage = new S3FileStorage(o => o.ConnectionString(connectionString)); + try + { + storage.Client.PutBucketAsync(options.Bucket ?? "storage", ct).GetAwaiter().GetResult(); + } + catch + { + // ignored + } + }); + + return builder.AddResource(resource) + .WithImage(MinIoContainerImageTags.Image) + .WithImageRegistry(MinIoContainerImageTags.Registry) + .WithImageTag(MinIoContainerImageTags.Tag) + .WithArgs("server", "/data", "--console-address", $":{MinIoResource.DefaultConsolePort}") + .WithEndpoint(port: options.ApiPort, targetPort: MinIoResource.DefaultApiPort, name: MinIoResource.ApiEndpointName) + .WithHttpEndpoint(port: options.ConsolePort, targetPort: MinIoResource.DefaultConsolePort, name: MinIoResource.ConsoleEndpointName) + .ConfigureCredentials(options) + .ConfigureVolume(options); + } + + private static IResourceBuilder ConfigureCredentials( + this IResourceBuilder builder, + MinIoBuilder options) + { + return builder + .WithEnvironment("MINIO_ROOT_USER", options.AccessKey ?? "minioadmin") + .WithEnvironment("MINIO_ROOT_PASSWORD", options.SecretKey ?? "minioadmin"); + } + + private static IResourceBuilder ConfigureVolume( + this IResourceBuilder builder, + MinIoBuilder options) + { + if (!string.IsNullOrEmpty(options.DataVolumePath)) + builder = builder.WithVolume(options.DataVolumePath, "/data"); + + return builder; + } +} + +public class MinIoResource(string name, string? accessKey = null, string? secretKey = null, string? bucket = "storage") + : ContainerResource(name), IResourceWithConnectionString +{ + internal const string ApiEndpointName = "api"; + internal const string ConsoleEndpointName = "console"; + internal const int DefaultApiPort = 9000; + internal const int DefaultConsolePort = 9001; + + private EndpointReference? _apiReference; + private EndpointReference? _consoleReference; + + private EndpointReference ApiEndpoint => + _apiReference ??= new EndpointReference(this, ApiEndpointName); + + private EndpointReference ConsoleEndpoint => + _consoleReference ??= new EndpointReference(this, ConsoleEndpointName); + + public ReferenceExpression ConnectionStringExpression => + ReferenceExpression.Create( + $"ServiceUrl=http://{ApiEndpoint.Property(EndpointProperty.Host)}:{ApiEndpoint.Property(EndpointProperty.Port)};" + + $"AccessKey={AccessKey ?? "minioadmin"};" + + $"SecretKey={SecretKey ?? "minioadmin"};" + + $"Bucket={Bucket}"); + + public string? AccessKey { get; } = accessKey; + public string? SecretKey { get; } = secretKey; + public string? Bucket { get; } = bucket; +} + +public class MinIoBuilder +{ + public int? ApiPort { get; set; } + public int? ConsolePort { get; set; } + public string? AccessKey { get; set; } + public string? SecretKey { get; set; } + public string? Bucket { get; set; } + public string? DataVolumePath { get; set; } + + public MinIoBuilder WithPorts(int? apiPort = null, int? consolePort = null) + { + ApiPort = apiPort; + ConsolePort = consolePort; + return this; + } + + public MinIoBuilder WithCredentials(string accessKey, string secretKey) + { + AccessKey = accessKey; + SecretKey = secretKey; + return this; + } + + public MinIoBuilder WithBucket(string bucket) + { + Bucket = bucket; + return this; + } + + public MinIoBuilder WithDataVolume(string path) + { + DataVolumePath = path; + return this; + } +} + +internal static class MinIoContainerImageTags +{ + internal const string Registry = "docker.io"; + internal const string Image = "minio/minio"; + internal const string Tag = "latest"; +} diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index f0052cc490..668a8576b8 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -7,6 +7,10 @@ .WithDataVolume("exceptionless.data.v1") .WithKibana(b => b.WithLifetime(ContainerLifetime.Persistent).WithContainerName("Exceptionless-Kibana")); +var storage = builder.AddMinIo("Storage", s => s.WithCredentials("guest", "password").WithPorts(9000)) + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerName("Exceptionless-Storage"); + var cache = builder.AddRedis("Redis", port: 6379) .WithImageTag("7.4") .WithLifetime(ContainerLifetime.Persistent) @@ -32,6 +36,7 @@ var api = builder.AddProject("Api", "Exceptionless") .WithReference(cache) .WithReference(elastic) + .WithReference(storage) .WithEnvironment("ConnectionStrings:Email", "smtp://localhost:1025") .WithEnvironment("RunJobsInProcess", "false") .WaitFor(elastic) diff --git a/src/Exceptionless.Job/Exceptionless.Job.csproj b/src/Exceptionless.Job/Exceptionless.Job.csproj index 5ae38e689a..10b6c6b6bd 100644 --- a/src/Exceptionless.Job/Exceptionless.Job.csproj +++ b/src/Exceptionless.Job/Exceptionless.Job.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Exceptionless.Job/Program.cs b/src/Exceptionless.Job/Program.cs index e77235fcb4..e6d422649e 100644 --- a/src/Exceptionless.Job/Program.cs +++ b/src/Exceptionless.Job/Program.cs @@ -85,13 +85,13 @@ public static IHostBuilder CreateHostBuilder(string[] args) app.UseSerilogRequestLogging(o => { o.MessageTemplate = "TraceId={TraceId} HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"; - o.GetLevel = (context, duration, ex) => + o.GetLevel = new Func((context, duration, ex) => { if (ex is not null || context.Response.StatusCode > 499) return LogEventLevel.Error; return duration < 1000 && context.Response.StatusCode < 400 ? LogEventLevel.Debug : LogEventLevel.Information; - }; + }); }); Bootstrapper.LogConfiguration(app.ApplicationServices, options, app.ApplicationServices.GetRequiredService>()); diff --git a/src/Exceptionless.Web/ApmExtensions.cs b/src/Exceptionless.Web/ApmExtensions.cs index c63169813d..ef11a669f2 100644 --- a/src/Exceptionless.Web/ApmExtensions.cs +++ b/src/Exceptionless.Web/ApmExtensions.cs @@ -36,7 +36,7 @@ public static IHostBuilder AddApm(this IHostBuilder builder, ApmConfig config) b.AddAspNetCoreInstrumentation(o => { - o.Filter = context => + o.Filter = new Func(context => { if (context.Request.Path.StartsWithSegments("/api/v2/push", StringComparison.OrdinalIgnoreCase)) return false; @@ -48,7 +48,7 @@ public static IHostBuilder AddApm(this IHostBuilder builder, ApmConfig config) return false; return true; - }; + }); }); b.AddElasticsearchClientInstrumentation(c => diff --git a/src/Exceptionless.Web/Exceptionless.Web.csproj b/src/Exceptionless.Web/Exceptionless.Web.csproj index 132ffa909b..13d3b903c3 100644 --- a/src/Exceptionless.Web/Exceptionless.Web.csproj +++ b/src/Exceptionless.Web/Exceptionless.Web.csproj @@ -29,7 +29,7 @@ - + diff --git a/src/Exceptionless.Web/Startup.cs b/src/Exceptionless.Web/Startup.cs index a2bd162f63..8b7bfdfcf3 100644 --- a/src/Exceptionless.Web/Startup.cs +++ b/src/Exceptionless.Web/Startup.cs @@ -14,6 +14,7 @@ using Foundatio.Extensions.Hosting.Startup; using Foundatio.Repositories.Exceptions; using Joonasw.AspNetCore.SecurityHeaders; +using Joonasw.AspNetCore.SecurityHeaders.Csp; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting.Server.Features; @@ -299,11 +300,11 @@ ApplicationException applicationException when applicationException.Message.Cont .To("https://api-iam.intercom.io/") .To("wss://nexus-websocket-a.intercom.io"); - csp.OnSendingHeader = context => + csp.OnSendingHeader = new Func(context => { context.ShouldNotSend = context.HttpContext.Request.Path.StartsWithSegments("/api"); return Task.CompletedTask; - }; + }); }); app.UseSerilogRequestLogging(o => From 4794fc87a06e9fb15b433de7232c66798c18a50e Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Mon, 16 Dec 2024 17:42:39 -0600 Subject: [PATCH 09/24] Update Elasticsearch --- .../Extensions/ElasticsearchExtensions.cs | 4 ++-- src/Exceptionless.AppHost/Program.cs | 2 +- tests/Exceptionless.Tests/AspireWebHostFactory.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs index d4e5a86fd3..5a40b9ed24 100644 --- a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs +++ b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs @@ -16,7 +16,7 @@ public static class ElasticsearchBuilderExtensions private const int KibanaPort = 5601; /// - /// Adds a Elasticsearch container to the application model. The default image is "docker.elastic.co/elasticsearch/elasticsearch". This version the package defaults to the 8.16.1 tag of the Elasticsearch container image + /// Adds a Elasticsearch container to the application model. The default image is "docker.elastic.co/elasticsearch/elasticsearch". This version the package defaults to the 8.17.0 tag of the Elasticsearch container image /// /// The . /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. @@ -117,5 +117,5 @@ internal static class ElasticsearchContainerImageTags public const string Registry = "docker.elastic.co"; public const string Image = "elasticsearch/elasticsearch"; public const string KibanaImage = "kibana/kibana"; - public const string Tag = "8.16.1"; + public const string Tag = "8.17.0"; } diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index 668a8576b8..3a9b1f5787 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -1,7 +1,7 @@ var builder = DistributedApplication.CreateBuilder(args); var elastic = builder.AddElasticsearch("Elasticsearch", port: 9200) - .WithImageTag("8.16.1") + .WithImageTag("8.17.0") .WithLifetime(ContainerLifetime.Persistent) .WithContainerName("Exceptionless-Elasticsearch") .WithDataVolume("exceptionless.data.v1") diff --git a/tests/Exceptionless.Tests/AspireWebHostFactory.cs b/tests/Exceptionless.Tests/AspireWebHostFactory.cs index f4341de31e..1d389b7277 100644 --- a/tests/Exceptionless.Tests/AspireWebHostFactory.cs +++ b/tests/Exceptionless.Tests/AspireWebHostFactory.cs @@ -25,7 +25,7 @@ public async Task InitializeAsync() builder.AddElasticsearch("Elasticsearch") .WithContainerName("Exceptionless-Elasticsearch-Test") - .WithImageTag("8.16.1") + .WithImageTag("8.17.0") .WithLifetime(ContainerLifetime.Persistent); builder.AddRedis("Redis") From 213332f83f343dd921a0ab194e71e5d809941fcf Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 17 Dec 2024 14:00:51 -0600 Subject: [PATCH 10/24] Fix tests --- .../Exceptionless.Insulation.csproj | 6 +- .../Exceptionless.Job.csproj | 8 +- .../Exceptionless.Web.csproj | 12 +- src/Exceptionless.Web/Startup.cs | 3 +- .../AspireWebHostFactory.cs | 23 ++-- .../Controllers/AuthControllerTests.cs | 108 +++++++++--------- .../Exceptionless.Tests.csproj | 2 +- .../IntegrationTestsBase.cs | 46 +++----- tests/Exceptionless.Tests/TestWithServices.cs | 9 +- 9 files changed, 101 insertions(+), 116 deletions(-) diff --git a/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj b/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj index 94aca8e88f..953d5aedea 100644 --- a/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj +++ b/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj @@ -14,10 +14,10 @@ - - + + - + diff --git a/src/Exceptionless.Job/Exceptionless.Job.csproj b/src/Exceptionless.Job/Exceptionless.Job.csproj index 10b6c6b6bd..a7161b3e28 100644 --- a/src/Exceptionless.Job/Exceptionless.Job.csproj +++ b/src/Exceptionless.Job/Exceptionless.Job.csproj @@ -7,7 +7,7 @@ - + @@ -15,12 +15,12 @@ - + - + - + diff --git a/src/Exceptionless.Web/Exceptionless.Web.csproj b/src/Exceptionless.Web/Exceptionless.Web.csproj index 13d3b903c3..7ca6504b29 100644 --- a/src/Exceptionless.Web/Exceptionless.Web.csproj +++ b/src/Exceptionless.Web/Exceptionless.Web.csproj @@ -16,8 +16,8 @@ - - + + @@ -25,13 +25,13 @@ - + - + - - + + diff --git a/src/Exceptionless.Web/Startup.cs b/src/Exceptionless.Web/Startup.cs index 8b7bfdfcf3..c20655cd01 100644 --- a/src/Exceptionless.Web/Startup.cs +++ b/src/Exceptionless.Web/Startup.cs @@ -311,7 +311,8 @@ ApplicationException applicationException when applicationException.Message.Cont { o.EnrichDiagnosticContext = (context, httpContext) => { - context.Set("ActivityId", Activity.Current?.Id); + if (Activity.Current?.Id is not null) + context.Set("ActivityId", Activity.Current.Id); }; o.MessageTemplate = "{ActivityId} HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"; o.GetLevel = (context, duration, ex) => diff --git a/tests/Exceptionless.Tests/AspireWebHostFactory.cs b/tests/Exceptionless.Tests/AspireWebHostFactory.cs index 1d389b7277..f7778df108 100644 --- a/tests/Exceptionless.Tests/AspireWebHostFactory.cs +++ b/tests/Exceptionless.Tests/AspireWebHostFactory.cs @@ -1,6 +1,5 @@ using Aspire.Hosting; using Aspire.Hosting.ApplicationModel; -using Aspire.Hosting.Testing; using Exceptionless.Insulation.Configuration; using Exceptionless.Web; using Microsoft.AspNetCore.Mvc.Testing; @@ -15,30 +14,22 @@ public class AspireWebHostFactory : WebApplicationFactory, IAsyncLifeti public DistributedApplication App => _app ?? throw new InvalidOperationException("The application is not initialized"); - public string? ElasticsearchConnectionString { get; private set; } - public string? RedisConnectionString { get; private set; } - - public async Task InitializeAsync() + public Task InitializeAsync() { var options = new DistributedApplicationOptions { AssemblyName = typeof(ElasticsearchResource).Assembly.FullName, DisableDashboard = true }; var builder = DistributedApplication.CreateBuilder(options); - builder.AddElasticsearch("Elasticsearch") - .WithContainerName("Exceptionless-Elasticsearch-Test") + // don't use random ports for tests + builder.Configuration["DcpPublisher:RandomizePorts"] = "false"; + + builder.AddElasticsearch("Elasticsearch", port: 9200) + .WithContainerName("Exceptionless-Elasticsearch") .WithImageTag("8.17.0") .WithLifetime(ContainerLifetime.Persistent); - builder.AddRedis("Redis") - .WithContainerName("Exceptionless-Redis-Test") - .WithImageTag("7.4") - .WithLifetime(ContainerLifetime.Persistent);; - _app = builder.Build(); - await _app.StartAsync(); - - ElasticsearchConnectionString = await _app.GetConnectionStringAsync("Elasticsearch"); - RedisConnectionString = await _app.GetConnectionStringAsync("Redis"); + return _app.StartAsync(); } protected override void ConfigureWebHost(IWebHostBuilder builder) diff --git a/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs b/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs index 54b5a0414c..92336f058b 100644 --- a/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs @@ -22,17 +22,21 @@ namespace Exceptionless.Tests.Controllers; public class AuthControllerTests : IntegrationTestsBase { - private readonly AuthOptions _authOptions; - private readonly IUserRepository _userRepository; - private readonly IOrganizationRepository _organizationRepository; - private readonly ITokenRepository _tokenRepository; + private AuthOptions? _authOptions; + private IUserRepository? _userRepository; + private IOrganizationRepository? _organizationRepository; + private ITokenRepository? _tokenRepository; public AuthControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { + } + + public override async Task InitializeAsync() + { + await base.InitializeAsync(); _authOptions = GetService(); _authOptions.EnableAccountCreation = true; _authOptions.EnableActiveDirectoryAuth = false; - _organizationRepository = GetService(); _userRepository = GetService(); _tokenRepository = GetService(); @@ -71,7 +75,7 @@ public async Task CannotSignupWithoutPassword() [InlineData(false, "test1@exceptionless.io", "Password1$")] public Task CannotSignupWhenAccountCreationDisabledWithNoTokenAsync(bool enableAdAuth, string email, string password) { - _authOptions.EnableAccountCreation = false; + _authOptions!.EnableAccountCreation = false; _authOptions.EnableActiveDirectoryAuth = enableAdAuth; if (enableAdAuth && email == TestDomainLoginProvider.ValidUsername) @@ -100,7 +104,7 @@ public Task CannotSignupWhenAccountCreationDisabledWithNoTokenAsync(bool enableA [InlineData(false, "test2@exceptionless.io", "Password1$")] public Task CannotSignupWhenAccountCreationDisabledWithInvalidTokenAsync(bool enableAdAuth, string email, string password) { - _authOptions.EnableAccountCreation = false; + _authOptions!.EnableAccountCreation = false; _authOptions.EnableActiveDirectoryAuth = enableAdAuth; if (enableAdAuth && email == TestDomainLoginProvider.ValidUsername) @@ -128,7 +132,7 @@ public Task CannotSignupWhenAccountCreationDisabledWithInvalidTokenAsync(bool en [InlineData(false, "test3@exceptionless.io", "Password1$")] public async Task CanSignupWhenAccountCreationDisabledWithValidTokenAsync(bool enableAdAuth, string email, string password) { - _authOptions.EnableAccountCreation = false; + _authOptions!.EnableAccountCreation = false; _authOptions.EnableActiveDirectoryAuth = enableAdAuth; if (enableAdAuth && email == TestDomainLoginProvider.ValidUsername) @@ -137,7 +141,7 @@ public async Task CanSignupWhenAccountCreationDisabledWithValidTokenAsync(bool e email = provider.GetEmailAddressFromUsername(email); } - var results = await _organizationRepository.GetAllAsync(); + var results = await _organizationRepository!.GetAllAsync(); var organization = results.Documents.First(); var invite = new Invite @@ -166,7 +170,7 @@ public async Task CanSignupWhenAccountCreationDisabledWithValidTokenAsync(bool e Assert.NotNull(result); Assert.False(String.IsNullOrEmpty(result.Token)); - var user = await _userRepository.GetByEmailAddressAsync(email); + var user = await _userRepository!.GetByEmailAddressAsync(email); Assert.NotNull(user); Assert.Equal("Test", user.FullName); Assert.Equal(email, user.EmailAddress); @@ -182,13 +186,13 @@ public async Task CanSignupWhenAccountCreationDisabledWithValidTokenAsync(bool e [Fact] public async Task CanSignupWhenAccountCreationDisabledWithValidTokenAndInvalidAdAccountAsync() { - _authOptions.EnableAccountCreation = false; + _authOptions!.EnableAccountCreation = false; _authOptions.EnableActiveDirectoryAuth = true; const string email = "test-user1@exceptionless.io"; const string password = "invalidAccount1"; - var organizations = await _organizationRepository.GetAllAsync(); + var organizations = await _organizationRepository!.GetAllAsync(); var organization = organizations.Documents.First(); var invite = new Invite { @@ -218,7 +222,7 @@ await SendRequestAsync(r => r [Fact] public async Task CanSignupWhenAccountCreationEnabledWithNoTokenAsync() { - _authOptions.EnableAccountCreation = true; + _authOptions!.EnableAccountCreation = true; const string email = "test4@exceptionless.io"; const string password = "Password1$"; @@ -239,7 +243,7 @@ public async Task CanSignupWhenAccountCreationEnabledWithNoTokenAsync() Assert.NotNull(result); Assert.False(String.IsNullOrEmpty(result.Token)); - var user = await _userRepository.GetByEmailAddressAsync(email); + var user = await _userRepository!.GetByEmailAddressAsync(email); Assert.NotNull(user); Assert.Equal("Test", user.FullName); Assert.Equal(email, user.EmailAddress); @@ -254,7 +258,7 @@ public async Task CanSignupWhenAccountCreationEnabledWithNoTokenAsync() [Fact] public async Task CanSignupWhenAccountCreationEnabledWithNoTokenAndValidAdAccountAsync() { - _authOptions.EnableAccountCreation = true; + _authOptions!.EnableAccountCreation = true; _authOptions.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); @@ -280,7 +284,7 @@ public async Task CanSignupWhenAccountCreationEnabledWithNoTokenAndValidAdAccoun [Fact] public Task CanSignupWhenAccountCreationEnabledWithNoTokenAndInvalidAdAccountAsync() { - _authOptions.EnableAccountCreation = true; + _authOptions!.EnableAccountCreation = true; _authOptions.EnableActiveDirectoryAuth = true; return SendRequestAsync(r => r @@ -300,9 +304,9 @@ public Task CanSignupWhenAccountCreationEnabledWithNoTokenAndInvalidAdAccountAsy [Fact] public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAsync() { - _authOptions.EnableAccountCreation = true; + _authOptions!.EnableAccountCreation = true; - var organizations = await _organizationRepository.GetAllAsync(); + var organizations = await _organizationRepository!.GetAllAsync(); var organization = organizations.Documents.First(); const string email = "test5@exceptionless.io"; const string name = "Test"; @@ -338,7 +342,7 @@ public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAsync() await RefreshDataAsync(); - var user = await _userRepository.GetByEmailAddressAsync(email); + var user = await _userRepository!.GetByEmailAddressAsync(email); Assert.NotNull(user); Assert.Equal("Test", user.FullName); Assert.NotEmpty(user.OrganizationIds); @@ -350,7 +354,7 @@ public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAsync() organization = await _organizationRepository.GetByIdAsync(organization.Id); Assert.Empty(organization.Invites); - var token = await _tokenRepository.GetByIdAsync(result.Token); + var token = await _tokenRepository!.GetByIdAsync(result.Token); Assert.NotNull(token); Assert.Equal(user.Id, token.UserId); Assert.Equal(TokenType.Authentication, token.Type); @@ -363,13 +367,13 @@ public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAsync() [Fact] public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAndValidAdAccountAsync() { - _authOptions.EnableAccountCreation = true; + _authOptions!.EnableAccountCreation = true; _authOptions.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); - var results = await _organizationRepository.GetAllAsync(); + var results = await _organizationRepository!.GetAllAsync(); var organization = results.Documents.First(); var invite = new Invite { @@ -401,11 +405,11 @@ public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAndValidAdAcc [Fact] public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAndInvalidAdAccountAsync() { - _authOptions.EnableAccountCreation = true; + _authOptions!.EnableAccountCreation = true; _authOptions.EnableActiveDirectoryAuth = true; string email = "test-user4@exceptionless.io"; - var results = await _organizationRepository.GetAllAsync(); + var results = await _organizationRepository!.GetAllAsync(); var organization = results.Documents.First(); var invite = new Invite { @@ -448,7 +452,7 @@ public async Task SignupShouldFailWhenUsingExistingAccountWithNoPasswordOrInvali }; user.MarkEmailAddressVerified(); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); var problemDetails = await SendRequestAsAsync(r => r .Post() @@ -482,7 +486,7 @@ await SendRequestAsync(r => r [Fact] public async Task LoginValidAsync() { - _authOptions.EnableActiveDirectoryAuth = false; + _authOptions!.EnableActiveDirectoryAuth = false; const string email = "test6@exceptionless.io"; const string password = "Test6 password"; @@ -497,7 +501,7 @@ public async Task LoginValidAsync() }; user.MarkEmailAddressVerified(); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -517,7 +521,7 @@ public async Task LoginValidAsync() [Fact] public async Task LoginInvalidPasswordAsync() { - _authOptions.EnableActiveDirectoryAuth = false; + _authOptions!.EnableActiveDirectoryAuth = false; const string email = "test7@exceptionless.io"; const string password = "Test7 password"; @@ -533,7 +537,7 @@ public async Task LoginInvalidPasswordAsync() }; user.MarkEmailAddressVerified(); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); await SendRequestAsync(r => r .Post() @@ -550,7 +554,7 @@ await SendRequestAsync(r => r [Fact] public async Task LoginNoSuchUserAsync() { - _authOptions.EnableActiveDirectoryAuth = false; + _authOptions!.EnableActiveDirectoryAuth = false; const string email = "test8@exceptionless.io"; const string password = "Test8 password"; @@ -565,7 +569,7 @@ public async Task LoginNoSuchUserAsync() }; user.MarkEmailAddressVerified(); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); await SendRequestAsync(r => r .Post() @@ -582,7 +586,7 @@ await SendRequestAsync(r => r [Fact] public async Task LoginValidExistingActiveDirectoryAsync() { - _authOptions.EnableActiveDirectoryAuth = true; + _authOptions!.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); @@ -593,7 +597,7 @@ public async Task LoginValidExistingActiveDirectoryAsync() }; user.MarkEmailAddressVerified(); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -613,7 +617,7 @@ public async Task LoginValidExistingActiveDirectoryAsync() [Fact] public Task LoginValidNonExistentActiveDirectoryAsync() { - _authOptions.EnableActiveDirectoryAuth = true; + _authOptions!.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); @@ -633,7 +637,7 @@ public Task LoginValidNonExistentActiveDirectoryAsync() [Fact] public async Task LoginInvalidNonExistentActiveDirectoryAsync() { - _authOptions.EnableActiveDirectoryAuth = true; + _authOptions!.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); @@ -649,14 +653,14 @@ await SendRequestAsync(r => r ); // Verify that a user account was not added - var user = await _userRepository.GetByEmailAddressAsync($"{email}.au"); + var user = await _userRepository!.GetByEmailAddressAsync($"{email}.au"); Assert.Null(user); } [Fact] public async Task LoginInvalidExistingActiveDirectoryAsync() { - _authOptions.EnableActiveDirectoryAuth = true; + _authOptions!.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); @@ -667,7 +671,7 @@ public async Task LoginInvalidExistingActiveDirectoryAsync() }; user.MarkEmailAddressVerified(); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); await SendRequestAsync(r => r .Post() @@ -684,7 +688,7 @@ await SendRequestAsync(r => r [Fact] public async Task LoginInvalidExistingActiveDirectoryAccountUsingUserNameLoginAsync() { - _authOptions.EnableActiveDirectoryAuth = true; + _authOptions!.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); @@ -695,7 +699,7 @@ public async Task LoginInvalidExistingActiveDirectoryAccountUsingUserNameLoginAs }; user.MarkEmailAddressVerified(); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); await SendRequestAsync(r => r .Post() @@ -727,7 +731,7 @@ public async Task CanChangePasswordAsync() }; user.MarkEmailAddressVerified(); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -743,7 +747,7 @@ public async Task CanChangePasswordAsync() Assert.NotNull(result); Assert.NotEmpty(result.Token); - var token = await _tokenRepository.GetByIdAsync(result.Token); + var token = await _tokenRepository!.GetByIdAsync(result.Token); Assert.NotNull(token); var actualUser = await _userRepository.GetByIdAsync(token.UserId); @@ -788,7 +792,7 @@ public async Task ChangePasswordShouldFailWithCurrentPasswordAsync() }; user.MarkEmailAddressVerified(); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -804,7 +808,7 @@ public async Task ChangePasswordShouldFailWithCurrentPasswordAsync() Assert.NotNull(result); Assert.NotEmpty(result.Token); - var token = await _tokenRepository.GetByIdAsync(result.Token); + var token = await _tokenRepository!.GetByIdAsync(result.Token); Assert.NotNull(token); var actualUser = await _userRepository.GetByIdAsync(token.UserId); @@ -852,7 +856,7 @@ public async Task CanResetPasswordAsync() Assert.NotNull(user.PasswordResetToken); Assert.True(user.PasswordResetTokenExpiration.IsAfter(TimeProvider.GetUtcNow().UtcDateTime)); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -868,7 +872,7 @@ public async Task CanResetPasswordAsync() Assert.NotNull(result); Assert.NotEmpty(result.Token); - var token = await _tokenRepository.GetByIdAsync(result.Token); + var token = await _tokenRepository!.GetByIdAsync(result.Token); Assert.NotNull(token); var actualUser = await _userRepository.GetByIdAsync(token.UserId); @@ -913,7 +917,7 @@ public async Task ResetPasswordShouldFailWithCurrentPasswordAsync() Assert.NotNull(user.PasswordResetToken); Assert.True(user.PasswordResetTokenExpiration.IsAfter(TimeProvider.GetUtcNow().UtcDateTime)); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -929,7 +933,7 @@ public async Task ResetPasswordShouldFailWithCurrentPasswordAsync() Assert.NotNull(result); Assert.NotEmpty(result.Token); - var token = await _tokenRepository.GetByIdAsync(result.Token); + var token = await _tokenRepository!.GetByIdAsync(result.Token); Assert.NotNull(token); var actualUser = await _userRepository.GetByIdAsync(token.UserId); @@ -973,7 +977,7 @@ public async Task CanLogoutUserAsync() }; user.MarkEmailAddressVerified(); - await _userRepository.AddAsync(user); + await _userRepository!.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -989,7 +993,7 @@ public async Task CanLogoutUserAsync() Assert.NotNull(result); // Verify that the token is valid - var token = await _tokenRepository.GetByIdAsync(result.Token); + var token = await _tokenRepository!.GetByIdAsync(result.Token); Assert.Equal(TokenType.Authentication, token.Type); Assert.False(token.IsDisabled); Assert.False(token.IsSuspended); @@ -1007,7 +1011,7 @@ await SendRequestAsync(r => r [Fact] public async Task CanLogoutUserAccessTokenAsync() { - var token = await _tokenRepository.GetByIdAsync(TestConstants.UserApiKey); + var token = await _tokenRepository!.GetByIdAsync(TestConstants.UserApiKey); Assert.NotNull(token); Assert.Equal(TokenType.Access, token.Type); Assert.False(token.IsDisabled); @@ -1028,7 +1032,7 @@ await SendRequestAsync(r => r [Fact] public async Task CanLogoutClientAccessTokenAsync() { - var token = await _tokenRepository.GetByIdAsync(TestConstants.ApiKey); + var token = await _tokenRepository!.GetByIdAsync(TestConstants.ApiKey); Assert.NotNull(token); Assert.Equal(TokenType.Access, token.Type); Assert.False(token.IsDisabled); diff --git a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj index f5e6b679d3..e18e4f4f01 100644 --- a/tests/Exceptionless.Tests/Exceptionless.Tests.csproj +++ b/tests/Exceptionless.Tests/Exceptionless.Tests.csproj @@ -15,7 +15,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/Exceptionless.Tests/IntegrationTestsBase.cs b/tests/Exceptionless.Tests/IntegrationTestsBase.cs index af6a429cdb..70e9888e84 100644 --- a/tests/Exceptionless.Tests/IntegrationTestsBase.cs +++ b/tests/Exceptionless.Tests/IntegrationTestsBase.cs @@ -35,13 +35,12 @@ public abstract class IntegrationTestsBase : TestWithLoggingBase, Xunit.IAsyncLi { private static bool _indexesHaveBeenConfigured = false; private static readonly SemaphoreSlim _semaphoreSlim = new(1, 1); - private ExceptionlessElasticConfiguration? _configuration; - private ProxyTimeProvider? _timeProvider; + private readonly ExceptionlessElasticConfiguration _configuration; + protected readonly TestServer _server; + private readonly ProxyTimeProvider _timeProvider; protected readonly IList _disposables = new List(); - protected readonly AspireWebHostFactory _hostFixture; - protected TestServer? _server; - public IntegrationTestsBase(ITestOutputHelper output, AspireWebHostFactory hostFixture) : base(output) + public IntegrationTestsBase(ITestOutputHelper output, AspireWebHostFactory factory) : base(output) { Log.DefaultMinimumLevel = LogLevel.Information; Log.SetLogLevel(LogLevel.Warning); @@ -49,26 +48,12 @@ public IntegrationTestsBase(ITestOutputHelper output, AspireWebHostFactory hostF Log.SetLogLevel(LogLevel.Warning); Log.SetLogLevel("StartupActions", LogLevel.Warning); Log.SetLogLevel(LogLevel.Warning); - _hostFixture = hostFixture; - } - - public virtual async Task InitializeAsync() - { - //await _hostFixture.StartAsync(); - var configuredFactory = _hostFixture.Factories.Count > 0 ? _hostFixture.Factories[0] : null; + var configuredFactory = factory.Factories.Count > 0 ? factory.Factories[0] : null; if (configuredFactory is null) { - configuredFactory = _hostFixture.WithWebHostBuilder(builder => + configuredFactory = factory.WithWebHostBuilder(builder => { - builder.ConfigureAppConfiguration(c => - { - c.AddInMemoryCollection(new Dictionary - { - { "ConnectionStrings:Elasticsearch", _hostFixture.ElasticsearchConnectionString }, - { "ConnectionStrings:Redis", _hostFixture.RedisConnectionString } - }); - }); builder.ConfigureTestServices(RegisterServices); // happens after normal container configure and overrides services }); } @@ -88,21 +73,22 @@ public virtual async Task InitializeAsync() throw new InvalidOperationException("TimeProvider must be of type ProxyTimeProvider"); _disposables.Add(new DisposableAction(() => _timeProvider.Restore())); + } + public virtual async Task InitializeAsync() + { Log.SetLogLevel("Microsoft.AspNetCore.Hosting.Internal.WebHost", LogLevel.Warning); Log.SetLogLevel("Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService", LogLevel.None); - await _server.WaitForReadyAsync(); - Log.SetLogLevel("Microsoft.AspNetCore.Hosting.Internal.WebHost", LogLevel.Information); Log.SetLogLevel("Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService", LogLevel.Information); await ResetDataAsync(); } - protected ProxyTimeProvider TimeProvider => _timeProvider!; + protected ProxyTimeProvider TimeProvider => _timeProvider; - private IServiceProvider ServiceProvider { get; set; } = new ServiceCollection().BuildServiceProvider(); + private IServiceProvider ServiceProvider { get; } protected TService GetService() where TService : notnull { @@ -128,14 +114,14 @@ protected virtual void RegisterServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - services.ReplaceSingleton(s => _server!.CreateHandler()); + services.ReplaceSingleton(s => _server.CreateHandler()); } public async Task<(List Stacks, List Events)> CreateDataAsync(Action dataBuilderFunc) { var eventBuilders = new List(); - var dataBuilder = new DataBuilder(eventBuilders, ServiceProvider, _timeProvider!); + var dataBuilder = new DataBuilder(eventBuilders, ServiceProvider, _timeProvider); dataBuilderFunc(dataBuilder); var eventRepository = GetService(); @@ -168,13 +154,13 @@ protected virtual async Task ResetDataAsync() await RefreshDataAsync(); if (!_indexesHaveBeenConfigured) { - await _configuration!.DeleteIndexesAsync(); + await _configuration.DeleteIndexesAsync(); await _configuration.ConfigureIndexesAsync(); _indexesHaveBeenConfigured = true; } else { - string indexes = String.Join(',', _configuration!.Indexes.Select(i => i.Name)); + string indexes = String.Join(',', _configuration.Indexes.Select(i => i.Name)); await _configuration.Client.DeleteByQueryAsync(new DeleteByQueryRequest(indexes) { Query = new MatchAllQuery(), @@ -214,7 +200,7 @@ protected async Task RefreshDataAsync(Indices? indices = null) protected HttpClient CreateHttpClient() { - var client = _server!.CreateClient(); + var client = _server.CreateClient(); client.BaseAddress = new Uri(_server.BaseAddress + "api/v2/", UriKind.Absolute); return client; } diff --git a/tests/Exceptionless.Tests/TestWithServices.cs b/tests/Exceptionless.Tests/TestWithServices.cs index 8d6c7ca715..29c4a2dc26 100644 --- a/tests/Exceptionless.Tests/TestWithServices.cs +++ b/tests/Exceptionless.Tests/TestWithServices.cs @@ -22,7 +22,7 @@ public class TestWithServices : TestWithLoggingBase, IAsyncLifetime { private readonly IServiceProvider _container; private readonly ProxyTimeProvider _timeProvider; - private static bool _startupActionsRun; + //private static bool _startupActionsRun; public TestWithServices(ITestOutputHelper output) : base(output) { @@ -39,8 +39,11 @@ public TestWithServices(ITestOutputHelper output) : base(output) throw new InvalidOperationException("TimeProvider must be of type ProxyTimeProvider"); } - public virtual async Task InitializeAsync() + public virtual Task InitializeAsync() { + return Task.CompletedTask; + + /* if (_startupActionsRun) return; @@ -48,7 +51,7 @@ public virtual async Task InitializeAsync() if (!result.Success) throw new ApplicationException($"Startup action \"{result.FailedActionName}\" failed"); - _startupActionsRun = true; + _startupActionsRun = true;*/ } protected ProxyTimeProvider TimeProvider => _timeProvider; From ba933e5db3a06373b6a4f93795839ae19af1ef67 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 17 Dec 2024 15:09:25 -0600 Subject: [PATCH 11/24] Revert some changes. Fix linting. --- Dockerfile | 1 - src/Exceptionless.Web/ClientApp/vite.config.ts | 2 +- .../{AspireWebHostFactory.cs => AppWebHostFactory.cs} | 4 ++-- tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs | 2 +- tests/Exceptionless.Tests/Controllers/EventControllerTests.cs | 2 +- .../Exceptionless.Tests/Controllers/ProjectControllerTests.cs | 2 +- tests/Exceptionless.Tests/Controllers/StackControllerTests.cs | 2 +- .../Exceptionless.Tests/Controllers/StatusControllerTests.cs | 2 +- tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs | 2 +- .../Exceptionless.Tests/Controllers/WebHookControllerTests.cs | 2 +- tests/Exceptionless.Tests/IntegrationTestsBase.cs | 4 ++-- tests/Exceptionless.Tests/Jobs/CleanupDataJobTests.cs | 2 +- .../Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs | 2 +- tests/Exceptionless.Tests/Jobs/EventPostJobTests.cs | 2 +- .../Migrations/FixDuplicateStacksMigrationTests.cs | 2 +- .../Migrations/UpdateEventUsageMigrationTests.cs | 2 +- tests/Exceptionless.Tests/Pipeline/EventPipelineTests.cs | 2 +- .../Exceptionless.Tests/Repositories/EventRepositoryTests.cs | 2 +- .../Repositories/OrganizationRepositoryTests.cs | 2 +- .../Repositories/ProjectRepositoryTests.cs | 2 +- .../Exceptionless.Tests/Repositories/StackRepositoryTests.cs | 2 +- .../Exceptionless.Tests/Repositories/TokenRepositoryTests.cs | 2 +- .../Repositories/WebHookRepositoryTests.cs | 2 +- tests/Exceptionless.Tests/Search/EventIndexTests.cs | 2 +- .../Exceptionless.Tests/Search/EventStackFilterQueryTests.cs | 2 +- tests/Exceptionless.Tests/Search/EventStackFilterTests.cs | 2 +- tests/Exceptionless.Tests/Search/MoreEventIndexTests.cs | 2 +- tests/Exceptionless.Tests/Search/StackIndexTests.cs | 2 +- tests/Exceptionless.Tests/Services/StackServiceTests.cs | 2 +- tests/Exceptionless.Tests/Services/UsageServiceTests.cs | 2 +- tests/Exceptionless.Tests/Stats/AggregationTests.cs | 2 +- 31 files changed, 32 insertions(+), 33 deletions(-) rename tests/Exceptionless.Tests/{AspireWebHostFactory.cs => AppWebHostFactory.cs} (91%) diff --git a/Dockerfile b/Dockerfile index cae57a7fe1..b9d1209987 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,6 @@ COPY ./*.sln ./NuGet.Config ./ COPY ./src/*.props ./src/ COPY ./tests/*.props ./tests/ COPY ./build/packages/* ./build/packages/ -COPY ./docker/docker-compose.dcproj ./docker/ # Copy the main source project files COPY src/*/*.csproj ./ diff --git a/src/Exceptionless.Web/ClientApp/vite.config.ts b/src/Exceptionless.Web/ClientApp/vite.config.ts index 566e7c21c8..ade7bab6fb 100644 --- a/src/Exceptionless.Web/ClientApp/vite.config.ts +++ b/src/Exceptionless.Web/ClientApp/vite.config.ts @@ -78,7 +78,7 @@ function getAspNetConfig() { // get current aspnetcore port / url const aspnetHttpsPort = process.env.ASPNETCORE_HTTPS_PORT; - const aspnetUrls = process.env.ASPNETCORE_URLS ?? process.env.services__Api__0;; + const aspnetUrls = process.env.ASPNETCORE_URLS ?? process.env.services__Api__0; const serverPort = 5173; const hmrRemoteHost = codespaceName ? `${codespaceName}-${serverPort}.${codespaceDomain}` : 'localhost'; diff --git a/tests/Exceptionless.Tests/AspireWebHostFactory.cs b/tests/Exceptionless.Tests/AppWebHostFactory.cs similarity index 91% rename from tests/Exceptionless.Tests/AspireWebHostFactory.cs rename to tests/Exceptionless.Tests/AppWebHostFactory.cs index f7778df108..26f23eb67e 100644 --- a/tests/Exceptionless.Tests/AspireWebHostFactory.cs +++ b/tests/Exceptionless.Tests/AppWebHostFactory.cs @@ -8,7 +8,7 @@ namespace Exceptionless.Tests; -public class AspireWebHostFactory : WebApplicationFactory, IAsyncLifetime +public class AppWebHostFactory : WebApplicationFactory, IAsyncLifetime { private DistributedApplication? _app; @@ -23,7 +23,7 @@ public Task InitializeAsync() builder.Configuration["DcpPublisher:RandomizePorts"] = "false"; builder.AddElasticsearch("Elasticsearch", port: 9200) - .WithContainerName("Exceptionless-Elasticsearch") + .WithContainerName("Exceptionless-Elasticsearch-Test") .WithImageTag("8.17.0") .WithLifetime(ContainerLifetime.Persistent); diff --git a/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs b/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs index 92336f058b..e5d2b15373 100644 --- a/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs @@ -27,7 +27,7 @@ public class AuthControllerTests : IntegrationTestsBase private IOrganizationRepository? _organizationRepository; private ITokenRepository? _tokenRepository; - public AuthControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public AuthControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { } diff --git a/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs b/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs index 015bf80187..3a19ccada2 100644 --- a/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs @@ -43,7 +43,7 @@ public class EventControllerTests : IntegrationTestsBase private readonly IQueue _eventUserDescriptionQueue; private readonly UserData _userData; - public EventControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public EventControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _organizationRepository = GetService(); _stackData = GetService(); diff --git a/tests/Exceptionless.Tests/Controllers/ProjectControllerTests.cs b/tests/Exceptionless.Tests/Controllers/ProjectControllerTests.cs index e3c614acdf..c3010d96a6 100644 --- a/tests/Exceptionless.Tests/Controllers/ProjectControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/ProjectControllerTests.cs @@ -13,7 +13,7 @@ namespace Exceptionless.Tests.Controllers; public sealed class ProjectControllerTests : IntegrationTestsBase { - public ProjectControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public ProjectControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { } diff --git a/tests/Exceptionless.Tests/Controllers/StackControllerTests.cs b/tests/Exceptionless.Tests/Controllers/StackControllerTests.cs index 37d2b3e412..1e3d431866 100644 --- a/tests/Exceptionless.Tests/Controllers/StackControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/StackControllerTests.cs @@ -21,7 +21,7 @@ public class StackControllerTests : IntegrationTestsBase private readonly IQueue _eventQueue; private readonly IQueue _workItemQueue; - public StackControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public StackControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _stackRepository = GetService(); _eventRepository = GetService(); diff --git a/tests/Exceptionless.Tests/Controllers/StatusControllerTests.cs b/tests/Exceptionless.Tests/Controllers/StatusControllerTests.cs index da0a288839..5360717ee5 100644 --- a/tests/Exceptionless.Tests/Controllers/StatusControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/StatusControllerTests.cs @@ -10,7 +10,7 @@ namespace Exceptionless.Tests.Controllers; public class StatusControllerTests : IntegrationTestsBase { - public StatusControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public StatusControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { } diff --git a/tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs b/tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs index b915f3e409..2c5b46ea6a 100644 --- a/tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs @@ -14,7 +14,7 @@ namespace Exceptionless.Tests.Controllers; public sealed class TokenControllerTests : IntegrationTestsBase { - public TokenControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { } + public TokenControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { } protected override async Task ResetDataAsync() { diff --git a/tests/Exceptionless.Tests/Controllers/WebHookControllerTests.cs b/tests/Exceptionless.Tests/Controllers/WebHookControllerTests.cs index bcea584c1d..62e794d271 100644 --- a/tests/Exceptionless.Tests/Controllers/WebHookControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/WebHookControllerTests.cs @@ -10,7 +10,7 @@ namespace Exceptionless.Tests.Controllers; public sealed class WebHookControllerTests : IntegrationTestsBase { - public WebHookControllerTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) { } + public WebHookControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { } protected override async Task ResetDataAsync() { diff --git a/tests/Exceptionless.Tests/IntegrationTestsBase.cs b/tests/Exceptionless.Tests/IntegrationTestsBase.cs index 70e9888e84..1705fa284c 100644 --- a/tests/Exceptionless.Tests/IntegrationTestsBase.cs +++ b/tests/Exceptionless.Tests/IntegrationTestsBase.cs @@ -31,7 +31,7 @@ namespace Exceptionless.Tests; -public abstract class IntegrationTestsBase : TestWithLoggingBase, Xunit.IAsyncLifetime, IClassFixture +public abstract class IntegrationTestsBase : TestWithLoggingBase, Xunit.IAsyncLifetime, IClassFixture { private static bool _indexesHaveBeenConfigured = false; private static readonly SemaphoreSlim _semaphoreSlim = new(1, 1); @@ -40,7 +40,7 @@ public abstract class IntegrationTestsBase : TestWithLoggingBase, Xunit.IAsyncLi private readonly ProxyTimeProvider _timeProvider; protected readonly IList _disposables = new List(); - public IntegrationTestsBase(ITestOutputHelper output, AspireWebHostFactory factory) : base(output) + public IntegrationTestsBase(ITestOutputHelper output, AppWebHostFactory factory) : base(output) { Log.DefaultMinimumLevel = LogLevel.Information; Log.SetLogLevel(LogLevel.Warning); diff --git a/tests/Exceptionless.Tests/Jobs/CleanupDataJobTests.cs b/tests/Exceptionless.Tests/Jobs/CleanupDataJobTests.cs index 1d649a045d..44e262e5cd 100644 --- a/tests/Exceptionless.Tests/Jobs/CleanupDataJobTests.cs +++ b/tests/Exceptionless.Tests/Jobs/CleanupDataJobTests.cs @@ -27,7 +27,7 @@ public class CleanupDataJobTests : IntegrationTestsBase private readonly BillingManager _billingManager; private readonly BillingPlans _plans; - public CleanupDataJobTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public CleanupDataJobTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _job = GetService(); _organizationData = GetService(); diff --git a/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs b/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs index dcd60979a6..3fc9dd9b90 100644 --- a/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs +++ b/tests/Exceptionless.Tests/Jobs/CloseInactiveSessionsJobTests.cs @@ -31,7 +31,7 @@ public class CloseInactiveSessionsJobTests : IntegrationTestsBase private readonly BillingManager _billingManager; private readonly BillingPlans _plans; - public CloseInactiveSessionsJobTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public CloseInactiveSessionsJobTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _job = GetService(); _cache = GetService(); diff --git a/tests/Exceptionless.Tests/Jobs/EventPostJobTests.cs b/tests/Exceptionless.Tests/Jobs/EventPostJobTests.cs index 8713a8bd60..6aae0f1c0a 100644 --- a/tests/Exceptionless.Tests/Jobs/EventPostJobTests.cs +++ b/tests/Exceptionless.Tests/Jobs/EventPostJobTests.cs @@ -39,7 +39,7 @@ public class EventPostJobTests : IntegrationTestsBase private readonly BillingPlans _plans; private readonly AppOptions _options; - public EventPostJobTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public EventPostJobTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _job = GetService(); _eventQueue = GetService>(); diff --git a/tests/Exceptionless.Tests/Migrations/FixDuplicateStacksMigrationTests.cs b/tests/Exceptionless.Tests/Migrations/FixDuplicateStacksMigrationTests.cs index 23aed60bf8..226264e83b 100644 --- a/tests/Exceptionless.Tests/Migrations/FixDuplicateStacksMigrationTests.cs +++ b/tests/Exceptionless.Tests/Migrations/FixDuplicateStacksMigrationTests.cs @@ -21,7 +21,7 @@ public class FixDuplicateStacksMigrationTests : IntegrationTestsBase private readonly EventData _eventData; private readonly IEventRepository _eventRepository; - public FixDuplicateStacksMigrationTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public FixDuplicateStacksMigrationTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _stackData = GetService(); _stackRepository = GetService(); diff --git a/tests/Exceptionless.Tests/Migrations/UpdateEventUsageMigrationTests.cs b/tests/Exceptionless.Tests/Migrations/UpdateEventUsageMigrationTests.cs index f19a33eef0..70c0c99aa8 100644 --- a/tests/Exceptionless.Tests/Migrations/UpdateEventUsageMigrationTests.cs +++ b/tests/Exceptionless.Tests/Migrations/UpdateEventUsageMigrationTests.cs @@ -24,7 +24,7 @@ public class UpdateEventUsageMigrationTests : IntegrationTestsBase private readonly EventData _eventData; private readonly IEventRepository _eventRepository; - public UpdateEventUsageMigrationTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public UpdateEventUsageMigrationTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _organizationData = GetService(); _organizationRepository = GetService(); diff --git a/tests/Exceptionless.Tests/Pipeline/EventPipelineTests.cs b/tests/Exceptionless.Tests/Pipeline/EventPipelineTests.cs index a3fc14854a..b359cfa92e 100644 --- a/tests/Exceptionless.Tests/Pipeline/EventPipelineTests.cs +++ b/tests/Exceptionless.Tests/Pipeline/EventPipelineTests.cs @@ -40,7 +40,7 @@ public sealed class EventPipelineTests : IntegrationTestsBase private readonly BillingManager _billingManager; private readonly BillingPlans _plans; - public EventPipelineTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public EventPipelineTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _eventData = GetService(); _eventRepository = GetService(); diff --git a/tests/Exceptionless.Tests/Repositories/EventRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/EventRepositoryTests.cs index 842b9bf4db..79e2bd8066 100644 --- a/tests/Exceptionless.Tests/Repositories/EventRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/EventRepositoryTests.cs @@ -22,7 +22,7 @@ public sealed class EventRepositoryTests : IntegrationTestsBase private readonly StackData _stackData; private readonly IStackRepository _stackRepository; - public EventRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public EventRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _randomEventGenerator = GetService(); _eventData = GetService(); diff --git a/tests/Exceptionless.Tests/Repositories/OrganizationRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/OrganizationRepositoryTests.cs index 5a91c156d5..7b92db1318 100644 --- a/tests/Exceptionless.Tests/Repositories/OrganizationRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/OrganizationRepositoryTests.cs @@ -15,7 +15,7 @@ public sealed class OrganizationRepositoryTests : IntegrationTestsBase private readonly IOrganizationRepository _repository; private readonly BillingPlans _plans; - public OrganizationRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public OrganizationRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { Log.SetLogLevel(LogLevel.Trace); _cache = GetService() as InMemoryCacheClient ?? throw new InvalidOperationException(); diff --git a/tests/Exceptionless.Tests/Repositories/ProjectRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/ProjectRepositoryTests.cs index f11ec8fdfb..6d08e2d171 100644 --- a/tests/Exceptionless.Tests/Repositories/ProjectRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/ProjectRepositoryTests.cs @@ -19,7 +19,7 @@ public sealed class ProjectRepositoryTests : IntegrationTestsBase private readonly ProjectData _projectData; private readonly IProjectRepository _repository; - public ProjectRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public ProjectRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _organizationData = GetService(); _projectData = GetService(); diff --git a/tests/Exceptionless.Tests/Repositories/StackRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/StackRepositoryTests.cs index f4c7961151..43aee476cf 100644 --- a/tests/Exceptionless.Tests/Repositories/StackRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/StackRepositoryTests.cs @@ -19,7 +19,7 @@ public sealed class StackRepositoryTests : IntegrationTestsBase private readonly StackData _stackData; private readonly IStackRepository _repository; - public StackRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public StackRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _cache = GetService() as InMemoryCacheClient ?? throw new InvalidOperationException(); _stackData = GetService(); diff --git a/tests/Exceptionless.Tests/Repositories/TokenRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/TokenRepositoryTests.cs index 3912bdde0a..793418be24 100644 --- a/tests/Exceptionless.Tests/Repositories/TokenRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/TokenRepositoryTests.cs @@ -13,7 +13,7 @@ public sealed class TokenRepositoryTests : IntegrationTestsBase { private readonly ITokenRepository _repository; - public TokenRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public TokenRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _repository = GetService(); } diff --git a/tests/Exceptionless.Tests/Repositories/WebHookRepositoryTests.cs b/tests/Exceptionless.Tests/Repositories/WebHookRepositoryTests.cs index ac1e90c916..7918fe2a0f 100644 --- a/tests/Exceptionless.Tests/Repositories/WebHookRepositoryTests.cs +++ b/tests/Exceptionless.Tests/Repositories/WebHookRepositoryTests.cs @@ -11,7 +11,7 @@ public sealed class WebHookRepositoryTests : IntegrationTestsBase { private readonly IWebHookRepository _repository; - public WebHookRepositoryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public WebHookRepositoryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _repository = GetService(); } diff --git a/tests/Exceptionless.Tests/Search/EventIndexTests.cs b/tests/Exceptionless.Tests/Search/EventIndexTests.cs index 6656cb2b15..5a66fab4d9 100644 --- a/tests/Exceptionless.Tests/Search/EventIndexTests.cs +++ b/tests/Exceptionless.Tests/Search/EventIndexTests.cs @@ -16,7 +16,7 @@ public sealed class EventIndexTests : IntegrationTestsBase private readonly IEventRepository _repository; private readonly PersistentEventQueryValidator _validator; - public EventIndexTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public EventIndexTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { TimeProvider.SetUtcNow(new DateTime(2015, 2, 13, 0, 0, 0, DateTimeKind.Utc)); _eventData = GetService(); diff --git a/tests/Exceptionless.Tests/Search/EventStackFilterQueryTests.cs b/tests/Exceptionless.Tests/Search/EventStackFilterQueryTests.cs index ead126d6b3..2f7b9e8305 100644 --- a/tests/Exceptionless.Tests/Search/EventStackFilterQueryTests.cs +++ b/tests/Exceptionless.Tests/Search/EventStackFilterQueryTests.cs @@ -16,7 +16,7 @@ public class EventStackFilterQueryTests : IntegrationTestsBase private readonly IEventRepository _eventRepository; private static bool _isTestDataGenerated; - public EventStackFilterQueryTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public EventStackFilterQueryTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _stackRepository = GetService(); _eventRepository = GetService(); diff --git a/tests/Exceptionless.Tests/Search/EventStackFilterTests.cs b/tests/Exceptionless.Tests/Search/EventStackFilterTests.cs index a53c1c7698..ceb0a311f3 100644 --- a/tests/Exceptionless.Tests/Search/EventStackFilterTests.cs +++ b/tests/Exceptionless.Tests/Search/EventStackFilterTests.cs @@ -15,7 +15,7 @@ public sealed class EventStackFilterTests : IntegrationTestsBase private readonly EventData _eventData; private readonly IEventRepository _eventRepository; - public EventStackFilterTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public EventStackFilterTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { TimeProvider.SetUtcNow(new DateTime(2015, 2, 13, 0, 0, 0, DateTimeKind.Utc)); _stackData = GetService(); diff --git a/tests/Exceptionless.Tests/Search/MoreEventIndexTests.cs b/tests/Exceptionless.Tests/Search/MoreEventIndexTests.cs index 3b8f630467..0c1772f176 100644 --- a/tests/Exceptionless.Tests/Search/MoreEventIndexTests.cs +++ b/tests/Exceptionless.Tests/Search/MoreEventIndexTests.cs @@ -15,7 +15,7 @@ public sealed class MoreEventIndexTests : IntegrationTestsBase private readonly IEventRepository _repository; private readonly PersistentEventQueryValidator _validator; - public MoreEventIndexTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public MoreEventIndexTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { TimeProvider.SetUtcNow(new DateTime(2015, 2, 13, 0, 0, 0, DateTimeKind.Utc)); _repository = GetService(); diff --git a/tests/Exceptionless.Tests/Search/StackIndexTests.cs b/tests/Exceptionless.Tests/Search/StackIndexTests.cs index 6f3e74febe..b955960c00 100644 --- a/tests/Exceptionless.Tests/Search/StackIndexTests.cs +++ b/tests/Exceptionless.Tests/Search/StackIndexTests.cs @@ -13,7 +13,7 @@ public sealed class StackIndexTests : IntegrationTestsBase private readonly StackData _stackData; private readonly IStackRepository _repository; - public StackIndexTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public StackIndexTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _stackData = GetService(); _repository = GetService(); diff --git a/tests/Exceptionless.Tests/Services/StackServiceTests.cs b/tests/Exceptionless.Tests/Services/StackServiceTests.cs index bb87dd845e..c8a2447f02 100644 --- a/tests/Exceptionless.Tests/Services/StackServiceTests.cs +++ b/tests/Exceptionless.Tests/Services/StackServiceTests.cs @@ -17,7 +17,7 @@ public class StackServiceTests : IntegrationTestsBase private readonly StackService _stackService; private readonly IStackRepository _stackRepository; - public StackServiceTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public StackServiceTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { Log.SetLogLevel(LogLevel.Trace); _cache = GetService(); diff --git a/tests/Exceptionless.Tests/Services/UsageServiceTests.cs b/tests/Exceptionless.Tests/Services/UsageServiceTests.cs index 1a80de50f8..80dab5cb81 100644 --- a/tests/Exceptionless.Tests/Services/UsageServiceTests.cs +++ b/tests/Exceptionless.Tests/Services/UsageServiceTests.cs @@ -21,7 +21,7 @@ public sealed class UsageServiceTests : IntegrationTestsBase private readonly UsageService _usageService; private readonly BillingPlans _plans; - public UsageServiceTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public UsageServiceTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { TimeProvider.SetUtcNow(new DateTime(2015, 2, 13, 0, 0, 0, DateTimeKind.Utc)); Log.SetLogLevel(LogLevel.Information); diff --git a/tests/Exceptionless.Tests/Stats/AggregationTests.cs b/tests/Exceptionless.Tests/Stats/AggregationTests.cs index 77f1971ea7..459804b710 100644 --- a/tests/Exceptionless.Tests/Stats/AggregationTests.cs +++ b/tests/Exceptionless.Tests/Stats/AggregationTests.cs @@ -28,7 +28,7 @@ public sealed class AggregationTests : IntegrationTestsBase private readonly BillingManager _billingManager; private readonly BillingPlans _plans; - public AggregationTests(ITestOutputHelper output, AspireWebHostFactory factory) : base(output, factory) + public AggregationTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { _pipeline = GetService(); _eventData = GetService(); From a7f3c4629252afe2fb5d73d1fa2ab5a78726ab26 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 17 Dec 2024 15:12:48 -0600 Subject: [PATCH 12/24] Revert more changes --- .../Controllers/AuthControllerTests.cs | 108 +++++++++--------- .../Controllers/EventControllerTests.cs | 6 +- 2 files changed, 55 insertions(+), 59 deletions(-) diff --git a/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs b/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs index e5d2b15373..158c804757 100644 --- a/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/AuthControllerTests.cs @@ -22,21 +22,17 @@ namespace Exceptionless.Tests.Controllers; public class AuthControllerTests : IntegrationTestsBase { - private AuthOptions? _authOptions; - private IUserRepository? _userRepository; - private IOrganizationRepository? _organizationRepository; - private ITokenRepository? _tokenRepository; + private readonly AuthOptions _authOptions; + private readonly IUserRepository _userRepository; + private readonly IOrganizationRepository _organizationRepository; + private readonly ITokenRepository _tokenRepository; public AuthControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory) { - } - - public override async Task InitializeAsync() - { - await base.InitializeAsync(); _authOptions = GetService(); _authOptions.EnableAccountCreation = true; _authOptions.EnableActiveDirectoryAuth = false; + _organizationRepository = GetService(); _userRepository = GetService(); _tokenRepository = GetService(); @@ -75,7 +71,7 @@ public async Task CannotSignupWithoutPassword() [InlineData(false, "test1@exceptionless.io", "Password1$")] public Task CannotSignupWhenAccountCreationDisabledWithNoTokenAsync(bool enableAdAuth, string email, string password) { - _authOptions!.EnableAccountCreation = false; + _authOptions.EnableAccountCreation = false; _authOptions.EnableActiveDirectoryAuth = enableAdAuth; if (enableAdAuth && email == TestDomainLoginProvider.ValidUsername) @@ -104,7 +100,7 @@ public Task CannotSignupWhenAccountCreationDisabledWithNoTokenAsync(bool enableA [InlineData(false, "test2@exceptionless.io", "Password1$")] public Task CannotSignupWhenAccountCreationDisabledWithInvalidTokenAsync(bool enableAdAuth, string email, string password) { - _authOptions!.EnableAccountCreation = false; + _authOptions.EnableAccountCreation = false; _authOptions.EnableActiveDirectoryAuth = enableAdAuth; if (enableAdAuth && email == TestDomainLoginProvider.ValidUsername) @@ -132,7 +128,7 @@ public Task CannotSignupWhenAccountCreationDisabledWithInvalidTokenAsync(bool en [InlineData(false, "test3@exceptionless.io", "Password1$")] public async Task CanSignupWhenAccountCreationDisabledWithValidTokenAsync(bool enableAdAuth, string email, string password) { - _authOptions!.EnableAccountCreation = false; + _authOptions.EnableAccountCreation = false; _authOptions.EnableActiveDirectoryAuth = enableAdAuth; if (enableAdAuth && email == TestDomainLoginProvider.ValidUsername) @@ -141,7 +137,7 @@ public async Task CanSignupWhenAccountCreationDisabledWithValidTokenAsync(bool e email = provider.GetEmailAddressFromUsername(email); } - var results = await _organizationRepository!.GetAllAsync(); + var results = await _organizationRepository.GetAllAsync(); var organization = results.Documents.First(); var invite = new Invite @@ -170,7 +166,7 @@ public async Task CanSignupWhenAccountCreationDisabledWithValidTokenAsync(bool e Assert.NotNull(result); Assert.False(String.IsNullOrEmpty(result.Token)); - var user = await _userRepository!.GetByEmailAddressAsync(email); + var user = await _userRepository.GetByEmailAddressAsync(email); Assert.NotNull(user); Assert.Equal("Test", user.FullName); Assert.Equal(email, user.EmailAddress); @@ -186,13 +182,13 @@ public async Task CanSignupWhenAccountCreationDisabledWithValidTokenAsync(bool e [Fact] public async Task CanSignupWhenAccountCreationDisabledWithValidTokenAndInvalidAdAccountAsync() { - _authOptions!.EnableAccountCreation = false; + _authOptions.EnableAccountCreation = false; _authOptions.EnableActiveDirectoryAuth = true; const string email = "test-user1@exceptionless.io"; const string password = "invalidAccount1"; - var organizations = await _organizationRepository!.GetAllAsync(); + var organizations = await _organizationRepository.GetAllAsync(); var organization = organizations.Documents.First(); var invite = new Invite { @@ -222,7 +218,7 @@ await SendRequestAsync(r => r [Fact] public async Task CanSignupWhenAccountCreationEnabledWithNoTokenAsync() { - _authOptions!.EnableAccountCreation = true; + _authOptions.EnableAccountCreation = true; const string email = "test4@exceptionless.io"; const string password = "Password1$"; @@ -243,7 +239,7 @@ public async Task CanSignupWhenAccountCreationEnabledWithNoTokenAsync() Assert.NotNull(result); Assert.False(String.IsNullOrEmpty(result.Token)); - var user = await _userRepository!.GetByEmailAddressAsync(email); + var user = await _userRepository.GetByEmailAddressAsync(email); Assert.NotNull(user); Assert.Equal("Test", user.FullName); Assert.Equal(email, user.EmailAddress); @@ -258,7 +254,7 @@ public async Task CanSignupWhenAccountCreationEnabledWithNoTokenAsync() [Fact] public async Task CanSignupWhenAccountCreationEnabledWithNoTokenAndValidAdAccountAsync() { - _authOptions!.EnableAccountCreation = true; + _authOptions.EnableAccountCreation = true; _authOptions.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); @@ -284,7 +280,7 @@ public async Task CanSignupWhenAccountCreationEnabledWithNoTokenAndValidAdAccoun [Fact] public Task CanSignupWhenAccountCreationEnabledWithNoTokenAndInvalidAdAccountAsync() { - _authOptions!.EnableAccountCreation = true; + _authOptions.EnableAccountCreation = true; _authOptions.EnableActiveDirectoryAuth = true; return SendRequestAsync(r => r @@ -304,9 +300,9 @@ public Task CanSignupWhenAccountCreationEnabledWithNoTokenAndInvalidAdAccountAsy [Fact] public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAsync() { - _authOptions!.EnableAccountCreation = true; + _authOptions.EnableAccountCreation = true; - var organizations = await _organizationRepository!.GetAllAsync(); + var organizations = await _organizationRepository.GetAllAsync(); var organization = organizations.Documents.First(); const string email = "test5@exceptionless.io"; const string name = "Test"; @@ -342,7 +338,7 @@ public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAsync() await RefreshDataAsync(); - var user = await _userRepository!.GetByEmailAddressAsync(email); + var user = await _userRepository.GetByEmailAddressAsync(email); Assert.NotNull(user); Assert.Equal("Test", user.FullName); Assert.NotEmpty(user.OrganizationIds); @@ -354,7 +350,7 @@ public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAsync() organization = await _organizationRepository.GetByIdAsync(organization.Id); Assert.Empty(organization.Invites); - var token = await _tokenRepository!.GetByIdAsync(result.Token); + var token = await _tokenRepository.GetByIdAsync(result.Token); Assert.NotNull(token); Assert.Equal(user.Id, token.UserId); Assert.Equal(TokenType.Authentication, token.Type); @@ -367,13 +363,13 @@ public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAsync() [Fact] public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAndValidAdAccountAsync() { - _authOptions!.EnableAccountCreation = true; + _authOptions.EnableAccountCreation = true; _authOptions.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); - var results = await _organizationRepository!.GetAllAsync(); + var results = await _organizationRepository.GetAllAsync(); var organization = results.Documents.First(); var invite = new Invite { @@ -405,11 +401,11 @@ public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAndValidAdAcc [Fact] public async Task CanSignupWhenAccountCreationEnabledWithValidTokenAndInvalidAdAccountAsync() { - _authOptions!.EnableAccountCreation = true; + _authOptions.EnableAccountCreation = true; _authOptions.EnableActiveDirectoryAuth = true; string email = "test-user4@exceptionless.io"; - var results = await _organizationRepository!.GetAllAsync(); + var results = await _organizationRepository.GetAllAsync(); var organization = results.Documents.First(); var invite = new Invite { @@ -452,7 +448,7 @@ public async Task SignupShouldFailWhenUsingExistingAccountWithNoPasswordOrInvali }; user.MarkEmailAddressVerified(); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); var problemDetails = await SendRequestAsAsync(r => r .Post() @@ -486,7 +482,7 @@ await SendRequestAsync(r => r [Fact] public async Task LoginValidAsync() { - _authOptions!.EnableActiveDirectoryAuth = false; + _authOptions.EnableActiveDirectoryAuth = false; const string email = "test6@exceptionless.io"; const string password = "Test6 password"; @@ -501,7 +497,7 @@ public async Task LoginValidAsync() }; user.MarkEmailAddressVerified(); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -521,7 +517,7 @@ public async Task LoginValidAsync() [Fact] public async Task LoginInvalidPasswordAsync() { - _authOptions!.EnableActiveDirectoryAuth = false; + _authOptions.EnableActiveDirectoryAuth = false; const string email = "test7@exceptionless.io"; const string password = "Test7 password"; @@ -537,7 +533,7 @@ public async Task LoginInvalidPasswordAsync() }; user.MarkEmailAddressVerified(); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); await SendRequestAsync(r => r .Post() @@ -554,7 +550,7 @@ await SendRequestAsync(r => r [Fact] public async Task LoginNoSuchUserAsync() { - _authOptions!.EnableActiveDirectoryAuth = false; + _authOptions.EnableActiveDirectoryAuth = false; const string email = "test8@exceptionless.io"; const string password = "Test8 password"; @@ -569,7 +565,7 @@ public async Task LoginNoSuchUserAsync() }; user.MarkEmailAddressVerified(); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); await SendRequestAsync(r => r .Post() @@ -586,7 +582,7 @@ await SendRequestAsync(r => r [Fact] public async Task LoginValidExistingActiveDirectoryAsync() { - _authOptions!.EnableActiveDirectoryAuth = true; + _authOptions.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); @@ -597,7 +593,7 @@ public async Task LoginValidExistingActiveDirectoryAsync() }; user.MarkEmailAddressVerified(); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -617,7 +613,7 @@ public async Task LoginValidExistingActiveDirectoryAsync() [Fact] public Task LoginValidNonExistentActiveDirectoryAsync() { - _authOptions!.EnableActiveDirectoryAuth = true; + _authOptions.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); @@ -637,7 +633,7 @@ public Task LoginValidNonExistentActiveDirectoryAsync() [Fact] public async Task LoginInvalidNonExistentActiveDirectoryAsync() { - _authOptions!.EnableActiveDirectoryAuth = true; + _authOptions.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); @@ -653,14 +649,14 @@ await SendRequestAsync(r => r ); // Verify that a user account was not added - var user = await _userRepository!.GetByEmailAddressAsync($"{email}.au"); + var user = await _userRepository.GetByEmailAddressAsync($"{email}.au"); Assert.Null(user); } [Fact] public async Task LoginInvalidExistingActiveDirectoryAsync() { - _authOptions!.EnableActiveDirectoryAuth = true; + _authOptions.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); @@ -671,7 +667,7 @@ public async Task LoginInvalidExistingActiveDirectoryAsync() }; user.MarkEmailAddressVerified(); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); await SendRequestAsync(r => r .Post() @@ -688,7 +684,7 @@ await SendRequestAsync(r => r [Fact] public async Task LoginInvalidExistingActiveDirectoryAccountUsingUserNameLoginAsync() { - _authOptions!.EnableActiveDirectoryAuth = true; + _authOptions.EnableActiveDirectoryAuth = true; var provider = new TestDomainLoginProvider(); string email = provider.GetEmailAddressFromUsername(TestDomainLoginProvider.ValidUsername); @@ -699,7 +695,7 @@ public async Task LoginInvalidExistingActiveDirectoryAccountUsingUserNameLoginAs }; user.MarkEmailAddressVerified(); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); await SendRequestAsync(r => r .Post() @@ -731,7 +727,7 @@ public async Task CanChangePasswordAsync() }; user.MarkEmailAddressVerified(); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -747,7 +743,7 @@ public async Task CanChangePasswordAsync() Assert.NotNull(result); Assert.NotEmpty(result.Token); - var token = await _tokenRepository!.GetByIdAsync(result.Token); + var token = await _tokenRepository.GetByIdAsync(result.Token); Assert.NotNull(token); var actualUser = await _userRepository.GetByIdAsync(token.UserId); @@ -792,7 +788,7 @@ public async Task ChangePasswordShouldFailWithCurrentPasswordAsync() }; user.MarkEmailAddressVerified(); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -808,7 +804,7 @@ public async Task ChangePasswordShouldFailWithCurrentPasswordAsync() Assert.NotNull(result); Assert.NotEmpty(result.Token); - var token = await _tokenRepository!.GetByIdAsync(result.Token); + var token = await _tokenRepository.GetByIdAsync(result.Token); Assert.NotNull(token); var actualUser = await _userRepository.GetByIdAsync(token.UserId); @@ -856,7 +852,7 @@ public async Task CanResetPasswordAsync() Assert.NotNull(user.PasswordResetToken); Assert.True(user.PasswordResetTokenExpiration.IsAfter(TimeProvider.GetUtcNow().UtcDateTime)); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -872,7 +868,7 @@ public async Task CanResetPasswordAsync() Assert.NotNull(result); Assert.NotEmpty(result.Token); - var token = await _tokenRepository!.GetByIdAsync(result.Token); + var token = await _tokenRepository.GetByIdAsync(result.Token); Assert.NotNull(token); var actualUser = await _userRepository.GetByIdAsync(token.UserId); @@ -917,7 +913,7 @@ public async Task ResetPasswordShouldFailWithCurrentPasswordAsync() Assert.NotNull(user.PasswordResetToken); Assert.True(user.PasswordResetTokenExpiration.IsAfter(TimeProvider.GetUtcNow().UtcDateTime)); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -933,7 +929,7 @@ public async Task ResetPasswordShouldFailWithCurrentPasswordAsync() Assert.NotNull(result); Assert.NotEmpty(result.Token); - var token = await _tokenRepository!.GetByIdAsync(result.Token); + var token = await _tokenRepository.GetByIdAsync(result.Token); Assert.NotNull(token); var actualUser = await _userRepository.GetByIdAsync(token.UserId); @@ -977,7 +973,7 @@ public async Task CanLogoutUserAsync() }; user.MarkEmailAddressVerified(); - await _userRepository!.AddAsync(user); + await _userRepository.AddAsync(user); var result = await SendRequestAsAsync(r => r .Post() @@ -993,7 +989,7 @@ public async Task CanLogoutUserAsync() Assert.NotNull(result); // Verify that the token is valid - var token = await _tokenRepository!.GetByIdAsync(result.Token); + var token = await _tokenRepository.GetByIdAsync(result.Token); Assert.Equal(TokenType.Authentication, token.Type); Assert.False(token.IsDisabled); Assert.False(token.IsSuspended); @@ -1011,7 +1007,7 @@ await SendRequestAsync(r => r [Fact] public async Task CanLogoutUserAccessTokenAsync() { - var token = await _tokenRepository!.GetByIdAsync(TestConstants.UserApiKey); + var token = await _tokenRepository.GetByIdAsync(TestConstants.UserApiKey); Assert.NotNull(token); Assert.Equal(TokenType.Access, token.Type); Assert.False(token.IsDisabled); @@ -1032,7 +1028,7 @@ await SendRequestAsync(r => r [Fact] public async Task CanLogoutClientAccessTokenAsync() { - var token = await _tokenRepository!.GetByIdAsync(TestConstants.ApiKey); + var token = await _tokenRepository.GetByIdAsync(TestConstants.ApiKey); Assert.NotNull(token); Assert.Equal(TokenType.Access, token.Type); Assert.False(token.IsDisabled); diff --git a/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs b/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs index 3a19ccada2..5da3fd8ce8 100644 --- a/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs +++ b/tests/Exceptionless.Tests/Controllers/EventControllerTests.cs @@ -1046,7 +1046,7 @@ await SendRequestAsync(r => r public async Task SpaFallbackWorks() { var response = await SendRequestAsync(r => r - .BaseUri(_server!.BaseAddress) + .BaseUri(_server.BaseAddress) .AppendPath("blah") .StatusCodeShouldBeOk() ); @@ -1054,13 +1054,13 @@ public async Task SpaFallbackWorks() Assert.Contains("exceptionless", content); await SendRequestAsync(r => r - .BaseUri(_server!.BaseAddress) + .BaseUri(_server.BaseAddress) .AppendPaths("api", "blah") .StatusCodeShouldBeNotFound() ); await SendRequestAsync(r => r - .BaseUri(_server!.BaseAddress) + .BaseUri(_server.BaseAddress) .AppendPaths("docs", "blah") .StatusCodeShouldBeNotFound() ); From f91004d304f2573115c3cd0a9f906e14ea548e94 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 17 Dec 2024 15:21:32 -0600 Subject: [PATCH 13/24] Cleanup --- tests/Exceptionless.Tests/TestWithServices.cs | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/tests/Exceptionless.Tests/TestWithServices.cs b/tests/Exceptionless.Tests/TestWithServices.cs index 29c4a2dc26..40011b3954 100644 --- a/tests/Exceptionless.Tests/TestWithServices.cs +++ b/tests/Exceptionless.Tests/TestWithServices.cs @@ -18,11 +18,10 @@ namespace Exceptionless.Tests; -public class TestWithServices : TestWithLoggingBase, IAsyncLifetime +public class TestWithServices : TestWithLoggingBase, IDisposable { private readonly IServiceProvider _container; private readonly ProxyTimeProvider _timeProvider; - //private static bool _startupActionsRun; public TestWithServices(ITestOutputHelper output) : base(output) { @@ -38,21 +37,6 @@ public TestWithServices(ITestOutputHelper output) : base(output) else throw new InvalidOperationException("TimeProvider must be of type ProxyTimeProvider"); } - - public virtual Task InitializeAsync() - { - return Task.CompletedTask; - - /* - if (_startupActionsRun) - return; - - var result = await _container.RunStartupActionsAsync(); - if (!result.Success) - throw new ApplicationException($"Startup action \"{result.FailedActionName}\" failed"); - - _startupActionsRun = true;*/ - } protected ProxyTimeProvider TimeProvider => _timeProvider; protected TService GetService() where TService : class @@ -98,9 +82,8 @@ private IServiceProvider CreateContainer() return services.BuildServiceProvider(); } - public Task DisposeAsync() + public void Dispose() { _timeProvider.Restore(); - return Task.CompletedTask; } } From 2db11fb5d5ba303b4b9216792bf60743c364782d Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 17 Dec 2024 15:30:23 -0600 Subject: [PATCH 14/24] Use the right Elasticsearch docker image --- .../Extensions/ElasticsearchExtensions.cs | 9 +++++---- src/Exceptionless.AppHost/Program.cs | 1 - tests/Exceptionless.Tests/AppWebHostFactory.cs | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs index 5a40b9ed24..423bb813ae 100644 --- a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs +++ b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs @@ -55,7 +55,7 @@ public static IResourceBuilder AddElasticsearch(this IDis return builder.AddResource(elasticsearch) .WithImage(ElasticsearchContainerImageTags.Image, ElasticsearchContainerImageTags.Tag) - .WithImageRegistry(ElasticsearchContainerImageTags.Registry) + .WithImageRegistry(ElasticsearchContainerImageTags.ElasticsearchRegistry) .WithHttpEndpoint(targetPort: ElasticsearchPort, port: port, name: ElasticsearchResource.PrimaryEndpointName) .WithEndpoint(targetPort: ElasticsearchInternalPort, name: ElasticsearchResource.InternalEndpointName) .WithEnvironment("discovery.type", "single-node") @@ -85,7 +85,7 @@ public static IResourceBuilder WithKibana(this IResourceB var resource = new KibanaResource(containerName); var resourceBuilder = builder.ApplicationBuilder.AddResource(resource) .WithImage(ElasticsearchContainerImageTags.KibanaImage, ElasticsearchContainerImageTags.Tag) - .WithImageRegistry(ElasticsearchContainerImageTags.Registry) + .WithImageRegistry(ElasticsearchContainerImageTags.KibanaRegistry) .WithHttpEndpoint(targetPort: KibanaPort, name: containerName) .WithEnvironment("xpack.security.enabled", "false") .ExcludeFromManifest(); @@ -114,8 +114,9 @@ public static IResourceBuilder WithDataBindMount(this IRe internal static class ElasticsearchContainerImageTags { - public const string Registry = "docker.elastic.co"; - public const string Image = "elasticsearch/elasticsearch"; + public const string ElasticsearchRegistry = "docker.io"; + public const string Image = "exceptionless/elasticsearch"; + public const string KibanaRegistry = "docker.elastic.co"; public const string KibanaImage = "kibana/kibana"; public const string Tag = "8.17.0"; } diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index 3a9b1f5787..b6db53173f 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -1,7 +1,6 @@ var builder = DistributedApplication.CreateBuilder(args); var elastic = builder.AddElasticsearch("Elasticsearch", port: 9200) - .WithImageTag("8.17.0") .WithLifetime(ContainerLifetime.Persistent) .WithContainerName("Exceptionless-Elasticsearch") .WithDataVolume("exceptionless.data.v1") diff --git a/tests/Exceptionless.Tests/AppWebHostFactory.cs b/tests/Exceptionless.Tests/AppWebHostFactory.cs index 26f23eb67e..7b9abeb862 100644 --- a/tests/Exceptionless.Tests/AppWebHostFactory.cs +++ b/tests/Exceptionless.Tests/AppWebHostFactory.cs @@ -24,7 +24,6 @@ public Task InitializeAsync() builder.AddElasticsearch("Elasticsearch", port: 9200) .WithContainerName("Exceptionless-Elasticsearch-Test") - .WithImageTag("8.17.0") .WithLifetime(ContainerLifetime.Persistent); _app = builder.Build(); From 2280e3e1456cdcb1d105a6cd205a1a7190ad04a0 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 17 Dec 2024 15:36:32 -0600 Subject: [PATCH 15/24] Use explicit minio version --- src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs b/src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs index 6360caeb38..464f3985f8 100644 --- a/src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs +++ b/src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs @@ -134,5 +134,5 @@ internal static class MinIoContainerImageTags { internal const string Registry = "docker.io"; internal const string Image = "minio/minio"; - internal const string Tag = "latest"; + internal const string Tag = "RELEASE.2024-12-13T22-19-12Z"; } From fc66183f717c2712b2d38fcc5adfd0839cb59d99 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 17 Dec 2024 17:57:59 -0600 Subject: [PATCH 16/24] Fixed launch setting --- src/Exceptionless.Web/Properties/launchSettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exceptionless.Web/Properties/launchSettings.json b/src/Exceptionless.Web/Properties/launchSettings.json index 56edae3669..c4b0282bfa 100644 --- a/src/Exceptionless.Web/Properties/launchSettings.json +++ b/src/Exceptionless.Web/Properties/launchSettings.json @@ -7,7 +7,7 @@ "EX_AppMode": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "http://localhost:5200/next" + "applicationUrl": "http://localhost:5200" } } } From bf13d0e0f91709807690094cabfb457eef3b96d4 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 17 Dec 2024 17:59:01 -0600 Subject: [PATCH 17/24] Removed start and stop services --- start-services.ps1 | 1 - stop-services.ps1 | 1 - 2 files changed, 2 deletions(-) delete mode 100644 start-services.ps1 delete mode 100644 stop-services.ps1 diff --git a/start-services.ps1 b/start-services.ps1 deleted file mode 100644 index 559514bd2b..0000000000 --- a/start-services.ps1 +++ /dev/null @@ -1 +0,0 @@ -docker compose -f docker/docker-compose.yml up --detach elasticsearch kibana \ No newline at end of file diff --git a/stop-services.ps1 b/stop-services.ps1 deleted file mode 100644 index e407f79689..0000000000 --- a/stop-services.ps1 +++ /dev/null @@ -1 +0,0 @@ -docker compose -f docker/docker-compose.yml down --remove-orphans \ No newline at end of file From b41f15f8d26a701db36c0282189450d30e35ec62 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 17 Dec 2024 18:15:07 -0600 Subject: [PATCH 18/24] Use fixed web client ports --- src/Exceptionless.AppHost/Program.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index b6db53173f..e2c4fbc251 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -45,10 +45,12 @@ builder.AddNpmApp("Web", "../../src/Exceptionless.Web/ClientApp", "dev") .WithReference(api) - .WithEndpoint(scheme: "http", env: "PORT"); + .WithEnvironment("ASPNETCORE_URLS", "http://localhost:5200") + .WithEndpoint(port: 5173, targetPort: 5173, scheme: "http", env: "PORT", isProxied: false); builder.AddNpmApp("AngularWeb", "../../src/Exceptionless.Web/ClientApp.angular", "serve") .WithReference(api) - .WithEndpoint(scheme: "http", env: "PORT"); + .WithEnvironment("ASPNETCORE_URLS", "http://localhost:5200") + .WithEndpoint(port: 5100, targetPort: 5100, scheme: "http", env: "PORT", isProxied: false); builder.Build().Run(); From b4e714d1122711f20dfebc1d9d3a874ab336865e Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 17 Dec 2024 18:55:31 -0600 Subject: [PATCH 19/24] Use S3 storage when running local --- src/Exceptionless.AppHost/Program.cs | 3 +- .../Configuration/StorageOptions.cs | 25 ++++++++++---- src/Exceptionless.Insulation/Bootstrapper.cs | 10 +++--- .../Properties/launchSettings.json | 34 +++++++++---------- .../appsettings.Development.yml | 2 +- .../Properties/launchSettings.json | 2 +- .../appsettings.Development.yml | 2 +- 7 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index e2c4fbc251..bb9b7b0fd3 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -6,7 +6,7 @@ .WithDataVolume("exceptionless.data.v1") .WithKibana(b => b.WithLifetime(ContainerLifetime.Persistent).WithContainerName("Exceptionless-Kibana")); -var storage = builder.AddMinIo("Storage", s => s.WithCredentials("guest", "password").WithPorts(9000)) +var storage = builder.AddMinIo("S3", s => s.WithCredentials("guest", "password").WithPorts(9000)) .WithLifetime(ContainerLifetime.Persistent) .WithContainerName("Exceptionless-Storage"); @@ -26,6 +26,7 @@ builder.AddProject("Jobs", "AllJobs") .WithReference(cache) .WithReference(elastic) + .WithReference(storage) .WithEnvironment("ConnectionStrings:Email", "smtp://localhost:1025") .WaitFor(elastic) .WaitFor(cache) diff --git a/src/Exceptionless.Core/Configuration/StorageOptions.cs b/src/Exceptionless.Core/Configuration/StorageOptions.cs index 2c114e973d..6ab6bad7cd 100644 --- a/src/Exceptionless.Core/Configuration/StorageOptions.cs +++ b/src/Exceptionless.Core/Configuration/StorageOptions.cs @@ -16,18 +16,31 @@ public class StorageOptions public static StorageOptions ReadFromConfiguration(IConfiguration config, AppOptions appOptions) { - var options = new StorageOptions(); - - options.Scope = appOptions.AppScope; + var options = new StorageOptions { Scope = appOptions.AppScope }; options.ScopePrefix = !String.IsNullOrEmpty(options.Scope) ? $"{options.Scope}-" : String.Empty; string? cs = config.GetConnectionString("Storage"); - options.Data = cs.ParseConnectionString(); - options.Provider = options.Data.GetString(nameof(options.Provider)); + if (cs != null) + { + options.Data = cs.ParseConnectionString(); + options.Provider = options.Data.GetString(nameof(options.Provider)); + } + else + { + var minioConnectionString = config.GetConnectionString("S3"); + if (!String.IsNullOrEmpty(minioConnectionString)) + { + options.Provider = "s3"; + } + } string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null; if (!String.IsNullOrEmpty(providerConnectionString)) - options.Data.AddRange(providerConnectionString.ParseConnectionString()); + { + var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); + options.Data ??= []; + options.Data.AddRange(providerOptions); + } options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); diff --git a/src/Exceptionless.Insulation/Bootstrapper.cs b/src/Exceptionless.Insulation/Bootstrapper.cs index dd690ec6e4..0e37d4c3ed 100644 --- a/src/Exceptionless.Insulation/Bootstrapper.cs +++ b/src/Exceptionless.Insulation/Bootstrapper.cs @@ -183,22 +183,22 @@ private static void RegisterQueue(IServiceCollection container, QueueOptions opt private static void RegisterStorage(IServiceCollection container, StorageOptions options) { - if (String.Equals(options.Provider, "aliyun")) + if (String.Equals(options.Provider, "azurestorage")) { - container.ReplaceSingleton(s => new AliyunFileStorage(new AliyunFileStorageOptions + container.ReplaceSingleton(s => new AzureFileStorage(new AzureFileStorageOptions { ConnectionString = options.ConnectionString, + ContainerName = $"{options.ScopePrefix}ex-events", Serializer = s.GetRequiredService(), TimeProvider = s.GetRequiredService(), LoggerFactory = s.GetRequiredService() })); } - else if (String.Equals(options.Provider, "azurestorage")) + else if (String.Equals(options.Provider, "aliyun")) { - container.ReplaceSingleton(s => new AzureFileStorage(new AzureFileStorageOptions + container.ReplaceSingleton(s => new AliyunFileStorage(new AliyunFileStorageOptions { ConnectionString = options.ConnectionString, - ContainerName = $"{options.ScopePrefix}ex-events", Serializer = s.GetRequiredService(), TimeProvider = s.GetRequiredService(), LoggerFactory = s.GetRequiredService() diff --git a/src/Exceptionless.Job/Properties/launchSettings.json b/src/Exceptionless.Job/Properties/launchSettings.json index 9043c97165..f6f61e578f 100644 --- a/src/Exceptionless.Job/Properties/launchSettings.json +++ b/src/Exceptionless.Job/Properties/launchSettings.json @@ -5,7 +5,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "CleanupDataJob": { @@ -14,7 +14,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "DataMigrationJob": { @@ -23,7 +23,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "MigrationJob": { @@ -32,7 +32,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "EventPostsJob": { @@ -41,7 +41,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "EventUserDescriptionsJob": { @@ -50,7 +50,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "EventNotificationsJob": { @@ -59,7 +59,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "MailMessageJob": { @@ -68,7 +68,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "WebHooksJob": { @@ -77,7 +77,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "CloseInactiveSessionsJob": { @@ -86,7 +86,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "DailySummaryJob": { @@ -95,7 +95,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "DownloadGeoIPDatabaseJob": { @@ -104,7 +104,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "WorkItemJob": { @@ -113,7 +113,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "MaintainIndexesJob": { @@ -122,7 +122,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "StackEventCountJob": { @@ -131,7 +131,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "EventSnapshotJob": { @@ -140,8 +140,8 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" } } -} \ No newline at end of file +} diff --git a/src/Exceptionless.Job/appsettings.Development.yml b/src/Exceptionless.Job/appsettings.Development.yml index 438c3a12c0..bdd125522a 100644 --- a/src/Exceptionless.Job/appsettings.Development.yml +++ b/src/Exceptionless.Job/appsettings.Development.yml @@ -5,7 +5,7 @@ ConnectionStrings: # Cache: provider=redis; # MessageBus: provider=redis; # Queue: provider=redis; - Storage: provider=folder;path=..\Exceptionless.Web\storage +# Storage: provider=folder;path=..\Exceptionless.Web\storage Email: smtp://localhost:1025 # Base url for the ui used to build links in emails and other places. diff --git a/src/Exceptionless.Web/Properties/launchSettings.json b/src/Exceptionless.Web/Properties/launchSettings.json index c4b0282bfa..a62987a549 100644 --- a/src/Exceptionless.Web/Properties/launchSettings.json +++ b/src/Exceptionless.Web/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Exceptionless": { "commandName": "Project", - "launchBrowser": true, + "launchBrowser": false, "environmentVariables": { "EX_AppMode": "Development" }, diff --git a/src/Exceptionless.Web/appsettings.Development.yml b/src/Exceptionless.Web/appsettings.Development.yml index a399826abe..2abf77f96e 100644 --- a/src/Exceptionless.Web/appsettings.Development.yml +++ b/src/Exceptionless.Web/appsettings.Development.yml @@ -5,7 +5,7 @@ ConnectionStrings: # Cache: provider=redis; # MessageBus: provider=redis; # Queue: provider=redis; - Storage: provider=folder;path=.\storage +# Storage: provider=folder;path=.\storage # LDAP: '' # Email: smtp://localhost:1025 From 33171cfda3fc28fc85ecc473f9a0507f0337f989 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 17 Dec 2024 19:32:44 -0600 Subject: [PATCH 20/24] Fixed an issue where code could throw due to CurrentUser --- .../Configuration/CacheOptions.cs | 2 +- .../Base/ExceptionlessApiController.cs | 10 ++++++++++ .../Controllers/EventController.cs | 16 ++++++++-------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Exceptionless.Core/Configuration/CacheOptions.cs b/src/Exceptionless.Core/Configuration/CacheOptions.cs index 4bde275782..f746e9b3f8 100644 --- a/src/Exceptionless.Core/Configuration/CacheOptions.cs +++ b/src/Exceptionless.Core/Configuration/CacheOptions.cs @@ -42,7 +42,7 @@ public static CacheOptions ReadFromConfiguration(IConfiguration config, AppOptio options.Data.AddRange(providerOptions); } - options.ConnectionString = options.Data.BuildConnectionString([ nameof(options.Provider) ]); + options.ConnectionString = options.Data.BuildConnectionString([nameof(options.Provider)]); return options; } diff --git a/src/Exceptionless.Web/Controllers/Base/ExceptionlessApiController.cs b/src/Exceptionless.Web/Controllers/Base/ExceptionlessApiController.cs index 6292a71153..00fad3b8d6 100644 --- a/src/Exceptionless.Web/Controllers/Base/ExceptionlessApiController.cs +++ b/src/Exceptionless.Web/Controllers/Base/ExceptionlessApiController.cs @@ -94,6 +94,16 @@ protected int GetSkip(int currentPage, int limit) return skip; } + /// + /// This call will throw an exception if the user is a token auth type. + /// This is less than ideal, and we should refactor this to be a nullable user. + /// NOTE: The only endpoints that allow token auth types is + /// - post event + /// - post user event description + /// - post session heartbeat + /// - post session end + /// - project config + /// protected virtual User CurrentUser => Request.GetUser(); protected bool CanAccessOrganization(string organizationId) diff --git a/src/Exceptionless.Web/Controllers/EventController.cs b/src/Exceptionless.Web/Controllers/EventController.cs index 2f7b90c7c0..ca35362fb8 100644 --- a/src/Exceptionless.Web/Controllers/EventController.cs +++ b/src/Exceptionless.Web/Controllers/EventController.cs @@ -248,8 +248,8 @@ private async Task> CountInternalAsync(AppFilter sf, T } catch (Exception ex) { - using (_logger.BeginScope(new ExceptionlessState().Property("Search Filter", new { SystemFilter = sf, UserFilter = filter, Time = ti, Aggregations = aggregations }).Tag("Search").Identity(CurrentUser.EmailAddress).Property("User", CurrentUser).SetHttpContext(HttpContext))) - _logger.LogError(ex, "An error has occurred. Please check your filter or aggregations"); + using var _ = _logger.BeginScope(new ExceptionlessState().Property("Search Filter", new { SystemFilter = sf, UserFilter = filter, Time = ti, Aggregations = aggregations }).Tag("Search").Identity(CurrentUser.EmailAddress).Property("User", CurrentUser).SetHttpContext(HttpContext)); + _logger.LogError(ex, "An error has occurred. Please check your filter or aggregations: {Message}", ex.Message); throw; } @@ -867,8 +867,8 @@ await Task.WhenAll( { if (projectId != _appOptions.InternalProjectId) { - using (_logger.BeginScope(new ExceptionlessState().Project(projectId).Identity(CurrentUser.EmailAddress).Property("User", CurrentUser).Property("Id", id).Property("Close", close).SetHttpContext(HttpContext))) - _logger.LogError(ex, "Error enqueuing session heartbeat"); + using var _ = _logger.BeginScope(new ExceptionlessState().Project(projectId).Property("Id", id).Property("Close", close).SetHttpContext(HttpContext)); + _logger.LogError(ex, "Error enqueuing session heartbeat: {Message}", ex.Message); } throw; @@ -1127,8 +1127,8 @@ await _eventPostService.EnqueueAsync(new EventPost(_appOptions.EnableArchive) { if (projectId != _appOptions.InternalProjectId) { - using (_logger.BeginScope(new ExceptionlessState().Project(projectId).Identity(CurrentUser.EmailAddress).Property("User", CurrentUser).SetHttpContext(HttpContext))) - _logger.LogError(ex, "Error enqueuing event post"); + using var _ = _logger.BeginScope(new ExceptionlessState().Project(projectId).SetHttpContext(HttpContext)); + _logger.LogError(ex, "Error enqueuing event post: {Message}", ex.Message); } throw; @@ -1328,8 +1328,8 @@ await _eventPostService.EnqueueAsync(new EventPost(_appOptions.EnableArchive) { if (projectId != _appOptions.InternalProjectId) { - using (_logger.BeginScope(new ExceptionlessState().Project(projectId).Identity(CurrentUser.EmailAddress).Property("User", CurrentUser).SetHttpContext(HttpContext))) - _logger.LogError(ex, "Error enqueuing event post"); + using var _ = _logger.BeginScope(new ExceptionlessState().Project(projectId).SetHttpContext(HttpContext)); + _logger.LogError(ex, "Error enqueuing event post: {Message}", ex.Message); } throw; From 93caf83bc69cfb055a195f34a60c274dd5500ce3 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 17 Dec 2024 20:24:20 -0600 Subject: [PATCH 21/24] Fix S3 --- src/Exceptionless.AppHost/Program.cs | 2 +- .../Configuration/CacheOptions.cs | 2 +- .../Configuration/MessageBusOptions.cs | 2 +- .../Configuration/QueueOptions.cs | 2 +- .../Configuration/StorageOptions.cs | 2 +- src/Exceptionless.Insulation/Bootstrapper.cs | 24 ++++++++++++------- .../Base/ExceptionlessApiController.cs | 1 - 7 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index bb9b7b0fd3..0df5bf1581 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -6,7 +6,7 @@ .WithDataVolume("exceptionless.data.v1") .WithKibana(b => b.WithLifetime(ContainerLifetime.Persistent).WithContainerName("Exceptionless-Kibana")); -var storage = builder.AddMinIo("S3", s => s.WithCredentials("guest", "password").WithPorts(9000)) +var storage = builder.AddMinIo("S3", s => s.WithCredentials("guest", "password").WithPorts(9000).WithBucket("ex-events")) .WithLifetime(ContainerLifetime.Persistent) .WithContainerName("Exceptionless-Storage"); diff --git a/src/Exceptionless.Core/Configuration/CacheOptions.cs b/src/Exceptionless.Core/Configuration/CacheOptions.cs index f746e9b3f8..3427e1a1bb 100644 --- a/src/Exceptionless.Core/Configuration/CacheOptions.cs +++ b/src/Exceptionless.Core/Configuration/CacheOptions.cs @@ -38,7 +38,7 @@ public static CacheOptions ReadFromConfiguration(IConfiguration config, AppOptio if (!String.IsNullOrEmpty(providerConnectionString)) { var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); - options.Data ??= []; + options.Data ??= new Dictionary(StringComparer.OrdinalIgnoreCase); options.Data.AddRange(providerOptions); } diff --git a/src/Exceptionless.Core/Configuration/MessageBusOptions.cs b/src/Exceptionless.Core/Configuration/MessageBusOptions.cs index d5f882e49c..23327d2387 100644 --- a/src/Exceptionless.Core/Configuration/MessageBusOptions.cs +++ b/src/Exceptionless.Core/Configuration/MessageBusOptions.cs @@ -40,7 +40,7 @@ public static MessageBusOptions ReadFromConfiguration(IConfiguration config, App if (!String.IsNullOrEmpty(providerConnectionString)) { var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); - options.Data ??= []; + options.Data ??= new Dictionary(StringComparer.OrdinalIgnoreCase); options.Data.AddRange(providerOptions); } diff --git a/src/Exceptionless.Core/Configuration/QueueOptions.cs b/src/Exceptionless.Core/Configuration/QueueOptions.cs index 48195a3b75..3851df5613 100644 --- a/src/Exceptionless.Core/Configuration/QueueOptions.cs +++ b/src/Exceptionless.Core/Configuration/QueueOptions.cs @@ -38,7 +38,7 @@ public static QueueOptions ReadFromConfiguration(IConfiguration config, AppOptio if (!String.IsNullOrEmpty(providerConnectionString)) { var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); - options.Data ??= []; + options.Data ??= new Dictionary(StringComparer.OrdinalIgnoreCase); options.Data.AddRange(providerOptions); } diff --git a/src/Exceptionless.Core/Configuration/StorageOptions.cs b/src/Exceptionless.Core/Configuration/StorageOptions.cs index 6ab6bad7cd..79fbbba864 100644 --- a/src/Exceptionless.Core/Configuration/StorageOptions.cs +++ b/src/Exceptionless.Core/Configuration/StorageOptions.cs @@ -38,7 +38,7 @@ public static StorageOptions ReadFromConfiguration(IConfiguration config, AppOpt if (!String.IsNullOrEmpty(providerConnectionString)) { var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); - options.Data ??= []; + options.Data ??= new Dictionary(StringComparer.OrdinalIgnoreCase); options.Data.AddRange(providerOptions); } diff --git a/src/Exceptionless.Insulation/Bootstrapper.cs b/src/Exceptionless.Insulation/Bootstrapper.cs index 0e37d4c3ed..f1f88322b9 100644 --- a/src/Exceptionless.Insulation/Bootstrapper.cs +++ b/src/Exceptionless.Insulation/Bootstrapper.cs @@ -227,16 +227,22 @@ private static void RegisterStorage(IServiceCollection container, StorageOptions } else if (String.Equals(options.Provider, "s3")) { - container.ReplaceSingleton(s => new S3FileStorage(new S3FileStorageOptions + container.ReplaceSingleton(s => { - ConnectionString = options.ConnectionString, - Credentials = GetAWSCredentials(options.Data), - Region = GetAWSRegionEndpoint(options.Data), - Bucket = $"{options.ScopePrefix}{options.Data.GetString("bucket", "ex-events")}", - Serializer = s.GetRequiredService(), - TimeProvider = s.GetRequiredService(), - LoggerFactory = s.GetRequiredService() - })); + IFileStorage storage = new S3FileStorage(o => o + .ConnectionString(options.ConnectionString) + .Credentials(GetAWSCredentials(options.Data)) + .Region(GetAWSRegionEndpoint(options.Data)) + .Bucket(options.Data.GetString("bucket", "ex-events")) + .Serializer(s.GetRequiredService()) + .TimeProvider(s.GetRequiredService()) + .LoggerFactory(s.GetRequiredService())); + + if (!String.IsNullOrWhiteSpace(options.ScopePrefix)) + storage = new ScopedFileStorage(storage, options.ScopePrefix); + + return storage; + }); } } diff --git a/src/Exceptionless.Web/Controllers/Base/ExceptionlessApiController.cs b/src/Exceptionless.Web/Controllers/Base/ExceptionlessApiController.cs index 00fad3b8d6..fb968c017a 100644 --- a/src/Exceptionless.Web/Controllers/Base/ExceptionlessApiController.cs +++ b/src/Exceptionless.Web/Controllers/Base/ExceptionlessApiController.cs @@ -111,7 +111,6 @@ protected bool CanAccessOrganization(string organizationId) return Request.CanAccessOrganization(organizationId); } - protected bool IsInOrganization([NotNullWhen(true)] string? organizationId) { if (String.IsNullOrEmpty(organizationId)) From e6d7709f9fe1323e9d7c96e831171b5b24cd8671 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Tue, 17 Dec 2024 20:35:14 -0600 Subject: [PATCH 22/24] [BREAKING] Remove scope prefix from bucket names and instead use scoped file storage for app scopes --- src/Exceptionless.Insulation/Bootstrapper.cs | 86 ++++++++++++++------ 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/src/Exceptionless.Insulation/Bootstrapper.cs b/src/Exceptionless.Insulation/Bootstrapper.cs index f1f88322b9..775f6b7a6b 100644 --- a/src/Exceptionless.Insulation/Bootstrapper.cs +++ b/src/Exceptionless.Insulation/Bootstrapper.cs @@ -185,45 +185,77 @@ private static void RegisterStorage(IServiceCollection container, StorageOptions { if (String.Equals(options.Provider, "azurestorage")) { - container.ReplaceSingleton(s => new AzureFileStorage(new AzureFileStorageOptions + container.ReplaceSingleton(s => { - ConnectionString = options.ConnectionString, - ContainerName = $"{options.ScopePrefix}ex-events", - Serializer = s.GetRequiredService(), - TimeProvider = s.GetRequiredService(), - LoggerFactory = s.GetRequiredService() - })); + IFileStorage storage = new AzureFileStorage(new AzureFileStorageOptions + { + ConnectionString = options.ConnectionString, + ContainerName = "ex-events", + Serializer = s.GetRequiredService(), + TimeProvider = s.GetRequiredService(), + LoggerFactory = s.GetRequiredService() + }); + + if (!String.IsNullOrWhiteSpace(options.Scope)) + storage = new ScopedFileStorage(storage, options.Scope); + + return storage; + }); } else if (String.Equals(options.Provider, "aliyun")) { - container.ReplaceSingleton(s => new AliyunFileStorage(new AliyunFileStorageOptions + container.ReplaceSingleton(s => { - ConnectionString = options.ConnectionString, - Serializer = s.GetRequiredService(), - TimeProvider = s.GetRequiredService(), - LoggerFactory = s.GetRequiredService() - })); + IFileStorage storage = new AliyunFileStorage(new AliyunFileStorageOptions + { + ConnectionString = options.ConnectionString, + Serializer = s.GetRequiredService(), + TimeProvider = s.GetRequiredService(), + LoggerFactory = s.GetRequiredService() + }); + + if (!String.IsNullOrWhiteSpace(options.Scope)) + storage = new ScopedFileStorage(storage, options.Scope); + + return storage; + }); } else if (String.Equals(options.Provider, "folder")) { string path = options.Data.GetString("path", "|DataDirectory|\\storage"); - container.AddSingleton(s => new FolderFileStorage(new FolderFileStorageOptions + container.AddSingleton(s => { - Folder = PathHelper.ExpandPath(path), - Serializer = s.GetRequiredService(), - TimeProvider = s.GetRequiredService(), - LoggerFactory = s.GetRequiredService() - })); + IFileStorage storage = new FolderFileStorage(new FolderFileStorageOptions + { + Folder = PathHelper.ExpandPath(path), + Serializer = s.GetRequiredService(), + TimeProvider = s.GetRequiredService(), + LoggerFactory = s.GetRequiredService() + }); + + if (!String.IsNullOrWhiteSpace(options.Scope)) + storage = new ScopedFileStorage(storage, options.Scope); + + return storage; + }); } else if (String.Equals(options.Provider, "minio")) { - container.ReplaceSingleton(s => new MinioFileStorage(new MinioFileStorageOptions + container.ReplaceSingleton(s => { - ConnectionString = options.ConnectionString, - Serializer = s.GetRequiredService(), - TimeProvider = s.GetRequiredService(), - LoggerFactory = s.GetRequiredService() - })); + IFileStorage storage = new MinioFileStorage(new MinioFileStorageOptions + { + ConnectionString = options.ConnectionString, + Serializer = s.GetRequiredService(), + TimeProvider = s.GetRequiredService(), + LoggerFactory = s.GetRequiredService() + }); + + if (!String.IsNullOrWhiteSpace(options.Scope)) + storage = new ScopedFileStorage(storage, options.Scope); + + return storage; + }); } else if (String.Equals(options.Provider, "s3")) { @@ -238,8 +270,8 @@ private static void RegisterStorage(IServiceCollection container, StorageOptions .TimeProvider(s.GetRequiredService()) .LoggerFactory(s.GetRequiredService())); - if (!String.IsNullOrWhiteSpace(options.ScopePrefix)) - storage = new ScopedFileStorage(storage, options.ScopePrefix); + if (!String.IsNullOrWhiteSpace(options.Scope)) + storage = new ScopedFileStorage(storage, options.Scope); return storage; }); From 757ec52413e0aa94c2083a804f83e746c283be03 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Tue, 17 Dec 2024 22:05:11 -0600 Subject: [PATCH 23/24] Only poll queue metrics in the same process that is running the stack event count job --- .../Configuration/QueueOptions.cs | 4 +++ .../Exceptionless.Core.csproj | 4 +-- src/Exceptionless.Insulation/Bootstrapper.cs | 12 +++++-- src/Exceptionless.Job/Program.cs | 33 ++++++++++--------- src/Exceptionless.Web/Program.cs | 3 ++ 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/Exceptionless.Core/Configuration/QueueOptions.cs b/src/Exceptionless.Core/Configuration/QueueOptions.cs index 3851df5613..5955eb25fd 100644 --- a/src/Exceptionless.Core/Configuration/QueueOptions.cs +++ b/src/Exceptionless.Core/Configuration/QueueOptions.cs @@ -13,6 +13,8 @@ public class QueueOptions public string Scope { get; internal set; } = null!; public string ScopePrefix { get; internal set; } = null!; + public bool MetricsPollingEnabled { get; set; } = true; + public TimeSpan MetricsPollingInterval { get; set; } = TimeSpan.FromSeconds(5); public static QueueOptions ReadFromConfiguration(IConfiguration config, AppOptions appOptions) { @@ -44,6 +46,8 @@ public static QueueOptions ReadFromConfiguration(IConfiguration config, AppOptio options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); + options.MetricsPollingInterval = appOptions.AppMode == AppMode.Development ? TimeSpan.FromSeconds(15) : TimeSpan.FromSeconds(5); + return options; } } diff --git a/src/Exceptionless.Core/Exceptionless.Core.csproj b/src/Exceptionless.Core/Exceptionless.Core.csproj index bf3a748b3c..4b2da10210 100644 --- a/src/Exceptionless.Core/Exceptionless.Core.csproj +++ b/src/Exceptionless.Core/Exceptionless.Core.csproj @@ -22,8 +22,8 @@ - - + + diff --git a/src/Exceptionless.Insulation/Bootstrapper.cs b/src/Exceptionless.Insulation/Bootstrapper.cs index 775f6b7a6b..fc12ca30c5 100644 --- a/src/Exceptionless.Insulation/Bootstrapper.cs +++ b/src/Exceptionless.Insulation/Bootstrapper.cs @@ -289,7 +289,9 @@ private static IQueue CreateAzureStorageQueue(IServiceProvider container, WorkItemTimeout = workItemTimeout.GetValueOrDefault(TimeSpan.FromMinutes(5.0)), Serializer = container.GetRequiredService(), TimeProvider = container.GetRequiredService(), - LoggerFactory = container.GetRequiredService() + LoggerFactory = container.GetRequiredService(), + MetricsPollingEnabled = options.MetricsPollingEnabled, + MetricsPollingInterval = options.MetricsPollingInterval }); } @@ -305,7 +307,9 @@ private static IQueue CreateRedisQueue(IServiceProvider container, QueueOp RunMaintenanceTasks = runMaintenanceTasks, Serializer = container.GetRequiredService(), TimeProvider = container.GetRequiredService(), - LoggerFactory = container.GetRequiredService() + LoggerFactory = container.GetRequiredService(), + MetricsPollingEnabled = options.MetricsPollingEnabled, + MetricsPollingInterval = options.MetricsPollingInterval }); } @@ -333,7 +337,9 @@ private static IQueue CreateSQSQueue(IServiceProvider container, QueueOpti WorkItemTimeout = workItemTimeout.GetValueOrDefault(TimeSpan.FromMinutes(5.0)), Serializer = container.GetRequiredService(), TimeProvider = container.GetRequiredService(), - LoggerFactory = container.GetRequiredService() + LoggerFactory = container.GetRequiredService(), + MetricsPollingEnabled = options.MetricsPollingEnabled, + MetricsPollingInterval = options.MetricsPollingInterval }); } diff --git a/src/Exceptionless.Job/Program.cs b/src/Exceptionless.Job/Program.cs index e6d422649e..2433bb65be 100644 --- a/src/Exceptionless.Job/Program.cs +++ b/src/Exceptionless.Job/Program.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using Exceptionless.Core; using Exceptionless.Core.Configuration; using Exceptionless.Core.Extensions; @@ -60,6 +60,9 @@ public static IHostBuilder CreateHostBuilder(string[] args) .ForContext(); var options = AppOptions.ReadFromConfiguration(config); + // only poll the queue metrics if this process is going to run the stack event count job + options.QueueOptions.MetricsPollingEnabled = jobOptions.StackEventCount; + var apmConfig = new ApmConfig(config, $"job-{jobOptions.JobName.ToLowerUnderscoredWords('-')}", options.InformationalVersion, options.CacheOptions.Provider == "redis"); Log.Information("Bootstrapping Exceptionless {JobName} job(s) in {AppMode} mode ({InformationalVersion}) on {MachineName} with options {@Options}", jobOptions.JobName ?? "All", environment, options.InformationalVersion, Environment.MachineName, options); @@ -146,27 +149,27 @@ private static void AddJobs(IServiceCollection services, JobRunnerOptions option services.AddJob(); if (options.CloseInactiveSessions) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.DailySummary) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.DataMigration) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options is { DownloadGeoIPDatabase: true, AllJobs: true }) services.AddCronJob("0 1 * * *"); if (options is { DownloadGeoIPDatabase: true, AllJobs: false }) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.EventNotifications) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.EventPosts) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.EventUsage) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.EventUserDescriptions) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.MailMessage) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options is { MaintainIndexes: true, AllJobs: true }) services.AddCronJob("10 */2 * * *"); @@ -174,14 +177,14 @@ private static void AddJobs(IServiceCollection services, JobRunnerOptions option services.AddJob(); if (options.Migration) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.StackStatus) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.StackEventCount) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.WebHooks) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.WorkItem) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); } } diff --git a/src/Exceptionless.Web/Program.cs b/src/Exceptionless.Web/Program.cs index ce6cb5e30f..88bb1f0dad 100644 --- a/src/Exceptionless.Web/Program.cs +++ b/src/Exceptionless.Web/Program.cs @@ -61,6 +61,9 @@ public static IHostBuilder CreateHostBuilder(IConfigurationRoot config, string e .ForContext(); var options = AppOptions.ReadFromConfiguration(config); + // only poll the queue metrics if this process is going to host the jobs + options.QueueOptions.MetricsPollingEnabled = options.RunJobsInProcess; + var apmConfig = new ApmConfig(config, "web", options.InformationalVersion, options.CacheOptions.Provider == "redis"); Log.Information("Bootstrapping Exceptionless Web in {AppMode} mode ({InformationalVersion}) on {MachineName} with options {@Options}", environment, options.InformationalVersion, Environment.MachineName, options); From d47c729feea76a06889a7e090577e691ef271496 Mon Sep 17 00:00:00 2001 From: Blake Niemyjski Date: Wed, 18 Dec 2024 08:01:04 -0600 Subject: [PATCH 24/24] Reverted some of the breaking changes around storage. --- src/Exceptionless.Insulation/Bootstrapper.cs | 76 ++++++-------------- 1 file changed, 22 insertions(+), 54 deletions(-) diff --git a/src/Exceptionless.Insulation/Bootstrapper.cs b/src/Exceptionless.Insulation/Bootstrapper.cs index fc12ca30c5..d47c6916b6 100644 --- a/src/Exceptionless.Insulation/Bootstrapper.cs +++ b/src/Exceptionless.Insulation/Bootstrapper.cs @@ -185,40 +185,24 @@ private static void RegisterStorage(IServiceCollection container, StorageOptions { if (String.Equals(options.Provider, "azurestorage")) { - container.ReplaceSingleton(s => + container.ReplaceSingleton(s => new AzureFileStorage(new AzureFileStorageOptions { - IFileStorage storage = new AzureFileStorage(new AzureFileStorageOptions - { - ConnectionString = options.ConnectionString, - ContainerName = "ex-events", - Serializer = s.GetRequiredService(), - TimeProvider = s.GetRequiredService(), - LoggerFactory = s.GetRequiredService() - }); - - if (!String.IsNullOrWhiteSpace(options.Scope)) - storage = new ScopedFileStorage(storage, options.Scope); - - return storage; - }); + ConnectionString = options.ConnectionString, + ContainerName = $"{options.ScopePrefix}ex-events", + Serializer = s.GetRequiredService(), + TimeProvider = s.GetRequiredService(), + LoggerFactory = s.GetRequiredService() + })); } else if (String.Equals(options.Provider, "aliyun")) { - container.ReplaceSingleton(s => + container.ReplaceSingleton(s => new AliyunFileStorage(new AliyunFileStorageOptions { - IFileStorage storage = new AliyunFileStorage(new AliyunFileStorageOptions - { - ConnectionString = options.ConnectionString, - Serializer = s.GetRequiredService(), - TimeProvider = s.GetRequiredService(), - LoggerFactory = s.GetRequiredService() - }); - - if (!String.IsNullOrWhiteSpace(options.Scope)) - storage = new ScopedFileStorage(storage, options.Scope); - - return storage; - }); + ConnectionString = options.ConnectionString, + Serializer = s.GetRequiredService(), + TimeProvider = s.GetRequiredService(), + LoggerFactory = s.GetRequiredService() + })); } else if (String.Equals(options.Provider, "folder")) { @@ -241,40 +225,24 @@ private static void RegisterStorage(IServiceCollection container, StorageOptions } else if (String.Equals(options.Provider, "minio")) { - container.ReplaceSingleton(s => + container.ReplaceSingleton(s => new MinioFileStorage(new MinioFileStorageOptions { - IFileStorage storage = new MinioFileStorage(new MinioFileStorageOptions - { - ConnectionString = options.ConnectionString, - Serializer = s.GetRequiredService(), - TimeProvider = s.GetRequiredService(), - LoggerFactory = s.GetRequiredService() - }); - - if (!String.IsNullOrWhiteSpace(options.Scope)) - storage = new ScopedFileStorage(storage, options.Scope); - - return storage; - }); + ConnectionString = options.ConnectionString, + Serializer = s.GetRequiredService(), + TimeProvider = s.GetRequiredService(), + LoggerFactory = s.GetRequiredService() + })); } else if (String.Equals(options.Provider, "s3")) { - container.ReplaceSingleton(s => - { - IFileStorage storage = new S3FileStorage(o => o + container.ReplaceSingleton(s => new S3FileStorage(o => o .ConnectionString(options.ConnectionString) .Credentials(GetAWSCredentials(options.Data)) .Region(GetAWSRegionEndpoint(options.Data)) - .Bucket(options.Data.GetString("bucket", "ex-events")) + .Bucket(options.Data.GetString("bucket", $"{options.ScopePrefix}ex-events")) .Serializer(s.GetRequiredService()) .TimeProvider(s.GetRequiredService()) - .LoggerFactory(s.GetRequiredService())); - - if (!String.IsNullOrWhiteSpace(options.Scope)) - storage = new ScopedFileStorage(storage, options.Scope); - - return storage; - }); + .LoggerFactory(s.GetRequiredService()))); } }