dd0a846b64
Secrets were per-node SQLite, so a secret written on one node was invisible to the rest of a cluster. G-7's design resolved the "shared SQL store vs Akka replicator" fork to build only the former; both are built here so the choice is a deployment decision (availability vs partition tolerance) rather than a library limitation. Two new packages — ZB.MOM.WW.Secrets.Replicator.SqlServer (shared store, plus a local-store-with-hub mode) and .Replicator.AkkaDotNet (peer-to-peer over distributed pub/sub). Core gains ISecretsStoreMigrator, one shared SecretLastWriterWins predicate so no two stores can disagree on a tie, the transport-agnostic reconciler, and ReplicatingSecretStore — which closes a real gap: nothing had ever called ISecretReplicator.PublishAsync, so the seam was inert and local writes would not have propagated at all. Verified 182 pass / 1 skip / 0 warnings, including 15 live tests against a real SQL Server 2022 (the SQLite suite ported case-for-case, so any behavioural divergence between the stores fails) and a 9-test in-process 2-node Akka cluster over real remoting. A post-build review caught six defects, all fixed and now covered: both replication modes could not resolve from the container (no test had built one), an unbounded fetch that broke past SQL Server's 2100-parameter cap, a poison row that aborted the rest of its batch forever, Enum.Parse on peer input that could restart the actor in a loop, null crypto blobs crossing the trust boundary, and a silently dropped pull-read failure. Packed at 0.2.0 and vulnerability-scanned clean; not yet published to the feed. Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
58 lines
2.3 KiB
C#
58 lines
2.3 KiB
C#
using Microsoft.Data.SqlClient;
|
|
|
|
namespace ZB.MOM.WW.Secrets.Replicator.SqlServer;
|
|
|
|
/// <summary>
|
|
/// Opens connections to the shared SQL-Server secret database and carries the settings every
|
|
/// command needs (schema name, command timeout).
|
|
/// </summary>
|
|
/// <param name="options">The validated SQL-Server store options.</param>
|
|
public sealed class SecretsSqlServerConnectionFactory(SqlServerSecretsOptions options)
|
|
{
|
|
private readonly SqlServerSecretsOptions _options =
|
|
options ?? throw new ArgumentNullException(nameof(options));
|
|
|
|
/// <summary>The bracket-quoted, allow-list-validated schema prefix for object names.</summary>
|
|
public string SchemaName => _options.SchemaName;
|
|
|
|
/// <summary>Command timeout, in whole seconds, for every command issued against this store.</summary>
|
|
public int CommandTimeoutSeconds => Math.Max(1, (int)_options.CommandTimeout.TotalSeconds);
|
|
|
|
/// <summary>Opens a connection to the shared secret database.</summary>
|
|
/// <param name="cancellationToken">A token to cancel the connect.</param>
|
|
/// <returns>An opened connection. The caller owns disposal.</returns>
|
|
public async Task<SqlConnection> OpenConnectionAsync(CancellationToken cancellationToken)
|
|
{
|
|
var connection = new SqlConnection(_options.ConnectionString);
|
|
try
|
|
{
|
|
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
return connection;
|
|
}
|
|
catch
|
|
{
|
|
await connection.DisposeAsync().ConfigureAwait(false);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a command on <paramref name="connection"/> with the configured timeout applied.
|
|
/// </summary>
|
|
/// <param name="connection">The open connection.</param>
|
|
/// <param name="commandText">The command text.</param>
|
|
/// <param name="transaction">The ambient transaction, if any.</param>
|
|
/// <returns>The configured command. The caller owns disposal.</returns>
|
|
public SqlCommand CreateCommand(
|
|
SqlConnection connection, string commandText, SqlTransaction? transaction = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(connection);
|
|
|
|
SqlCommand command = connection.CreateCommand();
|
|
command.CommandText = commandText;
|
|
command.CommandTimeout = CommandTimeoutSeconds;
|
|
command.Transaction = transaction;
|
|
return command;
|
|
}
|
|
}
|