github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/compat/exec.go (about)

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