mirror of
https://github.com/docker/compose.git
synced 2026-08-27 03:45:29 +00:00
fix(run): scope Running events to project.Services
emitRunningEvents iterated the full ObservedState.Containers map, which is intentionally broader than the operation scope (it covers DisabledServices for orphan classification). compose run --no-deps SERVICE leaves project.Services empty and moves every other service to DisabledServices, so their running containers were reported as Running even though this command must not manage them. Filter the iteration by project.Services, matching the reconciler scope, and document the contract on the function. Fixes #13882 Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
This commit is contained in:
parent
e87f7b79df
commit
ab0d7898de
3 changed files with 108 additions and 5 deletions
|
|
@ -124,7 +124,7 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
|||
|
||||
// Emit "Running" events for containers that are already up-to-date,
|
||||
// matching the previous convergence behavior for progress display.
|
||||
emitRunningEvents(observed, plan, s.events)
|
||||
emitRunningEvents(project, observed, plan, s.events)
|
||||
|
||||
return s.executePlan(ctx, project, observed, plan)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,8 +213,11 @@ func (s *ObservedState) setResolvedVolumes(volumes map[string]string) {
|
|||
// emitRunningEvents emits "Running" progress events for containers that are already
|
||||
// running and have no operations planned for them. This matches the previous behavior
|
||||
// where convergence.ensureService emitted runningEvent for up-to-date containers.
|
||||
func emitRunningEvents(observed *ObservedState, plan *Plan, events api.EventProcessor) {
|
||||
// Collect all container IDs that appear in the plan
|
||||
//
|
||||
// Iterates project.Services (not observed.Containers) so that containers of
|
||||
// disabled services (e.g. dependencies untouched by `compose run --no-deps`)
|
||||
// are not falsely reported as Running — see issue 13882.
|
||||
func emitRunningEvents(project *types.Project, observed *ObservedState, plan *Plan, events api.EventProcessor) {
|
||||
planned := map[string]bool{}
|
||||
for _, node := range plan.Nodes {
|
||||
if node.Operation.Container != nil {
|
||||
|
|
@ -222,8 +225,8 @@ func emitRunningEvents(observed *ObservedState, plan *Plan, events api.EventProc
|
|||
}
|
||||
}
|
||||
|
||||
for _, containers := range observed.Containers {
|
||||
for _, oc := range containers {
|
||||
for _, svc := range project.Services {
|
||||
for _, oc := range observed.Containers[svc.Name] {
|
||||
if oc.State == container.StateRunning && !planned[oc.ID] {
|
||||
events.On(newEvent("Container "+oc.Name, api.Done, api.StatusRunning))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,3 +201,103 @@ func TestCollectObservedState(t *testing.T) {
|
|||
assert.Equal(t, vol.Driver, "local")
|
||||
assert.Equal(t, vol.ConfigHash, "volhash1")
|
||||
}
|
||||
|
||||
type capturingEvents struct {
|
||||
noopEventProcessor
|
||||
resources []api.Resource
|
||||
}
|
||||
|
||||
func (c *capturingEvents) On(events ...api.Resource) {
|
||||
c.resources = append(c.resources, events...)
|
||||
}
|
||||
|
||||
func TestEmitRunningEvents(t *testing.T) {
|
||||
runningWeb := ObservedContainer{ID: "c-web", Name: "p-web-1", State: container.StateRunning}
|
||||
runningDB := ObservedContainer{ID: "c-db", Name: "p-db-1", State: container.StateRunning}
|
||||
runningDisabled := ObservedContainer{ID: "c-misc", Name: "p-misc-1", State: container.StateRunning}
|
||||
exitedWeb := ObservedContainer{ID: "c-web-old", Name: "p-web-2", State: container.StateExited}
|
||||
|
||||
t.Run("emits Running for active services not in plan", func(t *testing.T) {
|
||||
project := &types.Project{
|
||||
Services: types.Services{
|
||||
"web": {Name: "web"},
|
||||
"db": {Name: "db"},
|
||||
},
|
||||
}
|
||||
observed := &ObservedState{
|
||||
Containers: map[string][]ObservedContainer{
|
||||
"web": {runningWeb},
|
||||
"db": {runningDB},
|
||||
},
|
||||
}
|
||||
events := &capturingEvents{}
|
||||
|
||||
emitRunningEvents(project, observed, &Plan{}, events)
|
||||
|
||||
assert.Equal(t, len(events.resources), 2)
|
||||
names := map[string]bool{}
|
||||
for _, r := range events.resources {
|
||||
names[r.ID] = true
|
||||
assert.Equal(t, r.Text, api.StatusRunning)
|
||||
}
|
||||
assert.Assert(t, names["Container p-web-1"])
|
||||
assert.Assert(t, names["Container p-db-1"])
|
||||
})
|
||||
|
||||
t.Run("skips containers belonging to disabled services", func(t *testing.T) {
|
||||
// Reproduces issue 13882: `compose run --no-deps misc` leaves the
|
||||
// project with project.Services empty (misc moved to DisabledServices).
|
||||
// Running containers for disabled services must not be reported.
|
||||
project := &types.Project{
|
||||
Services: types.Services{},
|
||||
DisabledServices: types.Services{
|
||||
"misc": {Name: "misc"},
|
||||
"db": {Name: "db"},
|
||||
},
|
||||
}
|
||||
observed := &ObservedState{
|
||||
Containers: map[string][]ObservedContainer{
|
||||
"misc": {runningDisabled},
|
||||
"db": {runningDB},
|
||||
},
|
||||
}
|
||||
events := &capturingEvents{}
|
||||
|
||||
emitRunningEvents(project, observed, &Plan{}, events)
|
||||
|
||||
assert.Equal(t, len(events.resources), 0)
|
||||
})
|
||||
|
||||
t.Run("skips containers that have a plan operation", func(t *testing.T) {
|
||||
project := &types.Project{
|
||||
Services: types.Services{"web": {Name: "web"}},
|
||||
}
|
||||
observed := &ObservedState{
|
||||
Containers: map[string][]ObservedContainer{"web": {runningWeb}},
|
||||
}
|
||||
plan := &Plan{}
|
||||
plan.addNode(Operation{
|
||||
Type: OpStopContainer,
|
||||
Container: &container.Summary{ID: runningWeb.ID},
|
||||
}, "")
|
||||
events := &capturingEvents{}
|
||||
|
||||
emitRunningEvents(project, observed, plan, events)
|
||||
|
||||
assert.Equal(t, len(events.resources), 0)
|
||||
})
|
||||
|
||||
t.Run("skips non-running containers", func(t *testing.T) {
|
||||
project := &types.Project{
|
||||
Services: types.Services{"web": {Name: "web"}},
|
||||
}
|
||||
observed := &ObservedState{
|
||||
Containers: map[string][]ObservedContainer{"web": {exitedWeb}},
|
||||
}
|
||||
events := &capturingEvents{}
|
||||
|
||||
emitRunningEvents(project, observed, &Plan{}, events)
|
||||
|
||||
assert.Equal(t, len(events.resources), 0)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue