mirror of
https://github.com/hanzoai/ci.git
synced 2026-10-20 15:28:05 +00:00
the board answers on a phone, and a pinned-back sidecar is not the service
The table is wider than a phone and had no box to scroll in, so the page itself scrolled sideways and carried the header and nav with it: a reader could see that something was wrong but not what, which is the one thing the four columns exist to say. And two containers can share a repository at different versions on purpose — a sidecar held back while the app moves on. Reading whichever came last reported the pin unapplied on a service whose app container was carrying it, so the board named drift where there was none. A workload runs the declared version if any of its containers does.
This commit is contained in:
@@ -189,6 +189,7 @@ var fleetTmpl = template.Must(template.New("fleet").Funcs(template.FuncMap{
|
|||||||
</div>{{end}}
|
</div>{{end}}
|
||||||
|
|
||||||
{{if .Services}}
|
{{if .Services}}
|
||||||
|
<div class="scroll">
|
||||||
<table>
|
<table>
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th>Service</th>
|
<th>Service</th>
|
||||||
@@ -233,6 +234,7 @@ var fleetTmpl = template.Must(template.New("fleet").Funcs(template.FuncMap{
|
|||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody></table>
|
</tbody></table>
|
||||||
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
No services visible.<br>
|
No services visible.<br>
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ nav .who{margin-left:auto;align-self:center;color:var(--text-muted);font-size:va
|
|||||||
background:color-mix(in srgb, var(--warning) 10%, transparent);
|
background:color-mix(in srgb, var(--warning) 10%, transparent);
|
||||||
color:var(--warning-light);font-size:var(--font-size-sm)}
|
color:var(--warning-light);font-size:var(--font-size-sm)}
|
||||||
|
|
||||||
|
/* The four versions are the answer, so they must survive a narrow screen. The
|
||||||
|
table scrolls inside its own box; without one the page scrolls sideways and
|
||||||
|
takes the header and nav with it, leaving a reader able to see that something
|
||||||
|
is wrong but not what. */
|
||||||
|
.scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
|
||||||
table{width:100%;border-collapse:collapse}
|
table{width:100%;border-collapse:collapse}
|
||||||
th{position:sticky;top:0;background:var(--surface-1);text-align:left;
|
th{position:sticky;top:0;background:var(--surface-1);text-align:left;
|
||||||
font-size:var(--font-size-xs);text-transform:uppercase;letter-spacing:.06em;
|
font-size:var(--font-size-xs);text-transform:uppercase;letter-spacing:.06em;
|
||||||
|
|||||||
@@ -277,6 +277,16 @@ func assemble(pins []pin, lives []live, links map[string]link) []Service {
|
|||||||
// service declares; its sidecars are not the service.
|
// service declares; its sidecars are not the service.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// A workload runs the declared version if ANY of its containers does.
|
||||||
|
// Two containers can share the repository and hold different versions on
|
||||||
|
// purpose — a sidecar pinned back while the app moves on — and taking
|
||||||
|
// whichever the loop reached last reported the pin unapplied on a service
|
||||||
|
// whose app container was carrying it. Preferring the match answers the
|
||||||
|
// question the column asks: is what we declared running here. For the
|
||||||
|
// single-container case, which is nearly every row, nothing changes.
|
||||||
|
if same, decided := alike(s.Running, s.Declared); decided && same {
|
||||||
|
continue
|
||||||
|
}
|
||||||
s.Running, s.Ready, s.Want = l.Version, l.Ready, l.Want
|
s.Running, s.Ready, s.Want = l.Version, l.Ready, l.Want
|
||||||
}
|
}
|
||||||
for _, s := range byName {
|
for _, s := range byName {
|
||||||
|
|||||||
@@ -337,6 +337,35 @@ func TestOneImageIsSeveralServices(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSidecarOnTheSameImageDoesNotHideThePin — two containers can share the
|
||||||
|
// repository and hold different versions deliberately, and taking whichever the
|
||||||
|
// loop reached last reported the pin unapplied on a service whose app container
|
||||||
|
// was carrying it. studio pins its build-manifest sidecar back to v0.19.26 while
|
||||||
|
// the app runs v0.19.31; the board called that drift and named a service that was
|
||||||
|
// in fact current.
|
||||||
|
func TestSidecarOnTheSameImageDoesNotHideThePin(t *testing.T) {
|
||||||
|
studio := "ghcr.io/hanzoai/studio"
|
||||||
|
pins := []pin{{Name: "studio", Namespace: "hanzo", Image: studio,
|
||||||
|
Version: Version{Tag: "v0.19.31"}}}
|
||||||
|
// Sidecar last, so a last-writer-wins read takes the pinned-back one.
|
||||||
|
lives := []live{
|
||||||
|
{Name: "studio", Namespace: "hanzo", Image: studio, Version: Version{Tag: "v0.19.31"}, Ready: 1, Want: 1},
|
||||||
|
{Name: "studio", Namespace: "hanzo", Image: studio, Version: Version{Tag: "v0.19.26"}, Ready: 1, Want: 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := assemble(pins, lives, map[string]link{})
|
||||||
|
if len(got) != 1 {
|
||||||
|
t.Fatalf("assembled %d rows from one declaration: %+v", len(got), got)
|
||||||
|
}
|
||||||
|
if got[0].Running.Tag != "v0.19.31" {
|
||||||
|
t.Errorf("running = %q; a workload runs the declared version if any container does",
|
||||||
|
got[0].Running.Tag)
|
||||||
|
}
|
||||||
|
if !got[0].current() {
|
||||||
|
t.Errorf("drift = %v; the pin was applied and the app container carries it", got[0].Drift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestRunningMatchesOnNameAndImage — a workload of the right name still has to be
|
// TestRunningMatchesOnNameAndImage — a workload of the right name still has to be
|
||||||
// running the image the service declares, so a sidecar is never read as the
|
// running the image the service declares, so a sidecar is never read as the
|
||||||
// service.
|
// service.
|
||||||
|
|||||||
Reference in New Issue
Block a user