( ctx context.Context, project *types.Project, service types.ServiceConfig, ctr container.Summary, index int, hook types.ServiceHook, listener api.ContainerEventListener, )
| 73 | } |
| 74 | |
| 75 | func (s *composeService) runPreStartHook( |
| 76 | ctx context.Context, project *types.Project, service types.ServiceConfig, |
| 77 | ctr container.Summary, index int, hook types.ServiceHook, listener api.ContainerEventListener, |
| 78 | ) error { |
| 79 | created, err := s.createPreStartContainer(ctx, project, service, ctr, hook) |
| 80 | if err != nil { |
| 81 | return err |
| 82 | } |
| 83 | |
| 84 | // Subscribe to wait before start to avoid missing the exit event for short-lived hooks. |
| 85 | // WaitConditionNotRunning would match immediately because the container is still in |
| 86 | // "created" state, so use WaitConditionNextExit to block until the run actually finishes. |
| 87 | waitRes := s.apiClient().ContainerWait(ctx, created.ID, client.ContainerWaitOptions{ |
| 88 | Condition: container.WaitConditionNextExit, |
| 89 | }) |
| 90 | |
| 91 | // Open the log stream before ContainerStart so AutoRemove cannot race us |
| 92 | // to a 404 on a fast-exiting hook. The dedicated logCtx lets us force the |
| 93 | // follow stream closed once the hook has exited, so a daemon that keeps |
| 94 | // the connection open cannot deadlock `<-logsDone`. |
| 95 | logCtx, cancelLogs := context.WithCancel(ctx) |
| 96 | defer cancelLogs() |
| 97 | logsDone := s.streamPreStartLogs(logCtx, created.ID, service, index, listener) |
| 98 | |
| 99 | if _, err := s.apiClient().ContainerStart(ctx, created.ID, client.ContainerStartOptions{}); err != nil { |
| 100 | // AutoRemove only fires after a successful start, so the never-started |
| 101 | // container has to be dropped explicitly. A failed removal is logged |
| 102 | // at warn level — without that hint the orphan is only discoverable |
| 103 | // via the project/service labels. |
| 104 | if _, removeErr := s.apiClient().ContainerRemove(ctx, created.ID, client.ContainerRemoveOptions{Force: true}); removeErr != nil { |
| 105 | logrus.Warnf("service %q pre_start[%d]: failed to remove orphan hook container %s: %v", service.Name, index, created.ID, removeErr) |
| 106 | } |
| 107 | // Drain waitRes so the client's wait goroutine exits without having to |
| 108 | // wait for the parent context to be canceled. |
| 109 | select { |
| 110 | case <-waitRes.Error: |
| 111 | case <-waitRes.Result: |
| 112 | case <-ctx.Done(): |
| 113 | } |
| 114 | cancelLogs() |
| 115 | <-logsDone |
| 116 | return err |
| 117 | } |
| 118 | |
| 119 | waitErr := waitPreStart(ctx, service.Name, index, waitRes) |
| 120 | cancelLogs() |
| 121 | <-logsDone |
| 122 | return waitErr |
| 123 | } |
| 124 | |
| 125 | func (s *composeService) createPreStartContainer( |
| 126 | ctx context.Context, project *types.Project, service types.ServiceConfig, |
no test coverage detected