fix(delmia-notifier)+chore(docker): review follow-ups — trim the env key like the file key, log which source supplied the key (never the key), hoist the not-configured message; README warns never to commit a filled-in appsettings.json; design-doc body lines updated; seed-delmia-api.sh no longer hides CLI errors and notes key names are not unique
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
This commit is contained in:
@@ -34,19 +34,41 @@ internal static class ConfigLoader
|
||||
return baseUrls.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
}
|
||||
|
||||
/// <summary>Resolve the API key: <c>SCADABRIDGE_API_KEY</c> wins when non-blank, otherwise <c>ScadaBridge:ApiKey</c> from <paramref name="cfg"/> (trimmed); null when neither is set. Env accessor is injected for testability.</summary>
|
||||
/// <summary>Human-readable name of the environment source, for diagnostics (never carries key material).</summary>
|
||||
public const string ApiKeySourceEnvironment = "environment (SCADABRIDGE_API_KEY)";
|
||||
|
||||
/// <summary>Human-readable name of the file source, for diagnostics (never carries key material).</summary>
|
||||
public const string ApiKeySourceFile = "appsettings.json (ScadaBridge:ApiKey)";
|
||||
|
||||
/// <summary>Resolve the API key: <c>SCADABRIDGE_API_KEY</c> wins when non-blank, otherwise <c>ScadaBridge:ApiKey</c> from <paramref name="cfg"/>; both trimmed; null when neither is set. Env accessor is injected for testability.</summary>
|
||||
/// <param name="envGet">Accessor used to read an environment variable by name.</param>
|
||||
/// <param name="cfg">The loaded configuration, consulted only when the environment variable is unset/blank.</param>
|
||||
/// <returns>The resolved API key, or <c>null</c> if neither source yields a non-blank value.</returns>
|
||||
public static string? ResolveApiKey(Func<string, string?> envGet, NotifierConfig cfg)
|
||||
public static string? ResolveApiKey(Func<string, string?> envGet, NotifierConfig cfg) =>
|
||||
ResolveApiKey(envGet, cfg, out _);
|
||||
|
||||
/// <summary>Same as <see cref="ResolveApiKey(Func{string, string?}, NotifierConfig)"/> but also reports which source supplied the key, so a stale environment variable silently shadowing an edited file is diagnosable from the log.</summary>
|
||||
/// <param name="envGet">Accessor used to read an environment variable by name.</param>
|
||||
/// <param name="cfg">The loaded configuration, consulted only when the environment variable is unset/blank.</param>
|
||||
/// <param name="source"><see cref="ApiKeySourceEnvironment"/>, <see cref="ApiKeySourceFile"/>, or <c>null</c> when no source yielded a key.</param>
|
||||
/// <returns>The resolved API key, or <c>null</c> if neither source yields a non-blank value.</returns>
|
||||
public static string? ResolveApiKey(Func<string, string?> envGet, NotifierConfig cfg, out string? source)
|
||||
{
|
||||
var env = envGet(ApiKeyEnvVar);
|
||||
if (!string.IsNullOrWhiteSpace(env))
|
||||
{
|
||||
return env;
|
||||
source = ApiKeySourceEnvironment;
|
||||
return env.Trim();
|
||||
}
|
||||
|
||||
var file = cfg.ScadaBridge.ApiKey;
|
||||
return string.IsNullOrWhiteSpace(file) ? null : file.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(file))
|
||||
{
|
||||
source = ApiKeySourceFile;
|
||||
return file.Trim();
|
||||
}
|
||||
|
||||
source = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ internal static class Program
|
||||
/// <summary>Entry point: loads config, sends the recipe-download notification with failover across the configured base URLs, and reports the legacy YES/NO stdout contract.</summary>
|
||||
/// <param name="args">Command-line arguments parsed into the recipe download payload.</param>
|
||||
/// <returns>A task that resolves to the process exit code.</returns>
|
||||
/// <summary>Stdout reason + log line when neither key source is set; one literal so the two cannot drift.</summary>
|
||||
private const string ApiKeyNotConfigured = "API key not configured (SCADABRIDGE_API_KEY or ScadaBridge:ApiKey)";
|
||||
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
var stdout = Console.Out;
|
||||
@@ -39,13 +42,15 @@ internal static class Program
|
||||
return Reporter.Report(false, "no BaseUrls configured", stdout);
|
||||
}
|
||||
|
||||
var apiKey = ConfigLoader.ResolveApiKey(Environment.GetEnvironmentVariable, cfg);
|
||||
var apiKey = ConfigLoader.ResolveApiKey(Environment.GetEnvironmentVariable, cfg, out var apiKeySource);
|
||||
if (apiKey is null)
|
||||
{
|
||||
log.Write("API key not configured (SCADABRIDGE_API_KEY or ScadaBridge:ApiKey)");
|
||||
return Reporter.Report(false, "API key not configured (SCADABRIDGE_API_KEY or ScadaBridge:ApiKey)", stdout);
|
||||
log.Write(ApiKeyNotConfigured);
|
||||
return Reporter.Report(false, ApiKeyNotConfigured, stdout);
|
||||
}
|
||||
|
||||
log.Write("api key source: " + apiKeySource);
|
||||
|
||||
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(cfg.ScadaBridge.TimeoutSeconds) };
|
||||
var sender = new LoggingRecipeSender(new HttpRecipeSender(http, apiKey), log);
|
||||
|
||||
|
||||
@@ -88,12 +88,16 @@ Placed next to the exe (copied to the output on build/publish):
|
||||
|
||||
The inbound API key is sent as `X-API-Key`. It is resolved from **`SCADABRIDGE_API_KEY`** first
|
||||
(recommended for production — the key never touches disk), falling back to **`ScadaBridge:ApiKey`** in
|
||||
`appsettings.json` when the variable is unset or blank. If neither is set → `NO` +
|
||||
`appsettings.json` when the variable is unset or blank (a blank file value counts as unset too; both
|
||||
values are trimmed). The diagnostic log records which source supplied the key — never the key itself — so
|
||||
a stale environment variable silently shadowing an edited file is visible. If neither is set → `NO` +
|
||||
`API key not configured (SCADABRIDGE_API_KEY or ScadaBridge:ApiKey)`, exit `-1`, no HTTP attempt.
|
||||
|
||||
Set the variable on the machine/account that DELMIA runs under (e.g. a system/user environment
|
||||
variable, or the service account's environment). If you use the file instead, restrict its ACL to the
|
||||
account DELMIA runs under — the key is stored in plain text there.
|
||||
account DELMIA runs under — the key is stored in plain text there. Fill `ApiKey` in **only on the deployed
|
||||
copy**: the repo's `appsettings.json` is tracked and copied to the build output, so a key pasted there to
|
||||
test locally is one `git add` away from being committed. It must stay `""` in source control.
|
||||
|
||||
## Building & publishing (Native AOT, `win-x64`)
|
||||
|
||||
@@ -106,7 +110,8 @@ dotnet publish src/ZB.MOM.WW.ScadaBridge.DelmiaNotifier -c Release -r win-x64
|
||||
# → bin/Release/net10.0/win-x64/publish/WWNotifier.exe (+ appsettings.json)
|
||||
```
|
||||
|
||||
Copy `WWNotifier.exe` **and** `appsettings.json` to the Delmia host, set `SCADABRIDGE_API_KEY`, and
|
||||
Copy `WWNotifier.exe` **and** `appsettings.json` to the Delmia host, set `SCADABRIDGE_API_KEY` (or fill in
|
||||
`ScadaBridge:ApiKey` on that copy), and
|
||||
point `BaseUrls` at the central node(s).
|
||||
|
||||
> Managed `dotnet build` / `dotnet test` run cross-platform and are the development/CI gate; only the
|
||||
|
||||
Reference in New Issue
Block a user