2016-11-22 16:49:54 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2017-11-30 09:43:35 +00:00
|
|
|
"encoding/json"
|
2016-12-15 11:58:45 +00:00
|
|
|
"fmt"
|
2017-02-08 14:11:52 +00:00
|
|
|
"io"
|
2017-11-30 09:43:35 +00:00
|
|
|
"io/ioutil"
|
2017-04-20 17:51:40 +00:00
|
|
|
"os"
|
2017-02-08 14:11:52 +00:00
|
|
|
"os/exec"
|
2017-11-09 10:10:35 +00:00
|
|
|
"time"
|
2016-12-15 11:58:45 +00:00
|
|
|
|
2017-06-10 16:33:35 +00:00
|
|
|
"github.com/docker/docker/pkg/pools"
|
2016-12-15 11:58:45 +00:00
|
|
|
"github.com/kubernetes-incubator/cri-o/oci"
|
2017-08-05 11:40:46 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2016-11-22 16:49:54 +00:00
|
|
|
"golang.org/x/net/context"
|
2017-08-04 11:13:19 +00:00
|
|
|
"k8s.io/client-go/tools/remotecommand"
|
|
|
|
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
2017-04-20 17:51:40 +00:00
|
|
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
|
|
|
utilexec "k8s.io/kubernetes/pkg/util/exec"
|
2017-02-08 14:11:52 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/term"
|
2016-11-22 16:49:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Exec prepares a streaming endpoint to execute a command in the container.
|
2017-11-09 10:10:35 +00:00
|
|
|
func (s *Server) Exec(ctx context.Context, req *pb.ExecRequest) (resp *pb.ExecResponse, err error) {
|
|
|
|
const operation = "exec"
|
|
|
|
defer func() {
|
|
|
|
recordOperation(operation, time.Now())
|
|
|
|
recordError(operation, err)
|
|
|
|
}()
|
|
|
|
|
2016-12-15 11:58:45 +00:00
|
|
|
logrus.Debugf("ExecRequest %+v", req)
|
2017-02-08 14:11:52 +00:00
|
|
|
|
2017-11-09 10:10:35 +00:00
|
|
|
resp, err = s.GetExec(req)
|
2016-12-15 11:58:45 +00:00
|
|
|
if err != nil {
|
2017-02-08 14:11:52 +00:00
|
|
|
return nil, fmt.Errorf("unable to prepare exec endpoint")
|
2016-12-15 11:58:45 +00:00
|
|
|
}
|
|
|
|
|
2017-02-08 14:11:52 +00:00
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exec endpoint for streaming.Runtime
|
2017-08-04 11:13:19 +00:00
|
|
|
func (ss streamService) Exec(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
2017-05-29 11:16:27 +00:00
|
|
|
c := ss.runtimeServer.GetContainer(containerID)
|
|
|
|
|
|
|
|
if c == nil {
|
|
|
|
return fmt.Errorf("could not find container %q", containerID)
|
|
|
|
}
|
2017-02-08 14:11:52 +00:00
|
|
|
|
2017-07-17 12:25:32 +00:00
|
|
|
if err := ss.runtimeServer.Runtime().UpdateStatus(c); err != nil {
|
2017-02-08 14:11:52 +00:00
|
|
|
return err
|
2016-12-15 11:58:45 +00:00
|
|
|
}
|
|
|
|
|
2017-07-17 12:25:32 +00:00
|
|
|
cState := ss.runtimeServer.Runtime().ContainerStatus(c)
|
2016-12-15 11:58:45 +00:00
|
|
|
if !(cState.Status == oci.ContainerStateRunning || cState.Status == oci.ContainerStateCreated) {
|
2017-02-08 14:11:52 +00:00
|
|
|
return fmt.Errorf("container is not created or running")
|
2016-12-15 11:58:45 +00:00
|
|
|
}
|
|
|
|
|
2017-11-30 09:43:35 +00:00
|
|
|
f, err := ioutil.TempFile("", "exec-process")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(f.Name())
|
|
|
|
|
|
|
|
pspec := c.Spec().Process
|
|
|
|
pspec.Args = cmd
|
|
|
|
processJSON, err := json.Marshal(pspec)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-04-20 17:51:40 +00:00
|
|
|
args := []string{"exec"}
|
|
|
|
if tty {
|
|
|
|
args = append(args, "-t")
|
|
|
|
}
|
2017-11-30 09:43:35 +00:00
|
|
|
args = append(args, "-p", f.Name())
|
2017-06-23 22:01:05 +00:00
|
|
|
args = append(args, c.ID())
|
2017-07-17 12:25:32 +00:00
|
|
|
execCmd := exec.Command(ss.runtimeServer.Runtime().Path(c), args...)
|
2017-04-20 17:51:40 +00:00
|
|
|
var cmdErr error
|
|
|
|
if tty {
|
|
|
|
p, err := kubecontainer.StartPty(execCmd)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer p.Close()
|
2016-12-15 11:58:45 +00:00
|
|
|
|
2017-04-20 17:51:40 +00:00
|
|
|
// make sure to close the stdout stream
|
|
|
|
defer stdout.Close()
|
|
|
|
|
2017-08-04 11:13:19 +00:00
|
|
|
kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
|
2017-04-20 17:51:40 +00:00
|
|
|
term.SetSize(p.Fd(), size)
|
|
|
|
})
|
|
|
|
|
|
|
|
if stdin != nil {
|
2017-06-10 16:33:35 +00:00
|
|
|
go pools.Copy(p, stdin)
|
2017-04-20 17:51:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if stdout != nil {
|
2017-06-10 16:33:35 +00:00
|
|
|
go pools.Copy(stdout, p)
|
2017-04-20 17:51:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cmdErr = execCmd.Wait()
|
|
|
|
} else {
|
|
|
|
if stdin != nil {
|
|
|
|
// Use an os.Pipe here as it returns true *os.File objects.
|
|
|
|
// This way, if you run 'kubectl exec <pod> -i bash' (no tty) and type 'exit',
|
|
|
|
// the call below to execCmd.Run() can unblock because its Stdin is the read half
|
|
|
|
// of the pipe.
|
|
|
|
r, w, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-06-10 16:33:35 +00:00
|
|
|
go pools.Copy(w, stdin)
|
2017-04-20 17:51:40 +00:00
|
|
|
|
|
|
|
execCmd.Stdin = r
|
|
|
|
}
|
|
|
|
if stdout != nil {
|
|
|
|
execCmd.Stdout = stdout
|
|
|
|
}
|
|
|
|
if stderr != nil {
|
|
|
|
execCmd.Stderr = stderr
|
|
|
|
}
|
|
|
|
|
|
|
|
cmdErr = execCmd.Run()
|
2017-02-08 14:11:52 +00:00
|
|
|
}
|
2016-12-15 11:58:45 +00:00
|
|
|
|
2017-04-20 17:51:40 +00:00
|
|
|
if exitErr, ok := cmdErr.(*exec.ExitError); ok {
|
|
|
|
return &utilexec.ExitErrorWrapper{ExitError: exitErr}
|
|
|
|
}
|
|
|
|
return cmdErr
|
2016-11-22 16:49:54 +00:00
|
|
|
}
|