Skip to content

Commit 0f5b5cc

Browse files
committed
detect replacement container is created and inform printer so it attach and don't stop
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent b3ec110 commit 0f5b5cc

File tree

10 files changed

+96
-42
lines changed

10 files changed

+96
-42
lines changed

cmd/compose/up.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func runUp(ctx context.Context, streams api.Streams, backend api.Service, create
171171
if len(attachTo) == 0 {
172172
attachTo = project.ServiceNames()
173173
}
174-
attachTo = utils.RemoveAll(attachTo, upOptions.noAttach)
174+
attachTo = utils.Remove(attachTo, upOptions.noAttach...)
175175

176176
create := api.CreateOptions{
177177
Services: services,

pkg/api/api.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ type ContainerEvent struct {
468468
// This is only suitable for display purposes within Compose, as it's
469469
// not guaranteed to be unique across services.
470470
Container string
471+
ID string
471472
Service string
472473
Line string
473474
// ContainerEventExit only
@@ -484,6 +485,8 @@ const (
484485
ContainerEventAttach
485486
// ContainerEventStopped is a ContainerEvent of type stopped.
486487
ContainerEventStopped
488+
// ContainerEventRecreated let consumer know container stopped but his being replaced
489+
ContainerEventRecreated
487490
// ContainerEventExit is a ContainerEvent of type exit. ExitCode is set
488491
ContainerEventExit
489492
// UserCancel user cancelled compose up, we are stopping containers

pkg/api/labels.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ const (
5555
VersionLabel = "com.docker.compose.version"
5656
// ImageBuilderLabel stores the builder (classic or BuildKit) used to produce the image.
5757
ImageBuilderLabel = "com.docker.compose.image.builder"
58+
// ContainerReplaceLabel is set when container is created to replace another container (recreated)
59+
ContainerReplaceLabel = "com.docker.compose.replace"
5860
)
5961

6062
// ComposeVersion is the compose tool version as declared by label VersionLabel

pkg/compose/attach.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,15 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
6666
listener(api.ContainerEvent{
6767
Type: api.ContainerEventAttach,
6868
Container: containerName,
69+
ID: container.ID,
6970
Service: serviceName,
7071
})
7172

7273
wOut := utils.GetWriter(func(line string) {
7374
listener(api.ContainerEvent{
7475
Type: api.ContainerEventLog,
7576
Container: containerName,
77+
ID: container.ID,
7678
Service: serviceName,
7779
Line: line,
7880
})
@@ -81,6 +83,7 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
8183
listener(api.ContainerEvent{
8284
Type: api.ContainerEventErr,
8385
Container: containerName,
86+
ID: container.ID,
8487
Service: serviceName,
8588
Line: line,
8689
})

pkg/compose/containers.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,3 @@ func (containers Containers) sorted() Containers {
148148
})
149149
return containers
150150
}
151-
152-
func (containers Containers) remove(id string) Containers {
153-
for i, c := range containers {
154-
if c.ID == id {
155-
l := len(containers) - 1
156-
containers[i] = containers[l]
157-
return containers[:l]
158-
}
159-
}
160-
return containers
161-
}

pkg/compose/convergence.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -416,35 +416,40 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
416416
var created moby.Container
417417
w := progress.ContextWriter(ctx)
418418
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
419-
timeoutInSecond := utils.DurationSecondToInt(timeout)
420-
err := s.apiClient().ContainerStop(ctx, replaced.ID, containerType.StopOptions{Timeout: timeoutInSecond})
419+
420+
number, err := strconv.Atoi(replaced.Labels[api.ContainerNumberLabel])
421421
if err != nil {
422422
return created, err
423423
}
424-
name := getCanonicalContainerName(replaced)
424+
425+
var inherited *moby.Container
426+
if inherit {
427+
inherited = &replaced
428+
}
429+
name := getContainerName(project.Name, service, number)
425430
tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
426-
err = s.apiClient().ContainerRename(ctx, replaced.ID, tmpName)
431+
service.Labels[api.ContainerReplaceLabel] = replaced.ID
432+
created, err = s.createMobyContainer(ctx, project, service, tmpName, number, inherited, false, true, false, w)
427433
if err != nil {
428434
return created, err
429435
}
430-
number, err := strconv.Atoi(replaced.Labels[api.ContainerNumberLabel])
436+
437+
timeoutInSecond := utils.DurationSecondToInt(timeout)
438+
err = s.apiClient().ContainerStop(ctx, replaced.ID, containerType.StopOptions{Timeout: timeoutInSecond})
431439
if err != nil {
432440
return created, err
433441
}
434442

435-
var inherited *moby.Container
436-
if inherit {
437-
inherited = &replaced
438-
}
439-
name = getContainerName(project.Name, service, number)
440-
created, err = s.createMobyContainer(ctx, project, service, name, number, inherited, false, true, false, w)
443+
err = s.apiClient().ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
441444
if err != nil {
442445
return created, err
443446
}
444-
err = s.apiClient().ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
447+
448+
err = s.apiClient().ContainerRename(ctx, created.ID, name)
445449
if err != nil {
446450
return created, err
447451
}
452+
448453
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Done, "Recreated"))
449454
setDependentLifecycle(project, service.Name, forceRecreate)
450455
return created, err

pkg/compose/logs.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func (s *composeService) Logs(
8383
printer.HandleEvent(api.ContainerEvent{
8484
Type: api.ContainerEventAttach,
8585
Container: getContainerNameWithoutProject(c),
86+
ID: c.ID,
8687
Service: c.Labels[api.ServiceLabel],
8788
})
8889
}
@@ -92,6 +93,7 @@ func (s *composeService) Logs(
9293
printer.HandleEvent(api.ContainerEvent{
9394
Type: api.ContainerEventAttach,
9495
Container: getContainerNameWithoutProject(c),
96+
ID: c.ID,
9597
Service: c.Labels[api.ServiceLabel],
9698
})
9799
err := s.logContainers(ctx, consumer, c, api.LogOptions{
@@ -106,6 +108,14 @@ func (s *composeService) Logs(
106108
return nil
107109
}
108110
return err
111+
}, func(c types.Container, t time.Time) error {
112+
printer.HandleEvent(api.ContainerEvent{
113+
Type: api.ContainerEventAttach,
114+
Container: "", // actual name will be set by start event
115+
ID: c.ID,
116+
Service: c.Labels[api.ServiceLabel],
117+
})
118+
return nil
109119
})
110120
printer.Stop()
111121
return err

pkg/compose/printer.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,22 +74,25 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error
7474
case <-p.stopCh:
7575
return exitCode, nil
7676
case event := <-p.queue:
77-
container := event.Container
77+
container, id := event.Container, event.ID
7878
switch event.Type {
7979
case api.UserCancel:
8080
aborting = true
8181
case api.ContainerEventAttach:
82-
if _, ok := containers[container]; ok {
82+
if _, ok := containers[id]; ok {
8383
continue
8484
}
85-
containers[container] = struct{}{}
85+
containers[id] = struct{}{}
8686
p.consumer.Register(container)
87-
case api.ContainerEventExit, api.ContainerEventStopped:
87+
case api.ContainerEventExit, api.ContainerEventStopped, api.ContainerEventRecreated:
8888
if !event.Restarting {
89-
delete(containers, container)
89+
delete(containers, id)
9090
}
9191
if !aborting {
9292
p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
93+
if event.Type == api.ContainerEventRecreated {
94+
p.consumer.Status(container, "has been recreated")
95+
}
9396
}
9497
if cascadeStop {
9598
if !aborting {

pkg/compose/start.go

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ func (s *composeService) start(ctx context.Context, projectName string, options
6363
return s.watchContainers(context.Background(), project.Name, options.AttachTo, options.Services, listener, attached,
6464
func(container moby.Container, _ time.Time) error {
6565
return s.attachContainer(ctx, container, listener)
66+
}, func(container moby.Container, _ time.Time) error {
67+
listener(api.ContainerEvent{
68+
Type: api.ContainerEventAttach,
69+
Container: "", // actual name will be set by start event
70+
ID: container.ID,
71+
Service: container.Labels[api.ServiceLabel],
72+
})
73+
return nil
6674
})
6775
})
6876
}
@@ -114,7 +122,7 @@ type containerWatchFn func(container moby.Container, t time.Time) error
114122
// watchContainers uses engine events to capture container start/die and notify ContainerEventListener
115123
func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
116124
projectName string, services, required []string,
117-
listener api.ContainerEventListener, containers Containers, onStart containerWatchFn) error {
125+
listener api.ContainerEventListener, containers Containers, onStart, onRecreate containerWatchFn) error {
118126
if len(containers) == 0 {
119127
return nil
120128
}
@@ -123,12 +131,13 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
123131
}
124132

125133
var (
126-
expected Containers
134+
expected []string
127135
watched = map[string]int{}
136+
replaced []string
128137
)
129138
for _, c := range containers {
130139
if utils.Contains(required, c.Labels[api.ServiceLabel]) {
131-
expected = append(expected, c)
140+
expected = append(expected, c.ID)
132141
}
133142
watched[c.ID] = 0
134143
}
@@ -157,23 +166,38 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
157166
service := container.Labels[api.ServiceLabel]
158167
switch event.Status {
159168
case "stop":
160-
listener(api.ContainerEvent{
161-
Type: api.ContainerEventStopped,
162-
Container: name,
163-
Service: service,
164-
})
169+
if _, ok := watched[container.ID]; ok {
170+
eType := api.ContainerEventStopped
171+
if utils.Contains(replaced, container.ID) {
172+
utils.Remove(replaced, container.ID)
173+
eType = api.ContainerEventRecreated
174+
}
175+
listener(api.ContainerEvent{
176+
Type: eType,
177+
Container: name,
178+
ID: container.ID,
179+
Service: service,
180+
})
181+
}
165182

166183
delete(watched, container.ID)
167-
expected = expected.remove(container.ID)
184+
expected = utils.Remove(expected, container.ID)
168185
case "die":
169186
restarted := watched[container.ID]
170187
watched[container.ID] = restarted + 1
171188
// Container terminated.
172189
willRestart := inspected.State.Restarting
173190

191+
eType := api.ContainerEventExit
192+
if utils.Contains(replaced, container.ID) {
193+
utils.Remove(replaced, container.ID)
194+
eType = api.ContainerEventRecreated
195+
}
196+
174197
listener(api.ContainerEvent{
175-
Type: api.ContainerEventExit,
198+
Type: eType,
176199
Container: name,
200+
ID: container.ID,
177201
Service: service,
178202
ExitCode: inspected.State.ExitCode,
179203
Restarting: willRestart,
@@ -182,15 +206,15 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
182206
if !willRestart {
183207
// we're done with this one
184208
delete(watched, container.ID)
185-
expected = expected.remove(container.ID)
209+
expected = utils.Remove(expected, container.ID)
186210
}
187211
case "start":
188212
count, ok := watched[container.ID]
189213
mustAttach := ok && count > 0 // Container restarted, need to re-attach
190214
if !ok {
191215
// A new container has just been added to service by scale
192216
watched[container.ID] = 0
193-
expected = append(expected, container)
217+
expected = append(expected, container.ID)
194218
mustAttach = true
195219
}
196220
if mustAttach {
@@ -200,6 +224,21 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
200224
return err
201225
}
202226
}
227+
case "create":
228+
if id, ok := container.Labels[api.ContainerReplaceLabel]; ok {
229+
replaced = append(replaced, id)
230+
err = onRecreate(container, event.Timestamp)
231+
if err != nil {
232+
return err
233+
}
234+
if utils.StringContains(expected, id) {
235+
expected = append(expected, inspected.ID)
236+
}
237+
watched[container.ID] = 1
238+
if utils.Contains(expected, id) {
239+
expected = append(expected, container.ID)
240+
}
241+
}
203242
}
204243
if len(expected) == 0 {
205244
stop()

pkg/utils/slices.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func Contains[T any](origin []T, element T) bool {
3030
}
3131

3232
// RemoveAll removes all elements from origin slice
33-
func RemoveAll[T any](origin []T, elements []T) []T {
33+
func Remove[T any](origin []T, elements ...T) []T {
3434
var filtered []T
3535
for _, v := range origin {
3636
if !Contains(elements, v) {

0 commit comments

Comments
 (0)