settings-3 final review: the control socket reports the route's channel for default settings
An empty settings column (default settings) names no channel, so GET /updates answered "channel":"" for every untouched instance. The reader now fills the route's channel when the column names none; a set column's channel, reconcile's effective copy, still wins. The stored column is never touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+37
−3
internal/instanceupdates/control.go+10 −2internal/instanceupdates/control_test.go+27 −1
diff --git a/internal/instanceupdates/control.go b/internal/instanceupdates/control.go| index bb86c5118..a115b9e9b 100644 |
| --- a/internal/instanceupdates/control.go |
| +++ b/internal/instanceupdates/control.go |
| @@ -63,8 +63,9 @@ func Mount(mux *http.ServeMux, host string, token func() (string, error), read f |
| // row for: the verb turns it into a 503, never a 200 with an empty report. |
| var errNoRoute = errors.New("instanceupdates: no route for host") |
| -// RegistryReader answers from the box's registry copy: the settings column, |
| -// the installed build and the serving build. Root's desired build and reason |
| +// RegistryReader answers from the box's registry copy: the settings column |
| +// (with the route's channel when the column names none), the installed |
| +// build and the serving build. Root's desired build and reason |
| // are added by plan 4's reader, which wraps this one. |
| func RegistryReader(reg interface { |
| Get(host string) (*registry.Route, error) |
| @@ -81,6 +82,13 @@ func RegistryReader(reg interface { |
| if err != nil { |
| return Report{}, err |
| } |
| + // Default settings are stored as "" (Column), which names no channel: |
| + // the instance follows its route's. A set column is reconcile's |
| + // effective copy and its channel wins; one with no channel falls back |
| + // to the route's too. Only the report is filled, never the column. |
| + if u.Channel == "" { |
| + u.Channel = rt.Channel |
| + } |
| return Report{Settings: u, MayChange: u.Control, Installed: rt.Version, Serving: rt.ServingVersion}, nil |
| } |
| } |
diff --git a/internal/instanceupdates/control_test.go b/internal/instanceupdates/control_test.go| index 81eda8f26..d726686b4 100644 |
| --- a/internal/instanceupdates/control_test.go |
| +++ b/internal/instanceupdates/control_test.go |
| @@ -160,11 +160,37 @@ func TestRegistryReaderFillsFromTheRoute(t *testing.T) { |
| } |
| } |
| +// An empty column is an instance with default settings (Column() writes "" |
| +// for them), and its channel is the route's: the report says which channel |
| +// the instance follows, and the stored column stays "". |
| func TestRegistryReaderReadsAnEmptyColumnAsAuto(t *testing.T) { |
| - rep, err := RegistryReader(fakeReg{rt: ®istry.Route{Host: "a.example.com", Version: "v1"}})("a.example.com") |
| + rt := ®istry.Route{Host: "a.example.com", Version: "v1", Channel: "beta"} |
| + rep, err := RegistryReader(fakeReg{rt: rt})("a.example.com") |
| if err != nil || rep.Settings.Updates != release.UpdatesAuto || rep.Installed != "v1" { |
| t.Fatalf("%+v, %v", rep, err) |
| } |
| + if rep.Settings.Channel != "beta" { |
| + t.Fatalf("channel of an empty column = %q, want the route's %q", rep.Settings.Channel, "beta") |
| + } |
| + if rt.UpdateSettings != "" { |
| + t.Fatalf("the reader wrote the column: %q", rt.UpdateSettings) |
| + } |
| +} |
| + |
| +// A set column is reconcile's effective copy and names its own channel, so |
| +// it wins over the route's; a set column with no channel falls back to the |
| +// route's. |
| +func TestRegistryReaderChannelPrefersTheColumn(t *testing.T) { |
| + rep, err := RegistryReader(fakeReg{rt: ®istry.Route{Host: "a.example.com", Channel: "stable", |
| + UpdateSettings: `{"updates":"manual","channel":"beta"}`}})("a.example.com") |
| + if err != nil || rep.Settings.Channel != "beta" { |
| + t.Fatalf("column beta, route stable: %+v, %v", rep, err) |
| + } |
| + rep, err = RegistryReader(fakeReg{rt: ®istry.Route{Host: "a.example.com", Channel: "stable", |
| + UpdateSettings: `{"updates":"manual"}`}})("a.example.com") |
| + if err != nil || rep.Settings.Channel != "stable" { |
| + t.Fatalf("column with no channel, route stable: %+v, %v", rep, err) |
| + } |
| } |
| // A host the registry does not know, or cannot read, is an error: the verb |