github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/api/handlers/compat/containers_attach.go (about)

     1  package compat
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  
     7  	"github.com/containers/libpod/libpod"
     8  	"github.com/containers/libpod/libpod/define"
     9  	"github.com/containers/libpod/pkg/api/handlers/utils"
    10  	"github.com/gorilla/schema"
    11  	"github.com/pkg/errors"
    12  	"github.com/sirupsen/logrus"
    13  	"k8s.io/client-go/tools/remotecommand"
    14  )
    15  
    16  func AttachContainer(w http.ResponseWriter, r *http.Request) {
    17  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    18  	decoder := r.Context().Value("decoder").(*schema.Decoder)
    19  
    20  	query := struct {
    21  		DetachKeys string `schema:"detachKeys"`
    22  		Logs       bool   `schema:"logs"`
    23  		Stream     bool   `schema:"stream"`
    24  		Stdin      bool   `schema:"stdin"`
    25  		Stdout     bool   `schema:"stdout"`
    26  		Stderr     bool   `schema:"stderr"`
    27  	}{
    28  		Stream: true,
    29  	}
    30  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
    31  		utils.Error(w, "Error parsing parameters", http.StatusBadRequest, err)
    32  		return
    33  	}
    34  
    35  	// Detach keys: explicitly set to "" is very different from unset
    36  	// TODO: Our format for parsing these may be different from Docker.
    37  	var detachKeys *string
    38  	if _, found := r.URL.Query()["detachKeys"]; found {
    39  		detachKeys = &query.DetachKeys
    40  	}
    41  
    42  	streams := new(libpod.HTTPAttachStreams)
    43  	streams.Stdout = true
    44  	streams.Stderr = true
    45  	streams.Stdin = true
    46  	useStreams := false
    47  	if _, found := r.URL.Query()["stdin"]; found {
    48  		streams.Stdin = query.Stdin
    49  		useStreams = true
    50  	}
    51  	if _, found := r.URL.Query()["stdout"]; found {
    52  		streams.Stdout = query.Stdout
    53  		useStreams = true
    54  	}
    55  	if _, found := r.URL.Query()["stderr"]; found {
    56  		streams.Stderr = query.Stderr
    57  		useStreams = true
    58  	}
    59  	if !useStreams {
    60  		streams = nil
    61  	}
    62  	if useStreams && !streams.Stdout && !streams.Stderr && !streams.Stdin {
    63  		utils.Error(w, "Parameter conflict", http.StatusBadRequest, errors.Errorf("at least one of stdin, stdout, stderr must be true"))
    64  		return
    65  	}
    66  
    67  	// At least one of these must be set
    68  	if !query.Stream && !query.Logs {
    69  		utils.Error(w, "Unsupported parameter", http.StatusBadRequest, errors.Errorf("at least one of Logs or Stream must be set"))
    70  		return
    71  	}
    72  
    73  	name := utils.GetName(r)
    74  	ctr, err := runtime.LookupContainer(name)
    75  	if err != nil {
    76  		utils.ContainerNotFound(w, name, err)
    77  		return
    78  	}
    79  
    80  	state, err := ctr.State()
    81  	if err != nil {
    82  		utils.InternalServerError(w, err)
    83  		return
    84  	}
    85  	// For Docker compatibility, we need to re-initialize containers in these states.
    86  	if state == define.ContainerStateConfigured || state == define.ContainerStateExited {
    87  		if err := ctr.Init(r.Context()); err != nil {
    88  			utils.InternalServerError(w, errors.Wrapf(err, "error preparing container %s for attach", ctr.ID()))
    89  			return
    90  		}
    91  	} else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) {
    92  		utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers"))
    93  		return
    94  	}
    95  
    96  	// Hijack the connection
    97  	hijacker, ok := w.(http.Hijacker)
    98  	if !ok {
    99  		utils.InternalServerError(w, errors.Errorf("unable to hijack connection"))
   100  		return
   101  	}
   102  
   103  	connection, buffer, err := hijacker.Hijack()
   104  	if err != nil {
   105  		utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection"))
   106  		return
   107  	}
   108  
   109  	// This header string sourced from Docker:
   110  	// https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go
   111  	// Using literally to ensure compatability with existing clients.
   112  	fmt.Fprintf(connection, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
   113  
   114  	logrus.Debugf("Hijack for attach of container %s successful", ctr.ID())
   115  
   116  	// Perform HTTP attach.
   117  	// HTTPAttach will handle everything about the connection from here on
   118  	// (including closing it and writing errors to it).
   119  	if err := ctr.HTTPAttach(connection, buffer, streams, detachKeys, nil, query.Stream, query.Logs); err != nil {
   120  		// We can't really do anything about errors anymore. HTTPAttach
   121  		// should be writing them to the connection.
   122  		logrus.Errorf("Error attaching to container %s: %v", ctr.ID(), err)
   123  	}
   124  
   125  	logrus.Debugf("Attach for container %s completed successfully", ctr.ID())
   126  }
   127  
   128  func ResizeContainer(w http.ResponseWriter, r *http.Request) {
   129  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   130  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   131  
   132  	query := struct {
   133  		Height uint16 `schema:"h"`
   134  		Width  uint16 `schema:"w"`
   135  	}{}
   136  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   137  		// This is not a 400, despite the fact that is should be, for
   138  		// compatibility reasons.
   139  		utils.InternalServerError(w, errors.Wrapf(err, "error parsing query options"))
   140  		return
   141  	}
   142  
   143  	name := utils.GetName(r)
   144  	ctr, err := runtime.LookupContainer(name)
   145  	if err != nil {
   146  		utils.ContainerNotFound(w, name, err)
   147  		return
   148  	}
   149  
   150  	newSize := remotecommand.TerminalSize{
   151  		Width:  query.Width,
   152  		Height: query.Height,
   153  	}
   154  	if err := ctr.AttachResize(newSize); err != nil {
   155  		utils.InternalServerError(w, err)
   156  		return
   157  	}
   158  	// This is not a 204, even though we write nothing, for compatibility
   159  	// reasons.
   160  	utils.WriteResponse(w, http.StatusOK, "")
   161  }