|
@@ -31,7 +31,7 @@ import (
|
|
|
"github.com/docker/docker/pkg/stdcopy"
|
|
|
)
|
|
|
|
|
|
-func (s *composeService) attach(ctx context.Context, project *types.Project, consumer compose.ContainerEventListener, selectedServices []string) (Containers, error) {
|
|
|
+func (s *composeService) attach(ctx context.Context, project *types.Project, listener compose.ContainerEventListener, selectedServices []string) (Containers, error) {
|
|
|
containers, err := s.getContainers(ctx, project, oneOffExclude, selectedServices)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
@@ -47,33 +47,72 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, con
|
|
|
fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
|
|
|
|
|
|
for _, container := range containers {
|
|
|
- consumer(compose.ContainerEvent{
|
|
|
- Type: compose.ContainerEventAttach,
|
|
|
- Source: container.ID,
|
|
|
- Name: getContainerNameWithoutProject(container),
|
|
|
- Service: container.Labels[serviceLabel],
|
|
|
- })
|
|
|
- err := s.attachContainer(ctx, container, consumer, project)
|
|
|
+ err := s.attachContainer(ctx, container, listener, project)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
}
|
|
|
- return containers, nil
|
|
|
+
|
|
|
+ // Watch events to capture container restart and re-attach
|
|
|
+ go func() {
|
|
|
+ crashed := map[string]struct{}{}
|
|
|
+ s.Events(ctx, project.Name, compose.EventsOptions{ // nolint: errcheck
|
|
|
+ Services: selectedServices,
|
|
|
+ Consumer: func(event compose.Event) error {
|
|
|
+ if event.Status == "die" {
|
|
|
+ crashed[event.Container] = struct{}{}
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if _, ok := crashed[event.Container]; ok {
|
|
|
+ inspect, err := s.apiClient.ContainerInspect(ctx, event.Container)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ container := moby.Container{
|
|
|
+ ID: event.Container,
|
|
|
+ Names: []string{inspect.Name},
|
|
|
+ State: convert.ContainerRunning,
|
|
|
+ Labels: map[string]string{
|
|
|
+ projectLabel: project.Name,
|
|
|
+ serviceLabel: event.Service,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ // Just ignore errors when reattaching to already crashed containers
|
|
|
+ s.attachContainer(ctx, container, listener, project) // nolint: errcheck
|
|
|
+ delete(crashed, event.Container)
|
|
|
+
|
|
|
+ s.waitContainer(ctx, container, listener)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ },
|
|
|
+ })
|
|
|
+ }()
|
|
|
+
|
|
|
+ return containers, err
|
|
|
}
|
|
|
|
|
|
-func (s *composeService) attachContainer(ctx context.Context, container moby.Container, consumer compose.ContainerEventListener, project *types.Project) error {
|
|
|
+func (s *composeService) attachContainer(ctx context.Context, container moby.Container, listener compose.ContainerEventListener, project *types.Project) error {
|
|
|
serviceName := container.Labels[serviceLabel]
|
|
|
- w := utils.GetWriter(getContainerNameWithoutProject(container), serviceName, container.ID, consumer)
|
|
|
+ w := utils.GetWriter(getContainerNameWithoutProject(container), serviceName, container.ID, listener)
|
|
|
|
|
|
service, err := project.GetService(serviceName)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- return s.attachContainerStreams(ctx, container, service.Tty, nil, w)
|
|
|
+ listener(compose.ContainerEvent{
|
|
|
+ Type: compose.ContainerEventAttach,
|
|
|
+ Source: container.ID,
|
|
|
+ Name: getContainerNameWithoutProject(container),
|
|
|
+ Service: container.Labels[serviceLabel],
|
|
|
+ })
|
|
|
+
|
|
|
+ return s.attachContainerStreams(ctx, container.ID, service.Tty, nil, w)
|
|
|
}
|
|
|
|
|
|
-func (s *composeService) attachContainerStreams(ctx context.Context, container moby.Container, tty bool, r io.Reader, w io.Writer) error {
|
|
|
+func (s *composeService) attachContainerStreams(ctx context.Context, container string, tty bool, r io.Reader, w io.Writer) error {
|
|
|
stdin, stdout, err := s.getContainerStreams(ctx, container)
|
|
|
if err != nil {
|
|
|
return err
|
|
@@ -105,32 +144,30 @@ func (s *composeService) attachContainerStreams(ctx context.Context, container m
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (s *composeService) getContainerStreams(ctx context.Context, container moby.Container) (io.WriteCloser, io.ReadCloser, error) {
|
|
|
+func (s *composeService) getContainerStreams(ctx context.Context, container string) (io.WriteCloser, io.ReadCloser, error) {
|
|
|
var stdout io.ReadCloser
|
|
|
var stdin io.WriteCloser
|
|
|
- if container.State == convert.ContainerRunning {
|
|
|
- logs, err := s.apiClient.ContainerLogs(ctx, container.ID, moby.ContainerLogsOptions{
|
|
|
- ShowStdout: true,
|
|
|
- ShowStderr: true,
|
|
|
- Follow: true,
|
|
|
- })
|
|
|
- if err != nil {
|
|
|
- return nil, nil, err
|
|
|
- }
|
|
|
- stdout = logs
|
|
|
- } else {
|
|
|
- cnx, err := s.apiClient.ContainerAttach(ctx, container.ID, moby.ContainerAttachOptions{
|
|
|
- Stream: true,
|
|
|
- Stdin: true,
|
|
|
- Stdout: true,
|
|
|
- Stderr: true,
|
|
|
- Logs: false,
|
|
|
- })
|
|
|
- if err != nil {
|
|
|
- return nil, nil, err
|
|
|
- }
|
|
|
+ cnx, err := s.apiClient.ContainerAttach(ctx, container, moby.ContainerAttachOptions{
|
|
|
+ Stream: true,
|
|
|
+ Stdin: true,
|
|
|
+ Stdout: true,
|
|
|
+ Stderr: true,
|
|
|
+ Logs: false,
|
|
|
+ })
|
|
|
+ if err == nil {
|
|
|
stdout = convert.ContainerStdout{HijackedResponse: cnx}
|
|
|
stdin = convert.ContainerStdin{HijackedResponse: cnx}
|
|
|
+ return stdin, stdout, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fallback to logs API
|
|
|
+ logs, err := s.apiClient.ContainerLogs(ctx, container, moby.ContainerLogsOptions{
|
|
|
+ ShowStdout: true,
|
|
|
+ ShowStderr: true,
|
|
|
+ Follow: true,
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, err
|
|
|
}
|
|
|
- return stdin, stdout, nil
|
|
|
+ return stdin, logs, nil
|
|
|
}
|