Skip to content

fix(plumbing): stop fallback factories overtaking specific ones - #420

Draft
james-nesbitt wants to merge 1 commit into
k0sproject:mainfrom
james-nesbitt:fix/provider-fallback-ordering
Draft

fix(plumbing): stop fallback factories overtaking specific ones#420
james-nesbitt wants to merge 1 commit into
k0sproject:mainfrom
james-nesbitt:fix/provider-fallback-ordering

Conversation

@james-nesbitt

Copy link
Copy Markdown
Contributor

What

Adds Provider.RegisterFallback for factories that match a superset of another factory's inputs, and registers ResolveLinuxCompat through it.

Why

Provider.Get promotes a winning factory to the front of the list to speed up later lookups:

if i != 0 {
    p.factories[0], p.factories[i] = p.factories[i], p.factories[0]
}

That is only sound while no factory matches a superset of another's inputs — and os.DefaultRegistry violates it.

The registry is [ResolveLinux, ResolveLinuxCompat, ResolveWindows, ResolveDarwin]. ResolveLinuxCompat is a last-resort resolver for hosts with no os-release file, and it accepts any Linux host, reporting ID: "linux" with no version.

Resolving a Windows host swaps index 0 with index 2:

order after
initial [Linux, LinuxCompat, Windows, Darwin]
after one Windows host [Windows, LinuxCompat, Linux, Darwin]

ResolveLinuxCompat now sits ahead of ResolveLinux, so every Linux host resolved afterwards is reported as linux rather than its real distribution. DefaultRegistry is a process-global built with sync.OnceValue, so this persists for the life of the process.

Any consumer managing a mixed Linux/Windows fleet is affected. It is order-dependent, so it presents as Linux-only runs working while mixed runs fail.

How this showed up

Found while migrating Mirantis/launchpad from rig v0 to v2. Linux hosts failed configurer lookup with unsupported OS: linux, but only in clusters that also contained a Windows host — the Windows host resolved first and reordered the registry.

Worth noting the failure is hard to diagnose from the outside: the consumer sees a plausible-looking Release with ID: "linux", not an error. In our case that cost a full CI run to track down. I also verified against a live host that ResolveLinux itself was working correctly (20/20 runs) before looking at ordering.

Approach

RegisterFallback keeps fallbacks in a separate slice, consulted only after every Registered factory has declined. They are never reordered or promoted, so registration order between them is preserved.

I chose this over removing the promotion optimisation, since the optimisation is worthwhile and the real problem is that a superset factory was registered as if it were a peer. It is additive — no change for existing Register callers.

GetAll also consults fallbacks, after the ordinary factories.

Testing

Both tests were confirmed to fail without the change:

  • plumbing: TestRegisterFallbackIsNeverPromoted registers the superset factory between the two specific ones, which is the position that actually breaks. With the fallback treated as an ordinary factory it returns fallback where specific is expected.
  • os: TestDefaultRegistryOrderingIsStable reproduces the real sequence — resolve a Windows host, then assert a Linux host is still ubuntu/22.04. Without the fix: got ID "linux" version "".
  • TestDefaultRegistryStillFallsBackToCompat confirms the fallback is still reached for a host with no os-release file, which is the case it exists for.
  • Full suite passes. (gofmt -l reports 8 files on this repo, all pre-existing on main and untouched here.)

Happy to adjust naming or reshape this if you would prefer a different mechanism — e.g. a priority argument on Register, or simply dropping the promotion.

Written by AI: claude-sonnet-5

Provider.Get promotes a winning factory to the front of the list to speed
up later lookups. That is only sound while no factory matches a superset
of another's inputs, and DefaultRegistry violates it.

os.DefaultRegistry registers [ResolveLinux, ResolveLinuxCompat,
ResolveWindows, ResolveDarwin]. ResolveLinuxCompat is a last-resort
resolver for hosts with no os-release file, and it accepts any Linux host,
reporting ID "linux" with no version.

Resolving a Windows host swaps index 0 with index 2, leaving [Windows,
LinuxCompat, Linux, Darwin]. The compat resolver now sits ahead of
ResolveLinux, so every Linux host resolved afterwards is reported as
"linux" instead of its real distribution. Because the registry is a
process-global built with sync.OnceValue, that persists for the life of
the process.

Any consumer managing a mixed Linux/Windows fleet hits this: the Linux
hosts are identified correctly until the first Windows host is resolved,
and incorrectly from then on. It is order-dependent, so it presents as
Linux-only runs working while mixed runs fail.

Add Provider.RegisterFallback for factories that match a superset of
another's inputs. Fallbacks are consulted only after every Register'ed
factory has declined, and are never reordered or promoted, so a superset
factory can no longer answer in place of the specific one it backs up.
Register ResolveLinuxCompat through it.

Both tests fail without the change. The plumbing test registers the
superset factory between the two specific ones, which is the position that
breaks, and the os test reproduces the real sequence: resolve a Windows
host, then check a Linux host is still identified correctly.

Written by AI: claude-sonnet-5

Signed-off-by: James Nesbitt <jnesbitt@mirantis.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant