test(e2e): snapshot must survive vanishing helper containers

The plugin/stable/containerd CI job caught a race the DSL had: hook
helper containers carry the project and service labels but no replica
number, and can be destroyed between the snapshot's ps listing and its
batch inspect — which then failed and blanked the entire snapshot,
making ServiceState report 'service has no container' while the
container was right there. Containers are now inspected one by one,
tolerating disappearances, and helper containers (neither numbered as
a replica nor one-off) are excluded from snapshots altogether so they
can't transiently pollute service checks either.

Signed-off-by: Nicolas De Loof <[email protected]>
This commit is contained in:
Nicolas De Loof
2026-08-18 11:37:35 +02:00
parent a80e42120b
commit d3838a73be
+39 -32
View File
@@ -324,41 +324,48 @@ func (s *Scenario) command(action Action) icmd.Cmd {
}
// snapshot captures the current state of the project's containers.
// Containers are inspected one by one: helper containers (e.g. the ones
// running lifecycle hooks) carry the project label but can vanish between the
// listing and the inspect, and one gone container must not blank the whole
// snapshot. Helpers are also excluded from the snapshot itself: a container
// belongs to a service if it is numbered as a replica or marked one-off.
func (s *Scenario) snapshot() snapshot {
s.t.Helper()
snap := snapshot{}
ids := s.projectContainerIDs()
if len(ids) == 0 {
return snap
}
res := icmd.RunCmd(s.cli.NewDockerCmd(s.t, append([]string{"inspect"}, ids...)...))
if res.ExitCode != 0 {
return snap
}
var containers []struct {
ID string `json:"Id"`
Name string `json:"Name"`
State struct {
Status string `json:"Status"`
StartedAt string `json:"StartedAt"`
} `json:"State"`
Config struct {
Labels map[string]string `json:"Labels"`
} `json:"Config"`
}
if err := json.Unmarshal([]byte(res.Stdout()), &containers); err != nil {
return snap
}
for _, c := range containers {
service := c.Config.Labels["com.docker.compose.service"]
snap[service] = append(snap[service], containerState{
ID: c.ID,
Name: strings.TrimPrefix(c.Name, "/"),
State: c.State.Status,
StartedAt: c.State.StartedAt,
OneOff: c.Config.Labels["com.docker.compose.oneoff"] == "True",
Labels: c.Config.Labels,
})
for _, id := range s.projectContainerIDs() {
res := icmd.RunCmd(s.cli.NewDockerCmd(s.t, "inspect", id))
if res.ExitCode != 0 {
continue
}
var containers []struct {
ID string `json:"Id"`
Name string `json:"Name"`
State struct {
Status string `json:"Status"`
StartedAt string `json:"StartedAt"`
} `json:"State"`
Config struct {
Labels map[string]string `json:"Labels"`
} `json:"Config"`
}
if err := json.Unmarshal([]byte(res.Stdout()), &containers); err != nil {
continue
}
for _, c := range containers {
oneOff := c.Config.Labels["com.docker.compose.oneoff"] == "True"
if !oneOff && c.Config.Labels["com.docker.compose.container-number"] == "" {
continue
}
service := c.Config.Labels["com.docker.compose.service"]
snap[service] = append(snap[service], containerState{
ID: c.ID,
Name: strings.TrimPrefix(c.Name, "/"),
State: c.State.Status,
StartedAt: c.State.StartedAt,
OneOff: oneOff,
Labels: c.Config.Labels,
})
}
}
for service := range snap {
slices.SortFunc(snap[service], func(a, b containerState) int { return strings.Compare(a.Name, b.Name) })