diff --git a/src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs index dca0f43ba..29f4e7b71 100644 --- a/src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs +++ b/src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs @@ -235,6 +235,7 @@ private static void AddOpenTelemetry(IServiceCollection services, IConfiguration DistributedContextPropagator.Current = new AspNetCorePropagator(); var appInsightsConnectionString = GetAppInsightsConnectionStringForOtel(config, env); + var useOpenTelemetryCollector = config.GetValue("AppSettings:UseOpenTelemetryCollector"); services .AddOpenTelemetry() @@ -252,12 +253,13 @@ private static void AddOpenTelemetry(IServiceCollection services, IConfiguration .AddAspNetCoreInstrumentation(opts => { opts.RecordException = true; + opts.Filter = httpContext => !httpContext.Request.Path.StartsWithSegments("/health"); }); if (isTest) return; - if (!string.IsNullOrWhiteSpace(appInsightsConnectionString)) + if (useOpenTelemetryCollector is not true && !string.IsNullOrWhiteSpace(appInsightsConnectionString)) { builder = builder.AddAzureMonitorTraceExporter(options => { @@ -280,7 +282,7 @@ private static void AddOpenTelemetry(IServiceCollection services, IConfiguration if (isTest) return; - if (!string.IsNullOrWhiteSpace(appInsightsConnectionString)) + if (useOpenTelemetryCollector is not true && !string.IsNullOrWhiteSpace(appInsightsConnectionString)) { builder = builder.AddAzureMonitorMetricExporter(options => { @@ -302,7 +304,7 @@ private static void AddOpenTelemetry(IServiceCollection services, IConfiguration if (isTest) return; - if (!string.IsNullOrWhiteSpace(appInsightsConnectionString)) + if (useOpenTelemetryCollector is not true && !string.IsNullOrWhiteSpace(appInsightsConnectionString)) { options.AddAzureMonitorLogExporter(options => { diff --git a/src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs b/src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs index 7b7fee936..ea3b5c8da 100644 --- a/src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs +++ b/src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs @@ -1,5 +1,8 @@ +using Altinn.App.Core.Configuration; using Altinn.App.Core.Extensions; using Altinn.App.Core.Features.Maskinporten.Extensions; +using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.FileProviders; namespace Altinn.App.Api.Extensions; @@ -29,19 +32,111 @@ public static void ConfigureAppWebHost(this IWebHostBuilder builder, string[] ar configBuilder.AddInMemoryCollection(config); + var runtimeSecretsDirectory = context.Configuration["AppSettings:RuntimeSecretsDirectory"]; + if (string.IsNullOrWhiteSpace(runtimeSecretsDirectory)) + { + runtimeSecretsDirectory = AppSettings.DefaultRuntimeSecretsDirectory; + } + configBuilder.AddMaskinportenSettingsFile( context, "MaskinportenSettingsFilepath", - "/mnt/app-secrets/maskinporten-settings.json" + Path.Join(runtimeSecretsDirectory, "maskinporten-settings.json") ); configBuilder.AddMaskinportenSettingsFile( context, "MaskinportenSettingsInternalFilepath", - "/mnt/app-secrets/maskinporten-settings-internal.json" + Path.Join(runtimeSecretsDirectory, "maskinporten-settings-internal.json") ); + configBuilder.AddRuntimeConfigFiles(context.HostingEnvironment, runtimeSecretsDirectory); configBuilder.LoadAppConfig(args); } ); } + + private static void AddRuntimeConfigFiles( + this IConfigurationBuilder configBuilder, + IHostEnvironment hostEnvironment, + string secretsDirectory + ) + { + if (hostEnvironment.IsDevelopment()) + { + return; + } + + ArgumentException.ThrowIfNullOrWhiteSpace(secretsDirectory); + const string overrideFileNameFragment = "override"; + if (!Directory.Exists(secretsDirectory)) + { + return; + } + + string[] jsonFiles = Directory.GetFiles(secretsDirectory, "*.json", SearchOption.TopDirectoryOnly); + Array.Sort(jsonFiles, StringComparer.OrdinalIgnoreCase); + + PhysicalFileProvider? secretsFileProvider = null; + HashSet existingJsonFilePaths = []; + + foreach (JsonConfigurationSource source in configBuilder.Sources.OfType()) + { + if (source.FileProvider is null || string.IsNullOrWhiteSpace(source.Path)) + { + continue; + } + + string? existingJsonFilePath = source.FileProvider.GetFileInfo(source.Path).PhysicalPath; + if (string.IsNullOrWhiteSpace(existingJsonFilePath)) + { + continue; + } + + existingJsonFilePaths.Add(Path.GetFullPath(existingJsonFilePath)); + } + + foreach (string jsonFile in jsonFiles) + { + string jsonFilePath = Path.GetFullPath(jsonFile); + if (existingJsonFilePaths.Contains(jsonFilePath)) + { + continue; + } + + string jsonFileName = Path.GetFileName(jsonFile); + if (jsonFileName.Contains(overrideFileNameFragment, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + configBuilder.AddJsonFile( + provider: secretsFileProvider ??= new PhysicalFileProvider(secretsDirectory), + path: jsonFileName, + optional: true, + reloadOnChange: true + ); + } + + foreach (string jsonFile in jsonFiles) + { + string jsonFilePath = Path.GetFullPath(jsonFile); + if (existingJsonFilePaths.Contains(jsonFilePath)) + { + continue; + } + + string jsonFileName = Path.GetFileName(jsonFile); + if (!jsonFileName.Contains(overrideFileNameFragment, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + configBuilder.AddJsonFile( + provider: secretsFileProvider ??= new PhysicalFileProvider(secretsDirectory), + path: jsonFileName, + optional: true, + reloadOnChange: true + ); + } + } } diff --git a/src/Altinn.App.Core/Configuration/AppSettings.cs b/src/Altinn.App.Core/Configuration/AppSettings.cs index 447bf32b6..2b98c9be9 100644 --- a/src/Altinn.App.Core/Configuration/AppSettings.cs +++ b/src/Altinn.App.Core/Configuration/AppSettings.cs @@ -220,4 +220,16 @@ public class AppSettings /// Improves instrumentation throughout the Altinn app libraries. /// public bool UseOpenTelemetry { get; set; } + + /// + /// Use OpenTelemetry collector via OTLP exporter instead of Azure Monitor exporters. + /// + public bool UseOpenTelemetryCollector { get; set; } + + internal const string DefaultRuntimeSecretsDirectory = "/mnt/app-secrets"; + + /// + /// Directory containing runtime secrets JSON files. + /// + public string RuntimeSecretsDirectory { get; set; } = DefaultRuntimeSecretsDirectory; } diff --git a/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt b/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt index 532f63d4c..bb1cce3fd 100644 --- a/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt +++ b/test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt @@ -40,10 +40,12 @@ namespace Altinn.App.Core.Configuration public string RuntimeAppFileName { get; set; } public string RuntimeCookieName { get; set; } public string RuntimeCssFileName { get; set; } + public string RuntimeSecretsDirectory { get; set; } public string ServiceStylesConfigFileName { get; set; } public string TextFolder { get; set; } public string UiFolder { get; set; } public bool UseOpenTelemetry { get; set; } + public bool UseOpenTelemetryCollector { get; set; } public string ValidationConfigurationFileName { get; set; } } public class CacheSettings