github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/api/handlers/compat/exec.go (about)

     1  package compat
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"github.com/containers/podman/v2/libpod"
    10  	"github.com/containers/podman/v2/libpod/define"
    11  	"github.com/containers/podman/v2/pkg/api/handlers"
    12  	"github.com/containers/podman/v2/pkg/api/handlers/utils"
    13  	"github.com/containers/podman/v2/pkg/api/server/idle"
    14  	"github.com/containers/podman/v2/pkg/specgen/generate"
    15  	"github.com/gorilla/mux"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  // ExecCreateHandler creates an exec session for a given container.
    21  func ExecCreateHandler(w http.ResponseWriter, r *http.Request) {
    22  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    23  
    24  	input := new(handlers.ExecCreateConfig)
    25  	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
    26  		utils.InternalServerError(w, errors.Wrapf(err, "error decoding request body as JSON"))
    27  		return
    28  	}
    29  
    30  	ctrName := utils.GetName(r)
    31  	ctr, err := runtime.LookupContainer(ctrName)
    32  	if err != nil {
    33  		utils.ContainerNotFound(w, ctrName, err)
    34  		return
    35  	}
    36  
    37  	libpodConfig := new(libpod.ExecConfig)
    38  	libpodConfig.Command = input.Cmd
    39  	libpodConfig.Terminal = input.Tty
    40  	libpodConfig.AttachStdin = input.AttachStdin
    41  	libpodConfig.AttachStderr = input.AttachStderr
    42  	libpodConfig.AttachStdout = input.AttachStdout
    43  	if input.DetachKeys != "" {
    44  		libpodConfig.DetachKeys = &input.DetachKeys
    45  	}
    46  	libpodConfig.Environment = make(map[string]string)
    47  	for _, envStr := range input.Env {
    48  		split := strings.SplitN(envStr, "=", 2)
    49  		if len(split) != 2 {
    50  			utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, errors.Errorf("environment variable %q badly formed, must be key=value", envStr))
    51  			return
    52  		}
    53  		libpodConfig.Environment[split[0]] = split[1]
    54  	}
    55  	libpodConfig.WorkDir = input.WorkingDir
    56  	libpodConfig.Privileged = input.Privileged
    57  	libpodConfig.User = input.User
    58  
    59  	// Make our exit command
    60  	storageConfig := runtime.StorageConfig()
    61  	runtimeConfig, err := runtime.GetConfig()
    62  	if err != nil {
    63  		utils.InternalServerError(w, err)
    64  		return
    65  	}
    66  	// Automatically log to syslog if the server has log-level=debug set
    67  	exitCommandArgs, err := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, logrus.IsLevelEnabled(logrus.DebugLevel), true, true)
    68  	if err != nil {
    69  		utils.InternalServerError(w, err)
    70  		return
    71  	}
    72  	libpodConfig.ExitCommand = exitCommandArgs
    73  
    74  	// Run the exit command after 5 minutes, to mimic Docker's exec cleanup
    75  	// behavior.
    76  	libpodConfig.ExitCommandDelay = 5 * 60
    77  
    78  	sessID, err := ctr.ExecCreate(libpodConfig)
    79  	if err != nil {
    80  		if errors.Cause(err) == define.ErrCtrStateInvalid {
    81  			// Check if the container is paused. If so, return a 409
    82  			state, err := ctr.State()
    83  			if err == nil {
    84  				// Ignore the error != nil case. We're already
    85  				// throwing an InternalServerError below.
    86  				if state == define.ContainerStatePaused {
    87  					utils.Error(w, "Container is paused", http.StatusConflict, errors.Errorf("cannot create exec session as container %s is paused", ctr.ID()))
    88  					return
    89  				}
    90  			}
    91  		}
    92  		utils.InternalServerError(w, err)
    93  		return
    94  	}
    95  
    96  	resp := new(handlers.ExecCreateResponse)
    97  	resp.ID = sessID
    98  
    99  	utils.WriteResponse(w, http.StatusCreated, resp)
   100  }
   101  
   102  // ExecInspectHandler inspects a given exec session.
   103  func ExecInspectHandler(w http.ResponseWriter, r *http.Request) {
   104  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   105  
   106  	sessionID := mux.Vars(r)["id"]
   107  	sessionCtr, err := runtime.GetExecSessionContainer(sessionID)
   108  	if err != nil {
   109  		utils.Error(w, fmt.Sprintf("No such exec session: %s", sessionID), http.StatusNotFound, err)
   110  		return
   111  	}
   112  
   113  	logrus.Debugf("Inspecting exec session %s of container %s", sessionID, sessionCtr.ID())
   114  
   115  	session, err := sessionCtr.ExecSession(sessionID)
   116  	if err != nil {
   117  		utils.InternalServerError(w, errors.Wrapf(err, "error retrieving exec session %s from container %s", sessionID, sessionCtr.ID()))
   118  		return
   119  	}
   120  
   121  	inspectOut, err := session.Inspect()
   122  	if err != nil {
   123  		utils.InternalServerError(w, err)
   124  		return
   125  	}
   126  
   127  	utils.WriteResponse(w, http.StatusOK, inspectOut)
   128  }
   129  
   130  // ExecStartHandler runs a given exec session.
   131  func ExecStartHandler(w http.ResponseWriter, r *http.Request) {
   132  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   133  
   134  	sessionID := mux.Vars(r)["id"]
   135  
   136  	// TODO: We should read/support Tty from here.
   137  	bodyParams := new(handlers.ExecStartConfig)
   138  
   139  	if err := json.NewDecoder(r.Body).Decode(&bodyParams); err != nil {
   140  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
   141  			errors.Wrapf(err, "failed to decode parameters for %s", r.URL.String()))
   142  		return
   143  	}
   144  	// TODO: Verify TTY setting against what inspect session was made with
   145  
   146  	sessionCtr, err := runtime.GetExecSessionContainer(sessionID)
   147  	if err != nil {
   148  		utils.Error(w, fmt.Sprintf("No such exec session: %s", sessionID), http.StatusNotFound, err)
   149  		return
   150  	}
   151  
   152  	logrus.Debugf("Starting exec session %s of container %s", sessionID, sessionCtr.ID())
   153  
   154  	state, err := sessionCtr.State()
   155  	if err != nil {
   156  		utils.InternalServerError(w, err)
   157  		return
   158  	}
   159  	if state != define.ContainerStateRunning {
   160  		utils.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict, errors.Errorf("cannot exec in a container that is not running; container %s is %s", sessionCtr.ID(), state.String()))
   161  		return
   162  	}
   163  
   164  	if bodyParams.Detach {
   165  		// If we are detaching, we do NOT want to hijack.
   166  		// Instead, we perform a detached start, and return 200 if
   167  		// successful.
   168  		if err := sessionCtr.ExecStart(sessionID); err != nil {
   169  			utils.InternalServerError(w, err)
   170  			return
   171  		}
   172  		// This is a 200 despite having no content
   173  		utils.WriteResponse(w, http.StatusOK, "")
   174  		return
   175  	}
   176  
   177  	logErr := func(e error) {
   178  		logrus.Error(errors.Wrapf(e, "error attaching to container %s exec session %s", sessionCtr.ID(), sessionID))
   179  	}
   180  
   181  	hijackChan := make(chan bool, 1)
   182  	err = sessionCtr.ExecHTTPStartAndAttach(sessionID, r, w, nil, nil, nil, hijackChan)
   183  
   184  	if <-hijackChan {
   185  		// If connection was Hijacked, we have to signal it's being closed
   186  		t := r.Context().Value("idletracker").(*idle.Tracker)
   187  		defer t.Close()
   188  
   189  		if err != nil {
   190  			// Cannot report error to client as a 500 as the Upgrade set status to 101
   191  			logErr(err)
   192  		}
   193  	} else {
   194  		// If the Hijack failed we are going to assume we can still inform client of failure
   195  		utils.InternalServerError(w, err)
   196  		logErr(err)
   197  	}
   198  	logrus.Debugf("Attach for container %s exec session %s completed successfully", sessionCtr.ID(), sessionID)
   199  }