fix: emit Networks and Addresses in deterministic order#744
Conversation
c20b526 to
f28de76
Compare
There was a problem hiding this comment.
Pull request overview
This PR makes the Networks and Addresses slices exposed to templates deterministic by sorting them, eliminating output churn caused by Go map iteration randomness and making index .Networks 0 stable across regenerations (fixing the instability described in #431 and #169).
Changes:
- Sort container
Addressesdeterministically inGetContainerAddresses(numeric port order, then proto/host-port/host-ip/ip). - Sort container
Networksdeterministically byNameimmediately after collecting them from Docker’s network map. - Add targeted, order-sensitive unit tests and document the new ordering behavior (including how to select a network by name).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents deterministic ordering for Addresses/Networks and shows how to select a network by name. |
| internal/generator/generator.go | Adds sortNetworks and applies it to stabilize runtimeContainer.Networks ordering. |
| internal/generator/generator_test.go | Adds TestSortNetworks to validate deterministic network ordering. |
| internal/context/address.go | Adds sortAddresses and calls it from GetContainerAddresses to stabilize address ordering. |
| internal/context/address_test.go | Adds order-sensitive tests for sortAddresses and deterministic GetContainerAddresses output. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@JamBalaya56562 please do not include issue or PR numbers in either PR titles or commit message. |
The per-container Networks and Addresses slices passed to templates were built by ranging over Go maps (NetworkSettings.Networks, NetworkSettings.Ports and Config.ExposedPorts), whose iteration order is random. This caused generated files to churn on every regeneration and triggered spurious reloads / container restarts even when nothing meaningful changed (nginx-proxy#431), and made `index .Networks 0` pick an arbitrary network (nginx-proxy#169). - internal/context/address.go: sort addresses in GetContainerAddresses via a new sortAddresses helper (Port numeric -> Proto -> HostPort -> HostIP -> IP). - internal/generator/generator.go: sort runtimeContainer.Networks by Name via a new sortNetworks helper, right after the network map is collected. - Tests: add order-sensitive TestSortAddresses / TestGetContainerAddressesSorted and TestSortNetworks. Existing order-insensitive address tests still pass. - README.md: document the deterministic ordering, the behavior change to `index .Networks 0`, and how to select a network by name with `where`. `index (where $value.Networks "Name" $name) 0` once the order is stable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
f28de76 to
28b2b35
Compare
buchdag
left a comment
There was a problem hiding this comment.
sortAddresses sorts addresses in place by port (numeric), then proto, host port, host IP and IP.
I'm not 100% certain about the rationale of sorting by IP last, could you elaborate ? 🤔
Not that I'm against it but I'm not quite sure what sorting strategy would make the most sense for a slice of Address.
|
Good question. The key thing is that the only fields which actually vary between the addresses of a single container are
|
Summary
The per-container
NetworksandAddressesslices exposed to templates are built by ranging over Go maps (NetworkSettings.Networks,NetworkSettings.PortsandConfig.ExposedPorts), whose iteration order is random. As a result:default.conf) churn on every regeneration even when nothing meaningful changed, triggering spurious reloads / container restarts (order of networks in nginx template is random #431).index .Networks 0picks an arbitrary network, so multi-network IP selection is unstable (Support for multiple docker networks (via libnetwork) #169).This PR sorts both slices into a deterministic, total order so the rendered output is stable across regenerations. Routing is unaffected — only the ordering becomes deterministic.
Changes
internal/context/address.go: sort addresses inGetContainerAddressesvia a newsortAddresseshelper — by port (compared numerically, so80 < 443 < 8080), then proto, host port, host IP, IP.internal/generator/generator.go: sortruntimeContainer.NetworksbyName(the unique network-name map key) via a newsortNetworkshelper, right after the network map is collected.TestSortAddresses,TestGetContainerAddressesSortedandTestSortNetworks. The existing order-insensitive address tests are unchanged and still pass.README.md: document the deterministic ordering and how to select a network by name.All new symbols are unexported and the change is purely additive.
Behavior note
index .Networks 0now returns the alphabetically-first network (e.g.bridge) instead of an arbitrary one — this is the intended, deterministic fix (as suggested in #431). A specific network can still be selected by name:Resolves
whereonce the ordering is stable.Note: an unrelated, pre-existing flake in
TestGenerateFromEvents(timing-dependent; see #238) can fail on the Windows runner — it reproduces onmainwithout this change (verified locally on Windows / Go 1.26). It is independent of this PR.