Skip to content

chore: runtime configuration improvements#1661

Merged
martinothamar merged 2 commits intomainfrom
chore/runtime-config-imp
Feb 24, 2026
Merged

chore: runtime configuration improvements#1661
martinothamar merged 2 commits intomainfrom
chore/runtime-config-imp

Conversation

@martinothamar
Copy link
Contributor

@martinothamar martinothamar commented Feb 13, 2026

Description

  • Expose a setting that we can flip through our syncroot/runtime when we have an OTel collector in place
  • Make /mnt/app-secrets/ JSON files automatically added, so we can extend configuration over time without relying on app-lib changes for every little thing

Related Issue(s)

  • N/A

Verification

  • Your code builds clean without any errors or warnings
  • Manual testing done (required)
  • Relevant automated test added (if you find this hard, leave it and we'll help out)
  • All tests run green

Documentation

  • User documentation is updated with a separate linked PR in altinn-studio-docs. (if applicable)

Summary by CodeRabbit

  • New Features

    • Option to route telemetry via an OpenTelemetry collector or Azure Monitor.
    • Load runtime JSON configuration from a secrets directory with ordered override handling.
    • New configuration settings to control telemetry routing and runtime secrets location.
  • Improvements

    • Health-check requests are excluded from telemetry to reduce noise.
  • Tests

    • Added unit tests for runtime configuration file loading, ordering, and duplicate avoidance.

@martinothamar martinothamar added kind/chore other A PR that should be in release notes, but as a chore backport-ignore This PR is a new feature and should not be cherry-picked onto release branches labels Feb 13, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Adds runtime secrets JSON loading from a configurable directory and two AppSettings properties; modifies OpenTelemetry exporter selection and excludes /health from ASP.NET Core tracing; adds unit tests for runtime config loading behavior and updates public API verification.

Changes

Cohort / File(s) Summary
OpenTelemetry & AppSettings
src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs, src/Altinn.App.Core/Configuration/AppSettings.cs
Reads AppSettings:UseOpenTelemetryCollector, adds UseOpenTelemetryCollector and RuntimeSecretsDirectory (with DefaultRuntimeSecretsDirectory) to AppSettings, excludes /health from ASP.NET Core tracing, and selects Azure Monitor exporters when collector flag is false and AppInsights connection string exists; otherwise configures OTLP exporters.
Runtime configuration loading
src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs
Adds AddRuntimeConfigFiles to load JSON files from runtime secrets directory (from AppSettings:RuntimeSecretsDirectory, default /mnt/app-secrets), skips in Development, preserves ordering (non-override before override), avoids duplicate configuration sources, and enables reloadOnChange via a PhysicalFileProvider.
Tests
test/Altinn.App.Api.Tests/Extensions/WebHostBuilderExtensionsTests.cs
Adds tests verifying Development vs Production behavior, file ordering (non-override before override), and duplicate-prevention when files already exist; includes TestHostEnvironment and TempDirectory test helpers.
Public API verification
test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt
Updates public API verification to include new AppSettings properties RuntimeSecretsDirectory and UseOpenTelemetryCollector.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: introducing runtime configuration improvements including OpenTelemetry collector settings and automated JSON configuration loading from /mnt/app-secrets/.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/runtime-config-imp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@martinothamar martinothamar force-pushed the chore/runtime-config-imp branch from 91b5a85 to ea4d578 Compare February 13, 2026 12:24
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs (1)

262-272: ⚠️ Potential issue | 🟡 Minor

Silent OTLP fallback when neither collector nor AppInsights is configured.

When useOpenTelemetryCollector is false/null and appInsightsConnectionString is empty, the else branch activates the OTLP exporter targeting the default endpoint (localhost:4317). In a non-test environment without an OTel collector running, this will silently fail or produce connection errors.

Consider either:

  • Logging a warning when falling back to OTLP without an explicit opt-in, or
  • Skipping exporter registration entirely when no backend is configured.

This same pattern applies to metrics (Line 285) and logs (Line 307).

🧹 Nitpick comments (2)
src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs (2)

101-143: Two-pass approach for override ordering is clear and correct.

Non-override files load first (sorted), then override files load second (sorted), ensuring overrides always win regardless of alphabetical position. The existingJsonFilePaths check prevents double-loading files already registered (e.g., maskinporten settings). Well structured.

One edge-case note: any file whose name contains the substring "override" anywhere (e.g., no-override-here.json) will be treated as an override file. If this is intentional, consider documenting the naming convention; if not, a stricter check (e.g., suffix .override.json) would be more predictable.


68-77: Consider logging when the secrets directory is absent.

When the directory doesn't exist, the method silently returns. A debug/trace-level log here would help operators diagnose misconfigured mounts without adding noise in normal operation.

@martinothamar martinothamar force-pushed the chore/runtime-config-imp branch from ea4d578 to 9a6d1e1 Compare February 23, 2026 09:14
@martinothamar
Copy link
Contributor Author

/publish

@github-actions
Copy link

github-actions bot commented Feb 23, 2026

PR release:

⚙️ Building...
✅ Done!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs (1)

115-142: PhysicalFileProvider disposal — ownership is transferred to the configuration system.

Static analysis flagged lines 116 and 138 for missing Dispose on PhysicalFileProvider. In practice, the provider's lifecycle is managed by the IConfigurationRoot built from this builder, which disposes its sources/providers when the host shuts down. This is the same pattern used in AddMaskinportenSettingsFile (see src/Altinn.App.Core/Features/Maskinporten/Extensions/WebHostBuilderExtensions.cs line 23). The lazy ??= allocation ensures the provider is only created when at least one file is loaded.

If you want to silence the warning without altering behavior, you could suppress it locally, but this is a standard ASP.NET Core configuration pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs` around lines 115 -
142, Static analysis warns that the created PhysicalFileProvider instances
(created by the expression "secretsFileProvider ??= new
PhysicalFileProvider(secretsDirectory)" used in the calls to
configBuilder.AddJsonFile inside WebHostBuilderExtensions) are not disposed;
because ownership is intentionally transferred to the configuration system,
silence the analyzer locally by wrapping the allocation lines with the
appropriate pragma to disable the disposal warning (e.g. disable CA2000) around
the statements that contain "secretsFileProvider ??= new
PhysicalFileProvider(secretsDirectory)" (or alternatively apply a
SuppressMessage attribute scoped to the method) so the behavior stays the same
and the warning is suppressed.
🧹 Nitpick comments (2)
src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs (1)

238-238: Consider extracting the repeated exporter-selection condition into a local variable.

The condition useOpenTelemetryCollector is not true && !string.IsNullOrWhiteSpace(appInsightsConnectionString) is duplicated across traces, metrics, and logs blocks (lines 262, 285, 307).

♻️ Suggested refactor
         var useOpenTelemetryCollector = config.GetValue<bool?>("AppSettings:UseOpenTelemetryCollector");
+        var useAzureMonitorExporter = useOpenTelemetryCollector is not true
+            && !string.IsNullOrWhiteSpace(appInsightsConnectionString);

Then replace each occurrence:

-                if (useOpenTelemetryCollector is not true && !string.IsNullOrWhiteSpace(appInsightsConnectionString))
+                if (useAzureMonitorExporter)

Also applies to: 262-272, 285-295, 307-317

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs` at line 238,
Extract the repeated condition into a clearly named local boolean (e.g.,
useAppInsightsExporter) computed from the existing useOpenTelemetryCollector and
appInsightsConnectionString variables (e.g., useAppInsightsExporter =
useOpenTelemetryCollector is not true &&
!string.IsNullOrWhiteSpace(appInsightsConnectionString)); then replace the three
duplicated checks in the OpenTelemetry traces, metrics and logs configuration
blocks (the blocks referencing traces, metrics and logs in
ServiceCollectionExtensions/ServiceCollectionExtensions.cs) with this single
local variable to avoid duplication and improve readability.
src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs (1)

101-143: Consider partitioning files upfront to reduce iteration and duplication.

The two loops iterate the full jsonFiles array with inverted conditions and both repeat the existingJsonFilePaths.Contains check. You could partition once and iterate each group, reducing code duplication. This is a minor nit given the array is small in practice.

♻️ Possible simplification
-        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
-            );
-        }
+        var newFiles = jsonFiles
+            .Where(f => !existingJsonFilePaths.Contains(Path.GetFullPath(f)))
+            .Select(f => (FullPath: f, FileName: Path.GetFileName(f)))
+            .ToList();
+
+        // Load non-override files first, then overrides, so overrides take precedence
+        var ordered = newFiles
+            .Where(f => !f.FileName.Contains(overrideFileNameFragment, StringComparison.OrdinalIgnoreCase))
+            .Concat(newFiles.Where(f => f.FileName.Contains(overrideFileNameFragment, StringComparison.OrdinalIgnoreCase)));
+
+        foreach (var (_, jsonFileName) in ordered)
+        {
+            configBuilder.AddJsonFile(
+                provider: secretsFileProvider ??= new PhysicalFileProvider(secretsDirectory),
+                path: jsonFileName,
+                optional: true,
+                reloadOnChange: true
+            );
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs` around lines 101 -
143, The two foreach blocks over jsonFiles duplicate the Contains check and
AddJsonFile call; refactor by partitioning jsonFiles once into two groups (those
whose Path.GetFileName(...) contains overrideFileNameFragment and those that do
not) while filtering out existingJsonFilePaths via Path.GetFullPath, then
iterate each group and call configBuilder.AddJsonFile with the same provider
logic (secretsFileProvider ??= new PhysicalFileProvider(secretsDirectory)),
preserving optional: true and reloadOnChange: true; this reduces duplicated
checks and makes the intent around overrideFileNameFragment,
existingJsonFilePaths, secretsFileProvider, secretsDirectory, and
configBuilder.AddJsonFile clearer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs`:
- Around line 115-142: Static analysis warns that the created
PhysicalFileProvider instances (created by the expression "secretsFileProvider
??= new PhysicalFileProvider(secretsDirectory)" used in the calls to
configBuilder.AddJsonFile inside WebHostBuilderExtensions) are not disposed;
because ownership is intentionally transferred to the configuration system,
silence the analyzer locally by wrapping the allocation lines with the
appropriate pragma to disable the disposal warning (e.g. disable CA2000) around
the statements that contain "secretsFileProvider ??= new
PhysicalFileProvider(secretsDirectory)" (or alternatively apply a
SuppressMessage attribute scoped to the method) so the behavior stays the same
and the warning is suppressed.

---

Nitpick comments:
In `@src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs`:
- Line 238: Extract the repeated condition into a clearly named local boolean
(e.g., useAppInsightsExporter) computed from the existing
useOpenTelemetryCollector and appInsightsConnectionString variables (e.g.,
useAppInsightsExporter = useOpenTelemetryCollector is not true &&
!string.IsNullOrWhiteSpace(appInsightsConnectionString)); then replace the three
duplicated checks in the OpenTelemetry traces, metrics and logs configuration
blocks (the blocks referencing traces, metrics and logs in
ServiceCollectionExtensions/ServiceCollectionExtensions.cs) with this single
local variable to avoid duplication and improve readability.

In `@src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs`:
- Around line 101-143: The two foreach blocks over jsonFiles duplicate the
Contains check and AddJsonFile call; refactor by partitioning jsonFiles once
into two groups (those whose Path.GetFileName(...) contains
overrideFileNameFragment and those that do not) while filtering out
existingJsonFilePaths via Path.GetFullPath, then iterate each group and call
configBuilder.AddJsonFile with the same provider logic (secretsFileProvider ??=
new PhysicalFileProvider(secretsDirectory)), preserving optional: true and
reloadOnChange: true; this reduces duplicated checks and makes the intent around
overrideFileNameFragment, existingJsonFilePaths, secretsFileProvider,
secretsDirectory, and configBuilder.AddJsonFile clearer.
ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea4d578 and 9a6d1e1.

📒 Files selected for processing (5)
  • src/Altinn.App.Api/Extensions/ServiceCollectionExtensions.cs
  • src/Altinn.App.Api/Extensions/WebHostBuilderExtensions.cs
  • src/Altinn.App.Core/Configuration/AppSettings.cs
  • test/Altinn.App.Api.Tests/Extensions/WebHostBuilderExtensionsTests.cs
  • test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/Altinn.App.Core.Tests/PublicApiTests.PublicApi_ShouldNotChange_Unintentionally.verified.txt
  • test/Altinn.App.Api.Tests/Extensions/WebHostBuilderExtensionsTests.cs

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
52.1% Condition Coverage on New Code (required ≥ 65%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-ignore This PR is a new feature and should not be cherry-picked onto release branches kind/chore other A PR that should be in release notes, but as a chore

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants