mirror of https://github.com/docker/compose.git
detect replacement container is created and inform printer so it attach and don't stop
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
b3ec110612
commit
0f5b5ccbd0
|
@ -171,7 +171,7 @@ func runUp(ctx context.Context, streams api.Streams, backend api.Service, create
|
|||
if len(attachTo) == 0 {
|
||||
attachTo = project.ServiceNames()
|
||||
}
|
||||
attachTo = utils.RemoveAll(attachTo, upOptions.noAttach)
|
||||
attachTo = utils.Remove(attachTo, upOptions.noAttach...)
|
||||
|
||||
create := api.CreateOptions{
|
||||
Services: services,
|
||||
|
|
|
@ -468,6 +468,7 @@ type ContainerEvent struct {
|
|||
// This is only suitable for display purposes within Compose, as it's
|
||||
// not guaranteed to be unique across services.
|
||||
Container string
|
||||
ID string
|
||||
Service string
|
||||
Line string
|
||||
// ContainerEventExit only
|
||||
|
@ -484,6 +485,8 @@ const (
|
|||
ContainerEventAttach
|
||||
// ContainerEventStopped is a ContainerEvent of type stopped.
|
||||
ContainerEventStopped
|
||||
// ContainerEventRecreated let consumer know container stopped but his being replaced
|
||||
ContainerEventRecreated
|
||||
// ContainerEventExit is a ContainerEvent of type exit. ExitCode is set
|
||||
ContainerEventExit
|
||||
// UserCancel user cancelled compose up, we are stopping containers
|
||||
|
|
|
@ -55,6 +55,8 @@ const (
|
|||
VersionLabel = "com.docker.compose.version"
|
||||
// ImageBuilderLabel stores the builder (classic or BuildKit) used to produce the image.
|
||||
ImageBuilderLabel = "com.docker.compose.image.builder"
|
||||
// ContainerReplaceLabel is set when container is created to replace another container (recreated)
|
||||
ContainerReplaceLabel = "com.docker.compose.replace"
|
||||
)
|
||||
|
||||
// ComposeVersion is the compose tool version as declared by label VersionLabel
|
||||
|
|
|
@ -66,6 +66,7 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
|
|||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: containerName,
|
||||
ID: container.ID,
|
||||
Service: serviceName,
|
||||
})
|
||||
|
||||
|
@ -73,6 +74,7 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
|
|||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventLog,
|
||||
Container: containerName,
|
||||
ID: container.ID,
|
||||
Service: serviceName,
|
||||
Line: line,
|
||||
})
|
||||
|
@ -81,6 +83,7 @@ func (s *composeService) attachContainer(ctx context.Context, container moby.Con
|
|||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventErr,
|
||||
Container: containerName,
|
||||
ID: container.ID,
|
||||
Service: serviceName,
|
||||
Line: line,
|
||||
})
|
||||
|
|
|
@ -148,14 +148,3 @@ func (containers Containers) sorted() Containers {
|
|||
})
|
||||
return containers
|
||||
}
|
||||
|
||||
func (containers Containers) remove(id string) Containers {
|
||||
for i, c := range containers {
|
||||
if c.ID == id {
|
||||
l := len(containers) - 1
|
||||
containers[i] = containers[l]
|
||||
return containers[:l]
|
||||
}
|
||||
}
|
||||
return containers
|
||||
}
|
||||
|
|
|
@ -416,17 +416,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
|||
var created moby.Container
|
||||
w := progress.ContextWriter(ctx)
|
||||
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Working, "Recreate"))
|
||||
timeoutInSecond := utils.DurationSecondToInt(timeout)
|
||||
err := s.apiClient().ContainerStop(ctx, replaced.ID, containerType.StopOptions{Timeout: timeoutInSecond})
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
name := getCanonicalContainerName(replaced)
|
||||
tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
|
||||
err = s.apiClient().ContainerRename(ctx, replaced.ID, tmpName)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
|
||||
number, err := strconv.Atoi(replaced.Labels[api.ContainerNumberLabel])
|
||||
if err != nil {
|
||||
return created, err
|
||||
|
@ -436,15 +426,30 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
|
|||
if inherit {
|
||||
inherited = &replaced
|
||||
}
|
||||
name = getContainerName(project.Name, service, number)
|
||||
created, err = s.createMobyContainer(ctx, project, service, name, number, inherited, false, true, false, w)
|
||||
name := getContainerName(project.Name, service, number)
|
||||
tmpName := fmt.Sprintf("%s_%s", replaced.ID[:12], name)
|
||||
service.Labels[api.ContainerReplaceLabel] = replaced.ID
|
||||
created, err = s.createMobyContainer(ctx, project, service, tmpName, number, inherited, false, true, false, w)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
|
||||
timeoutInSecond := utils.DurationSecondToInt(timeout)
|
||||
err = s.apiClient().ContainerStop(ctx, replaced.ID, containerType.StopOptions{Timeout: timeoutInSecond})
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
|
||||
err = s.apiClient().ContainerRemove(ctx, replaced.ID, moby.ContainerRemoveOptions{})
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
|
||||
err = s.apiClient().ContainerRename(ctx, created.ID, name)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
|
||||
w.Event(progress.NewEvent(getContainerProgressName(replaced), progress.Done, "Recreated"))
|
||||
setDependentLifecycle(project, service.Name, forceRecreate)
|
||||
return created, err
|
||||
|
|
|
@ -83,6 +83,7 @@ func (s *composeService) Logs(
|
|||
printer.HandleEvent(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: getContainerNameWithoutProject(c),
|
||||
ID: c.ID,
|
||||
Service: c.Labels[api.ServiceLabel],
|
||||
})
|
||||
}
|
||||
|
@ -92,6 +93,7 @@ func (s *composeService) Logs(
|
|||
printer.HandleEvent(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: getContainerNameWithoutProject(c),
|
||||
ID: c.ID,
|
||||
Service: c.Labels[api.ServiceLabel],
|
||||
})
|
||||
err := s.logContainers(ctx, consumer, c, api.LogOptions{
|
||||
|
@ -106,6 +108,14 @@ func (s *composeService) Logs(
|
|||
return nil
|
||||
}
|
||||
return err
|
||||
}, func(c types.Container, t time.Time) error {
|
||||
printer.HandleEvent(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: "", // actual name will be set by start event
|
||||
ID: c.ID,
|
||||
Service: c.Labels[api.ServiceLabel],
|
||||
})
|
||||
return nil
|
||||
})
|
||||
printer.Stop()
|
||||
return err
|
||||
|
|
|
@ -74,22 +74,25 @@ func (p *printer) Run(cascadeStop bool, exitCodeFrom string, stopFn func() error
|
|||
case <-p.stopCh:
|
||||
return exitCode, nil
|
||||
case event := <-p.queue:
|
||||
container := event.Container
|
||||
container, id := event.Container, event.ID
|
||||
switch event.Type {
|
||||
case api.UserCancel:
|
||||
aborting = true
|
||||
case api.ContainerEventAttach:
|
||||
if _, ok := containers[container]; ok {
|
||||
if _, ok := containers[id]; ok {
|
||||
continue
|
||||
}
|
||||
containers[container] = struct{}{}
|
||||
containers[id] = struct{}{}
|
||||
p.consumer.Register(container)
|
||||
case api.ContainerEventExit, api.ContainerEventStopped:
|
||||
case api.ContainerEventExit, api.ContainerEventStopped, api.ContainerEventRecreated:
|
||||
if !event.Restarting {
|
||||
delete(containers, container)
|
||||
delete(containers, id)
|
||||
}
|
||||
if !aborting {
|
||||
p.consumer.Status(container, fmt.Sprintf("exited with code %d", event.ExitCode))
|
||||
if event.Type == api.ContainerEventRecreated {
|
||||
p.consumer.Status(container, "has been recreated")
|
||||
}
|
||||
}
|
||||
if cascadeStop {
|
||||
if !aborting {
|
||||
|
|
|
@ -63,6 +63,14 @@ func (s *composeService) start(ctx context.Context, projectName string, options
|
|||
return s.watchContainers(context.Background(), project.Name, options.AttachTo, options.Services, listener, attached,
|
||||
func(container moby.Container, _ time.Time) error {
|
||||
return s.attachContainer(ctx, container, listener)
|
||||
}, func(container moby.Container, _ time.Time) error {
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventAttach,
|
||||
Container: "", // actual name will be set by start event
|
||||
ID: container.ID,
|
||||
Service: container.Labels[api.ServiceLabel],
|
||||
})
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -114,7 +122,7 @@ type containerWatchFn func(container moby.Container, t time.Time) error
|
|||
// watchContainers uses engine events to capture container start/die and notify ContainerEventListener
|
||||
func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
||||
projectName string, services, required []string,
|
||||
listener api.ContainerEventListener, containers Containers, onStart containerWatchFn) error {
|
||||
listener api.ContainerEventListener, containers Containers, onStart, onRecreate containerWatchFn) error {
|
||||
if len(containers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -123,12 +131,13 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
|||
}
|
||||
|
||||
var (
|
||||
expected Containers
|
||||
expected []string
|
||||
watched = map[string]int{}
|
||||
replaced []string
|
||||
)
|
||||
for _, c := range containers {
|
||||
if utils.Contains(required, c.Labels[api.ServiceLabel]) {
|
||||
expected = append(expected, c)
|
||||
expected = append(expected, c.ID)
|
||||
}
|
||||
watched[c.ID] = 0
|
||||
}
|
||||
|
@ -157,23 +166,38 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
|||
service := container.Labels[api.ServiceLabel]
|
||||
switch event.Status {
|
||||
case "stop":
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventStopped,
|
||||
Container: name,
|
||||
Service: service,
|
||||
})
|
||||
if _, ok := watched[container.ID]; ok {
|
||||
eType := api.ContainerEventStopped
|
||||
if utils.Contains(replaced, container.ID) {
|
||||
utils.Remove(replaced, container.ID)
|
||||
eType = api.ContainerEventRecreated
|
||||
}
|
||||
listener(api.ContainerEvent{
|
||||
Type: eType,
|
||||
Container: name,
|
||||
ID: container.ID,
|
||||
Service: service,
|
||||
})
|
||||
}
|
||||
|
||||
delete(watched, container.ID)
|
||||
expected = expected.remove(container.ID)
|
||||
expected = utils.Remove(expected, container.ID)
|
||||
case "die":
|
||||
restarted := watched[container.ID]
|
||||
watched[container.ID] = restarted + 1
|
||||
// Container terminated.
|
||||
willRestart := inspected.State.Restarting
|
||||
|
||||
eType := api.ContainerEventExit
|
||||
if utils.Contains(replaced, container.ID) {
|
||||
utils.Remove(replaced, container.ID)
|
||||
eType = api.ContainerEventRecreated
|
||||
}
|
||||
|
||||
listener(api.ContainerEvent{
|
||||
Type: api.ContainerEventExit,
|
||||
Type: eType,
|
||||
Container: name,
|
||||
ID: container.ID,
|
||||
Service: service,
|
||||
ExitCode: inspected.State.ExitCode,
|
||||
Restarting: willRestart,
|
||||
|
@ -182,7 +206,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
|||
if !willRestart {
|
||||
// we're done with this one
|
||||
delete(watched, container.ID)
|
||||
expected = expected.remove(container.ID)
|
||||
expected = utils.Remove(expected, container.ID)
|
||||
}
|
||||
case "start":
|
||||
count, ok := watched[container.ID]
|
||||
|
@ -190,7 +214,7 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
|||
if !ok {
|
||||
// A new container has just been added to service by scale
|
||||
watched[container.ID] = 0
|
||||
expected = append(expected, container)
|
||||
expected = append(expected, container.ID)
|
||||
mustAttach = true
|
||||
}
|
||||
if mustAttach {
|
||||
|
@ -200,6 +224,21 @@ func (s *composeService) watchContainers(ctx context.Context, //nolint:gocyclo
|
|||
return err
|
||||
}
|
||||
}
|
||||
case "create":
|
||||
if id, ok := container.Labels[api.ContainerReplaceLabel]; ok {
|
||||
replaced = append(replaced, id)
|
||||
err = onRecreate(container, event.Timestamp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if utils.StringContains(expected, id) {
|
||||
expected = append(expected, inspected.ID)
|
||||
}
|
||||
watched[container.ID] = 1
|
||||
if utils.Contains(expected, id) {
|
||||
expected = append(expected, container.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(expected) == 0 {
|
||||
stop()
|
||||
|
|
|
@ -30,7 +30,7 @@ func Contains[T any](origin []T, element T) bool {
|
|||
}
|
||||
|
||||
// RemoveAll removes all elements from origin slice
|
||||
func RemoveAll[T any](origin []T, elements []T) []T {
|
||||
func Remove[T any](origin []T, elements ...T) []T {
|
||||
var filtered []T
|
||||
for _, v := range origin {
|
||||
if !Contains(elements, v) {
|
||||
|
|
Loading…
Reference in New Issue