Skip to content

[BUG] Docker instance with leading/trailing spaces in Name is synchronized but never scanned or sampled #266

Open

Description

@LarsLaskowski

Summary

The configuration synchronization stores a Docker instance under its trimmed Name, but the runtime container scan and the resource-statistics collection look the stored instance up in a dictionary keyed by the untrimmed configured Name. A configured name with leading or trailing whitespace (e.g. "Name": "Production ") is therefore created in the database, passes validation, but is skipped on every scan and every sampling round as "configuration missing". Both sides must use the same, trimmed name.

Area

  • docs/areas/docker-instances.md (Configured instances, Container snapshots, Resource statistics)
  • Security area: None

Current behavior

Synchronization trims the name, both when creating/matching and when computing obsolete instances:

  • src/DockerUpdateGuard/Images/InstanceDiscoveryService.cs:91 – var configuredInstanceName = configuredInstance.Name.Trim(); and Name = configuredInstanceName (line 100)
  • src/DockerUpdateGuard/Images/InstanceDiscoveryService.cs:169 – configuredInstances.Select(instance => instance.Name.Trim())

The validator also compares trimmed names for duplicates (src/DockerUpdateGuard/Configuration/DockerUpdateGuardOptionsValidator.cs:283).

The consumers build their lookup from the raw configured names and query it with the stored (trimmed) name:

// RuntimeContainerScanOrchestrator.cs:1179 / ResourceStatisticsCollector.cs:226
var optionsByName = _optionsMonitor.CurrentValue.DockerInstances.ToDictionary(entity => entity.Name, StringComparer.OrdinalIgnoreCase);
...
if (optionsByName.TryGetValue(dockerInstance.Name, out var configuredInstance) == false)
  • src/DockerUpdateGuard/Images/RuntimeContainerScanOrchestrator.cs:1179 and :1197-1204 – the instance is counted as skipped and RuntimeContainerScanSkippedConfigurationMissing is logged.
  • src/DockerUpdateGuard/Images/ResourceStatisticsCollector.cs:226 and :233-237 – the instance is silently skipped.

Reproduction:

  1. Configure DockerUpdateGuard:DockerInstances:0:Name = "Production " (trailing space) with a reachable BaseUrl.
  2. Start the application. Validation passes; the Docker Instances page lists Production.
  3. Every runtime container scan logs "configuration missing" for Production and records no scan run; no resource samples are ever written. The instance stays empty forever.

Secondary effect of the same inconsistency: the untrimmed name is also passed into DockerInstanceClient (src/DockerUpdateGuard/Docker/DockerInstanceClient.cs:251 connection-pool key, :289 the once-per-instance SkipCertificateValidation warning) and into Portainer log messages and error texts, so logs and messages show a different spelling than the UI.

Expected behavior

  • A Docker instance is identified everywhere by its trimmed Name, compared case-insensitively (as docs/areas/docker-instances.md already states).
  • A configured instance whose Name has surrounding whitespace is scanned and sampled exactly like the same instance without the whitespace.
  • Log entries, scan-run messages and integration error messages use the trimmed name.

Acceptance criteria

  • A configured instance with Name = " Production " is scanned by RuntimeContainerScanOrchestrator.ScanAllAsync (one scan run recorded, no "configuration missing" skip).
  • The same instance gets resource samples from ResourceStatisticsCollector.CollectAsync.
  • Unit tests (MSTest, naming per docs/UNIT_TESTS.md), for example:
    • RuntimeContainerScanOrchestratorScanAllWithUntrimmedConfiguredNameScansInstance
    • ResourceStatisticsCollectorCollectWithUntrimmedConfiguredNameRecordsSamples (there are no tests for ResourceStatisticsCollector yet; add the test class)
    • if the fix normalizes options centrally: a test that the bound/post-configured options carry the trimmed name.
  • Existing behavior for names without whitespace is unchanged (existing tests stay green).
  • The area document docs/areas/docker-instances.md describes the new behavior and its Known gaps entry for this issue (currently Open questions item 1) is removed.

Implementation notes

Options (recommendation first):

  1. Recommended – normalize once at the options boundary. Add a PostConfigure<DockerUpdateGuardOptions> (in src/DockerUpdateGuard/ServiceCollectionExtensions.cs, next to AddOptions<DockerUpdateGuardOptions>() at line 54) that trims Name of every DockerInstanceOptions. Every consumer (InstanceDiscoveryService, both lookups, DockerInstanceClient, PortainerClient, logging) then sees the same name, and the explicit .Trim() calls in InstanceDiscoveryService become redundant (they may stay). Post-configure runs on every reload, so a reloaded configuration is normalized too. Make sure the validator still sees an empty/whitespace-only name as missing (it does, IsNullOrWhiteSpace).
  2. Alternative – build both dictionaries with entity => entity.Name.Trim(). Smaller, but leaves the untrimmed name in DockerInstanceClient and Portainer messages and keeps the trimming rule scattered.

Pitfalls:

  • ToDictionary throws on duplicate keys; with trimmed keys, two entries differing only by whitespace would collide — the validator already rejects that as a duplicate, keep it that way.
  • Keep the case-insensitive comparison (StringComparer.OrdinalIgnoreCase).

No configuration, README or migration impact. Must not change: the synchronization semantics (removal of an instance no longer configured), the guarantees in .squad/project.md.

Related


Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions