summaryrefslogtreecommitdiff
path: root/pkg/varlinkapi/attach.go
blob: 8acf2a1b6534fe37406a715687919b618c12ef3b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// +build varlink

package varlinkapi

import (
	"bufio"
	"context"
	"io"

	"github.com/containers/libpod/v2/libpod"
	"github.com/containers/libpod/v2/libpod/define"
	"github.com/containers/libpod/v2/libpod/events"
	iopodman "github.com/containers/libpod/v2/pkg/varlink"
	"github.com/containers/libpod/v2/pkg/varlinkapi/virtwriter"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"k8s.io/client-go/tools/remotecommand"
)

func setupStreams(call iopodman.VarlinkCall) (*bufio.Reader, *bufio.Writer, *io.PipeReader, *io.PipeWriter, *define.AttachStreams) {

	// These are the varlink sockets
	reader := call.Call.Reader
	writer := call.Call.Writer

	// This pipe is used to pass stdin from the client to the input stream
	// once the msg has been "decoded"
	pr, pw := io.Pipe()

	stdoutWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdout)
	// TODO if runc ever starts passing stderr, we can too
	// stderrWriter := NewVirtWriteCloser(writer, ToStderr)

	streams := define.AttachStreams{
		OutputStream: stdoutWriter,
		InputStream:  bufio.NewReader(pr),
		// Runc eats the error stream
		ErrorStream:  stdoutWriter,
		AttachInput:  true,
		AttachOutput: true,
		// Runc eats the error stream
		AttachError: true,
	}
	return reader, writer, pr, pw, &streams
}

// Attach connects to a containers console
func (i *VarlinkAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys string, start bool) error {
	var finalErr error
	resize := make(chan remotecommand.TerminalSize)
	errChan := make(chan error)

	if !call.WantsUpgrade() {
		return call.ReplyErrorOccurred("client must use upgraded connection to attach")
	}
	ctr, err := i.Runtime.LookupContainer(name)
	if err != nil {
		return call.ReplyErrorOccurred(err.Error())
	}
	state, err := ctr.State()
	if err != nil {
		return call.ReplyErrorOccurred(err.Error())
	}
	if !start && state != define.ContainerStateRunning {
		return call.ReplyErrorOccurred("container must be running to attach")
	}

	// ACK the client upgrade request
	if err := call.ReplyAttach(); err != nil {
		return call.ReplyErrorOccurred(err.Error())
	}

	reader, writer, _, pw, streams := setupStreams(call)
	go func() {
		if err := virtwriter.Reader(reader, nil, nil, pw, resize, nil); err != nil {
			errChan <- err
		}
	}()

	if state == define.ContainerStateRunning {
		finalErr = attach(ctr, streams, detachKeys, resize, errChan)
	} else {
		finalErr = startAndAttach(ctr, streams, detachKeys, resize, errChan)
	}

	exitCode := define.ExitCode(finalErr)
	if finalErr != define.ErrDetach && finalErr != nil {
		logrus.Error(finalErr)
	} else {
		if ecode, err := ctr.Wait(); err != nil {
			if errors.Cause(err) == define.ErrNoSuchCtr {
				// Check events
				event, err := i.Runtime.GetLastContainerEvent(context.Background(), ctr.ID(), events.Exited)
				if err != nil {
					logrus.Errorf("Cannot get exit code: %v", err)
					exitCode = define.ExecErrorCodeNotFound
				} else {
					exitCode = event.ContainerExitCode
				}
			} else {
				exitCode = define.ExitCode(err)
			}
		} else {
			exitCode = int(ecode)
		}
	}

	if ctr.AutoRemove() {
		err := i.Runtime.RemoveContainer(getContext(), ctr, false, false)
		if err != nil {
			logrus.Errorf("Failed to remove container %s: %s", ctr.ID(), err.Error())
		}
	}

	if err = virtwriter.HangUp(writer, uint32(exitCode)); err != nil {
		logrus.Errorf("Failed to HANG-UP attach to %s: %s", ctr.ID(), err.Error())
	}
	return call.Writer.Flush()
}

func attach(ctr *libpod.Container, streams *define.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error {
	go func() {
		if err := ctr.Attach(streams, detachKeys, resize); err != nil {
			errChan <- err
		}
	}()
	attachError := <-errChan
	return attachError
}

func startAndAttach(ctr *libpod.Container, streams *define.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error {
	var finalErr error
	attachChan, err := ctr.StartAndAttach(getContext(), streams, detachKeys, resize, false)
	if err != nil {
		return err
	}
	select {
	case attachChanErr := <-attachChan:
		finalErr = attachChanErr
	case chanError := <-errChan:
		finalErr = chanError
	}
	return finalErr
}