diff --git a/Directory.Packages.props b/Directory.Packages.props
index 2623d82..fe2305d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,8 +3,8 @@
true
-
-
+
+
@@ -22,7 +22,7 @@
-
+
diff --git a/src/DockerUpdateGuard/Images/RuntimeContainerScanOrchestrator.cs b/src/DockerUpdateGuard/Images/RuntimeContainerScanOrchestrator.cs
index 63d32be..0b943b1 100644
--- a/src/DockerUpdateGuard/Images/RuntimeContainerScanOrchestrator.cs
+++ b/src/DockerUpdateGuard/Images/RuntimeContainerScanOrchestrator.cs
@@ -934,7 +934,7 @@ private async Task CreateDerivedBaseRuntimeFindingAsync(RuntimeFindingContext fi
var imageReferenceOrId = string.IsNullOrWhiteSpace(container.LocalImageId)
? container.ImageReference
- : container.LocalImageId!;
+ : container.LocalImageId;
var historyResult = await _dockerInstanceClient.GetImageHistoryAsync(configuredInstance,
imageReferenceOrId,
cancellationToken)
@@ -1073,7 +1073,7 @@ private async Task CreateNginxRuntimeFindingAsync(RuntimeFindingContext findingC
{
var imageReferenceOrId = string.IsNullOrWhiteSpace(container.LocalImageId)
? container.ImageReference
- : container.LocalImageId!;
+ : container.LocalImageId;
var inspectResult = await _dockerInstanceClient.InspectImageAsync(configuredInstance,
imageReferenceOrId,
cancellationToken)
diff --git a/src/DockerUpdateGuard/Portainer/PortainerClient.cs b/src/DockerUpdateGuard/Portainer/PortainerClient.cs
index 8354975..7f5d627 100644
--- a/src/DockerUpdateGuard/Portainer/PortainerClient.cs
+++ b/src/DockerUpdateGuard/Portainer/PortainerClient.cs
@@ -116,7 +116,8 @@ public PortainerClient(IHttpClientFactory httpClientFactory, ILogger(cancellationToken: cancellationToken).ConfigureAwait(false);
return containers?.FirstOrDefault(c => c.Names?.Any(n => string.Equals(n, $"/{containerName}", StringComparison.OrdinalIgnoreCase)
- || string.Equals(n, containerName, StringComparison.OrdinalIgnoreCase)) == true)?.Id;
+ || string.Equals(n, containerName, StringComparison.OrdinalIgnoreCase)) == true)
+ ?.Id;
}
#endregion // Static methods
@@ -149,7 +150,7 @@ private async Task CreateAuthenticatedClientAsync(PortainerOptions o
if (string.IsNullOrWhiteSpace(options.Username) == false
&& string.IsNullOrWhiteSpace(options.Password) == false)
{
- var loginBody = new PortainerLoginRequest(options.Username!, options.Password!);
+ var loginBody = new PortainerLoginRequest(options.Username, options.Password);
using var loginResponse = await client.PostAsJsonAsync("/api/auth", loginBody, cancellationToken).ConfigureAwait(false);
if (loginResponse.IsSuccessStatusCode)
diff --git a/src/Tests/DockerUpdateGuard.Tests/ImageScanOrchestratorTests.cs b/src/Tests/DockerUpdateGuard.Tests/ImageScanOrchestratorTests.cs
index c5454c6..afcc5b3 100644
--- a/src/Tests/DockerUpdateGuard.Tests/ImageScanOrchestratorTests.cs
+++ b/src/Tests/DockerUpdateGuard.Tests/ImageScanOrchestratorTests.cs
@@ -1153,113 +1153,114 @@ await seedContext.SaveChangesAsync(TestContext.CancellationToken)
var dbContext2 = database.CreateDbContext();
await using (dbContext1.ConfigureAwait(false))
-
- await using (dbContext2.ConfigureAwait(false))
{
- var imageCatalogRepository1 = new ImageCatalogRepository(dbContext1);
- var imageCatalogRepository2 = new ImageCatalogRepository(dbContext2);
- var imageReferenceParser = new ImageReferenceParser();
- var baseImageResolver = Substitute.For();
- var derivedBaseRuntimeDetector = Substitute.For();
- var dotNetReleaseMetadataService = Substitute.For();
- var nginxReleaseMetadataService = Substitute.For();
- var registryMetadataService = Substitute.For();
- var firstResolveStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- var releaseFirstResolve = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- var resolveCallCount = 0;
- var baseImageDescriptors = ExternalOperationResult>.Succeeded([
- new BaseImageDescriptor
- {
- Registry = "docker.io",
- Repository = "library/debian",
- Tag = "12.0.0",
- Digest = "sha256:base",
- Depth = 1,
- SourceReference = "FROM debian:12.0.0",
- },
- ]);
-
- baseImageResolver.ResolveAsync(Arg.Any(), Arg.Any())
- .Returns(async _ =>
- {
- var callIndex = Interlocked.Increment(ref resolveCallCount);
-
- if (callIndex == 1)
+ await using (dbContext2.ConfigureAwait(false))
+ {
+ var imageCatalogRepository1 = new ImageCatalogRepository(dbContext1);
+ var imageCatalogRepository2 = new ImageCatalogRepository(dbContext2);
+ var imageReferenceParser = new ImageReferenceParser();
+ var baseImageResolver = Substitute.For();
+ var derivedBaseRuntimeDetector = Substitute.For();
+ var dotNetReleaseMetadataService = Substitute.For();
+ var nginxReleaseMetadataService = Substitute.For();
+ var registryMetadataService = Substitute.For();
+ var firstResolveStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var releaseFirstResolve = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var resolveCallCount = 0;
+ var baseImageDescriptors = ExternalOperationResult>.Succeeded([
+ new BaseImageDescriptor
+ {
+ Registry = "docker.io",
+ Repository = "library/debian",
+ Tag = "12.0.0",
+ Digest = "sha256:base",
+ Depth = 1,
+ SourceReference = "FROM debian:12.0.0",
+ },
+ ]);
+
+ baseImageResolver.ResolveAsync(Arg.Any(), Arg.Any())
+ .Returns(async _ =>
{
- firstResolveStarted.TrySetResult(true);
- await releaseFirstResolve.Task.ConfigureAwait(false);
- }
-
- return baseImageDescriptors;
- });
- registryMetadataService.GetTagAsync(Arg.Any(), cancellationToken: Arg.Any())
- .Returns(callInfo =>
- {
- var imageReference = callInfo.ArgAt(0);
+ var callIndex = Interlocked.Increment(ref resolveCallCount);
+
+ if (callIndex == 1)
+ {
+ firstResolveStarted.TrySetResult(true);
+ await releaseFirstResolve.Task.ConfigureAwait(false);
+ }
+
+ return baseImageDescriptors;
+ });
+ registryMetadataService.GetTagAsync(Arg.Any(), cancellationToken: Arg.Any())
+ .Returns(callInfo =>
+ {
+ var imageReference = callInfo.ArgAt(0);
+
+ return ExternalOperationResult.Succeeded(new DockerHubTagData
+ {
+ Tag = imageReference.Tag,
+ Digest = imageReference.Digest,
+ PublishedAtUtc = new DateTimeOffset(2025, 06, 01, 12, 00, 00, TimeSpan.Zero),
+ });
+ });
+ registryMetadataService.GetImageConfigurationAsync(Arg.Any(), Arg.Any())
+ .Returns(ExternalOperationResult.NotFound("No registry config"));
+
+ var orchestrator1 = new ImageScanOrchestrator(new ApplicationTelemetry(),
+ baseImageResolver,
+ dbContext1,
+ derivedBaseRuntimeDetector,
+ dotNetReleaseMetadataService,
+ nginxReleaseMetadataService,
+ imageCatalogRepository1,
+ imageReferenceParser,
+ NullLogger.Instance,
+ registryMetadataService);
+ var orchestrator2 = new ImageScanOrchestrator(new ApplicationTelemetry(),
+ baseImageResolver,
+ dbContext2,
+ derivedBaseRuntimeDetector,
+ dotNetReleaseMetadataService,
+ nginxReleaseMetadataService,
+ imageCatalogRepository2,
+ imageReferenceParser,
+ NullLogger.Instance,
+ registryMetadataService);
+ var observedImageId = await dbContext1.ObservedImages.Select(entity => entity.Id)
+ .SingleAsync(TestContext.CancellationToken)
+ .ConfigureAwait(false);
+
+ var firstScanTask = orchestrator1.ScanAsync(observedImageId, ScanTriggerSource.Scheduled, CancellationToken.None);
+
+ await firstResolveStarted.Task.ConfigureAwait(false);
+
+ var secondScanTask = orchestrator2.ScanAsync(observedImageId, ScanTriggerSource.Scheduled, CancellationToken.None);
+
+ await Task.Delay(100, TestContext.CancellationToken).ConfigureAwait(false);
- return ExternalOperationResult.Succeeded(new DockerHubTagData
- {
- Tag = imageReference.Tag,
- Digest = imageReference.Digest,
- PublishedAtUtc = new DateTimeOffset(2025, 06, 01, 12, 00, 00, TimeSpan.Zero),
- });
- });
- registryMetadataService.GetImageConfigurationAsync(Arg.Any(), Arg.Any())
- .Returns(ExternalOperationResult.NotFound("No registry config"));
-
- var orchestrator1 = new ImageScanOrchestrator(new ApplicationTelemetry(),
- baseImageResolver,
- dbContext1,
- derivedBaseRuntimeDetector,
- dotNetReleaseMetadataService,
- nginxReleaseMetadataService,
- imageCatalogRepository1,
- imageReferenceParser,
- NullLogger.Instance,
- registryMetadataService);
- var orchestrator2 = new ImageScanOrchestrator(new ApplicationTelemetry(),
- baseImageResolver,
- dbContext2,
- derivedBaseRuntimeDetector,
- dotNetReleaseMetadataService,
- nginxReleaseMetadataService,
- imageCatalogRepository2,
- imageReferenceParser,
- NullLogger.Instance,
- registryMetadataService);
- var observedImageId = await dbContext1.ObservedImages.Select(entity => entity.Id)
- .SingleAsync(TestContext.CancellationToken)
- .ConfigureAwait(false);
-
- var firstScanTask = orchestrator1.ScanAsync(observedImageId, ScanTriggerSource.Scheduled, CancellationToken.None);
-
- await firstResolveStarted.Task.ConfigureAwait(false);
-
- var secondScanTask = orchestrator2.ScanAsync(observedImageId, ScanTriggerSource.Scheduled, CancellationToken.None);
-
- await Task.Delay(100, TestContext.CancellationToken).ConfigureAwait(false);
-
- Assert.AreEqual(1,
- Volatile.Read(ref resolveCallCount),
- "Concurrent scans for the same observed image must wait for the in-flight scan before starting base-image resolution");
+ Assert.AreEqual(1,
+ Volatile.Read(ref resolveCallCount),
+ "Concurrent scans for the same observed image must wait for the in-flight scan before starting base-image resolution");
- releaseFirstResolve.SetResult(true);
+ releaseFirstResolve.SetResult(true);
- await Task.WhenAll(firstScanTask, secondScanTask).ConfigureAwait(false);
+ await Task.WhenAll(firstScanTask, secondScanTask).ConfigureAwait(false);
- var assertionContext = database.CreateDbContext();
+ var assertionContext = database.CreateDbContext();
- await using (assertionContext.ConfigureAwait(false))
- {
- var relationshipCount = await assertionContext.ImageRelationships.CountAsync(TestContext.CancellationToken).ConfigureAwait(false);
- var scanRunCount = await assertionContext.ScanRuns.CountAsync(TestContext.CancellationToken).ConfigureAwait(false);
+ await using (assertionContext.ConfigureAwait(false))
+ {
+ var relationshipCount = await assertionContext.ImageRelationships.CountAsync(TestContext.CancellationToken).ConfigureAwait(false);
+ var scanRunCount = await assertionContext.ScanRuns.CountAsync(TestContext.CancellationToken).ConfigureAwait(false);
- Assert.AreEqual(1,
- relationshipCount,
- "Serialized scans must leave a single current base-image relationship for the observed image");
- Assert.AreEqual(2,
- scanRunCount,
- "Both serialized scans must complete and persist their scan runs");
+ Assert.AreEqual(1,
+ relationshipCount,
+ "Serialized scans must leave a single current base-image relationship for the observed image");
+ Assert.AreEqual(2,
+ scanRunCount,
+ "Both serialized scans must complete and persist their scan runs");
+ }
}
}
}
diff --git a/src/Tests/DockerUpdateGuard.Tests/NavMenuTests.cs b/src/Tests/DockerUpdateGuard.Tests/NavMenuTests.cs
index 6db541e..28b927b 100644
--- a/src/Tests/DockerUpdateGuard.Tests/NavMenuTests.cs
+++ b/src/Tests/DockerUpdateGuard.Tests/NavMenuTests.cs
@@ -136,7 +136,7 @@ public async Task NavMenuRendersVulnerabilitiesNavigationEntry()
var vulnerabilitiesLink = component.FindAll("a").SingleOrDefault(link => link.TextContent.Trim() == "Vulnerabilities");
Assert.IsNotNull(vulnerabilitiesLink, "The Vulnerabilities navigation entry must always be rendered");
- Assert.IsTrue(vulnerabilitiesLink!.GetAttribute("href")?.Contains("vulnerabilities", StringComparison.OrdinalIgnoreCase),
+ Assert.IsTrue(vulnerabilitiesLink.GetAttribute("href")?.Contains("vulnerabilities", StringComparison.OrdinalIgnoreCase),
"The Vulnerabilities navigation entry must link to the vulnerabilities page");
}
}