fix(site-secrets): mirror secrets before pushing artifact data connections to the DCL
Final review M2: HandleDeployArtifacts now sends ApplyArtifactDataConnectionsToDcl after the shipped secrets are written and mirrored (from a finally, so the push still goes out if the secrets step fails), so a connection created by the same deploy resolves a newly shipped secret on first connect. Definitions are still stored at the same point; response semantics unchanged.
This commit is contained in:
+58
-2
@@ -1,4 +1,5 @@
|
||||
using Akka.Actor;
|
||||
using Akka.TestKit;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
@@ -47,7 +48,7 @@ public class DeploymentManagerActorArtifactSecretsTests : TestKit, IDisposable
|
||||
TestLocalDb.DeleteFiles(path);
|
||||
}
|
||||
|
||||
private IActorRef CreateDeploymentManager(DeployedSecretsMirror? secretsMirror = null)
|
||||
private IActorRef CreateDeploymentManager(DeployedSecretsMirror? secretsMirror = null, IActorRef? dclManager = null)
|
||||
{
|
||||
var options = new SiteRuntimeOptions();
|
||||
// Positional, not named, past the constructor's first six parameters: a named
|
||||
@@ -62,7 +63,7 @@ public class DeploymentManagerActorArtifactSecretsTests : TestKit, IDisposable
|
||||
null, // streamManager: no stream manager in tests
|
||||
options,
|
||||
NullLogger<DeploymentManagerActor>.Instance,
|
||||
null, // dclManager
|
||||
dclManager,
|
||||
null, // healthCollector
|
||||
null, // serviceProvider
|
||||
null, // loggerFactory
|
||||
@@ -245,6 +246,61 @@ public class DeploymentManagerActorArtifactSecretsTests : TestKit, IDisposable
|
||||
Assert.Null(r2.ErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataConnections_PushedToDcl_OnlyAfterShippedSecretsAreMirrored()
|
||||
{
|
||||
// M2 (final review): a connection whose config references a secret shipped by THIS
|
||||
// deploy must find it on its first connect, so the DCL create must not reach the
|
||||
// DCL manager before the secret is in this node's store.
|
||||
var store = new FakeSecretStore();
|
||||
var resolver = new FakeSecretResolver();
|
||||
var mirror = CreateMirror(store, resolver);
|
||||
var dcl = CreateTestProbe();
|
||||
bool? secretPresentWhenDclSawCreate = null;
|
||||
dcl.SetAutoPilot(new DelegateAutoPilot((_, msg) =>
|
||||
{
|
||||
if (msg is ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection.CreateConnectionCommand)
|
||||
{
|
||||
secretPresentWhenDclSawCreate ??= store.Contains("gw-key");
|
||||
}
|
||||
|
||||
return AutoPilot.KeepRunning;
|
||||
}));
|
||||
var manager = CreateDeploymentManager(mirror, dcl.Ref);
|
||||
|
||||
var command = ArtifactsCommand("dep-order", secrets: [Art("gw-key", 1)]) with
|
||||
{
|
||||
DataConnections = [new DataConnectionArtifact("Gw", "OpcUa", "{}", null, 3)],
|
||||
};
|
||||
manager.Tell(command);
|
||||
|
||||
var response = ExpectMsg<ArtifactDeploymentResponse>(TimeSpan.FromSeconds(10));
|
||||
Assert.True(response.Success);
|
||||
Assert.Equal(1, response.SecretsApplied);
|
||||
AwaitCondition(() => secretPresentWhenDclSawCreate.HasValue, TimeSpan.FromSeconds(10));
|
||||
Assert.True(secretPresentWhenDclSawCreate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DataConnections_StillStoredAndPushed_WhenSecretsStepFails()
|
||||
{
|
||||
// The reorder must not lose the connection push when the secrets step fails.
|
||||
var dcl = CreateTestProbe();
|
||||
var manager = CreateDeploymentManager(secretsMirror: null, dclManager: dcl.Ref);
|
||||
|
||||
var command = ArtifactsCommand("dep-order-fail", secrets: [Art("gw-key", 1)]) with
|
||||
{
|
||||
DataConnections = [new DataConnectionArtifact("Gw", "OpcUa", "{}", null, 3)],
|
||||
};
|
||||
manager.Tell(command);
|
||||
|
||||
var response = ExpectMsg<ArtifactDeploymentResponse>(TimeSpan.FromSeconds(10));
|
||||
Assert.False(response.Success);
|
||||
dcl.ExpectMsg<ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection.CreateConnectionCommand>(
|
||||
TimeSpan.FromSeconds(10));
|
||||
Assert.Contains(await _storage.GetAllDataConnectionDefinitionsAsync(), d => d.Name == "Gw");
|
||||
}
|
||||
|
||||
// ── Fakes (mirrors DeployedSecretsMirrorTests' fakes; kept local/minimal per-task) ──
|
||||
|
||||
private sealed class FakeSecretStore : ISecretStore
|
||||
|
||||
Reference in New Issue
Block a user