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

     1  package compat
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"net/http"
     7  	"strconv"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/containers/libpod/libpod"
    13  	"github.com/containers/libpod/libpod/define"
    14  	"github.com/containers/libpod/libpod/logs"
    15  	"github.com/containers/libpod/pkg/api/handlers"
    16  	"github.com/containers/libpod/pkg/api/handlers/utils"
    17  	"github.com/containers/libpod/pkg/signal"
    18  	"github.com/containers/libpod/pkg/util"
    19  	"github.com/gorilla/schema"
    20  	"github.com/pkg/errors"
    21  	log "github.com/sirupsen/logrus"
    22  )
    23  
    24  func RemoveContainer(w http.ResponseWriter, r *http.Request) {
    25  	decoder := r.Context().Value("decoder").(*schema.Decoder)
    26  	query := struct {
    27  		Force bool `schema:"force"`
    28  		Vols  bool `schema:"v"`
    29  		Link  bool `schema:"link"`
    30  	}{
    31  		// override any golang type defaults
    32  	}
    33  
    34  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
    35  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
    36  			errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
    37  		return
    38  	}
    39  
    40  	if query.Link && !utils.IsLibpodRequest(r) {
    41  		utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
    42  			utils.ErrLinkNotSupport)
    43  		return
    44  	}
    45  
    46  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    47  	name := utils.GetName(r)
    48  	con, err := runtime.LookupContainer(name)
    49  	if err != nil {
    50  		utils.ContainerNotFound(w, name, err)
    51  		return
    52  	}
    53  
    54  	if err := runtime.RemoveContainer(r.Context(), con, query.Force, query.Vols); err != nil {
    55  		utils.InternalServerError(w, err)
    56  		return
    57  	}
    58  	utils.WriteResponse(w, http.StatusNoContent, "")
    59  }
    60  
    61  func ListContainers(w http.ResponseWriter, r *http.Request) {
    62  	var (
    63  		containers []*libpod.Container
    64  		err        error
    65  	)
    66  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
    67  	decoder := r.Context().Value("decoder").(*schema.Decoder)
    68  	query := struct {
    69  		All     bool                `schema:"all"`
    70  		Limit   int                 `schema:"limit"`
    71  		Size    bool                `schema:"size"`
    72  		Filters map[string][]string `schema:"filters"`
    73  	}{
    74  		// override any golang type defaults
    75  	}
    76  
    77  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
    78  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
    79  		return
    80  	}
    81  	if query.All {
    82  		containers, err = runtime.GetAllContainers()
    83  	} else {
    84  		containers, err = runtime.GetRunningContainers()
    85  	}
    86  	if err != nil {
    87  		utils.InternalServerError(w, err)
    88  		return
    89  	}
    90  	if _, found := r.URL.Query()["limit"]; found && query.Limit != -1 {
    91  		last := query.Limit
    92  		if len(containers) > last {
    93  			containers = containers[len(containers)-last:]
    94  		}
    95  	}
    96  	// TODO filters still need to be applied
    97  	var list = make([]*handlers.Container, len(containers))
    98  	for i, ctnr := range containers {
    99  		api, err := handlers.LibpodToContainer(ctnr, query.Size)
   100  		if err != nil {
   101  			utils.InternalServerError(w, err)
   102  			return
   103  		}
   104  		list[i] = api
   105  	}
   106  	utils.WriteResponse(w, http.StatusOK, list)
   107  }
   108  
   109  func GetContainer(w http.ResponseWriter, r *http.Request) {
   110  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   111  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   112  	query := struct {
   113  		Size bool `schema:"size"`
   114  	}{
   115  		// override any golang type defaults
   116  	}
   117  
   118  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   119  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
   120  		return
   121  	}
   122  
   123  	name := utils.GetName(r)
   124  	ctnr, err := runtime.LookupContainer(name)
   125  	if err != nil {
   126  		utils.ContainerNotFound(w, name, err)
   127  		return
   128  	}
   129  	api, err := handlers.LibpodToContainerJSON(ctnr, query.Size)
   130  	if err != nil {
   131  		utils.InternalServerError(w, err)
   132  		return
   133  	}
   134  	utils.WriteResponse(w, http.StatusOK, api)
   135  }
   136  
   137  func KillContainer(w http.ResponseWriter, r *http.Request) {
   138  	// /{version}/containers/(name)/kill
   139  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   140  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   141  	query := struct {
   142  		Signal string `schema:"signal"`
   143  	}{
   144  		Signal: "KILL",
   145  	}
   146  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   147  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
   148  		return
   149  	}
   150  
   151  	sig, err := signal.ParseSignalNameOrNumber(query.Signal)
   152  	if err != nil {
   153  		utils.InternalServerError(w, err)
   154  		return
   155  	}
   156  	name := utils.GetName(r)
   157  	con, err := runtime.LookupContainer(name)
   158  	if err != nil {
   159  		utils.ContainerNotFound(w, name, err)
   160  		return
   161  	}
   162  
   163  	state, err := con.State()
   164  	if err != nil {
   165  		utils.InternalServerError(w, err)
   166  		return
   167  	}
   168  
   169  	// If the Container is stopped already, send a 409
   170  	if state == define.ContainerStateStopped || state == define.ContainerStateExited {
   171  		utils.Error(w, fmt.Sprintf("Container %s is not running", name), http.StatusConflict, errors.New(fmt.Sprintf("Cannot kill Container %s, it is not running", name)))
   172  		return
   173  	}
   174  
   175  	err = con.Kill(uint(sig))
   176  	if err != nil {
   177  		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name))
   178  	}
   179  
   180  	if utils.IsLibpodRequest(r) {
   181  		// the kill behavior for docker differs from podman in that they appear to wait
   182  		// for the Container to croak so the exit code is accurate immediately after the
   183  		// kill is sent.  libpod does not.  but we can add a wait here only for the docker
   184  		// side of things and mimic that behavior
   185  		if _, err = con.Wait(); err != nil {
   186  			utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to wait for Container %s", con.ID()))
   187  			return
   188  		}
   189  	}
   190  	// Success
   191  	utils.WriteResponse(w, http.StatusNoContent, "")
   192  }
   193  
   194  func WaitContainer(w http.ResponseWriter, r *http.Request) {
   195  	var msg string
   196  	// /{version}/containers/(name)/wait
   197  	exitCode, err := utils.WaitContainer(w, r)
   198  	if err != nil {
   199  		return
   200  	}
   201  	utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
   202  		StatusCode: int(exitCode),
   203  		Error: struct {
   204  			Message string
   205  		}{
   206  			Message: msg,
   207  		},
   208  	})
   209  }
   210  
   211  func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
   212  	decoder := r.Context().Value("decoder").(*schema.Decoder)
   213  	runtime := r.Context().Value("runtime").(*libpod.Runtime)
   214  
   215  	query := struct {
   216  		Follow     bool   `schema:"follow"`
   217  		Stdout     bool   `schema:"stdout"`
   218  		Stderr     bool   `schema:"stderr"`
   219  		Since      string `schema:"since"`
   220  		Until      string `schema:"until"`
   221  		Timestamps bool   `schema:"timestamps"`
   222  		Tail       string `schema:"tail"`
   223  	}{
   224  		Tail: "all",
   225  	}
   226  	if err := decoder.Decode(&query, r.URL.Query()); err != nil {
   227  		utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
   228  		return
   229  	}
   230  
   231  	if !(query.Stdout || query.Stderr) {
   232  		msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest))
   233  		utils.Error(w, msg, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String()))
   234  		return
   235  	}
   236  
   237  	name := utils.GetName(r)
   238  	ctnr, err := runtime.LookupContainer(name)
   239  	if err != nil {
   240  		utils.ContainerNotFound(w, name, err)
   241  		return
   242  	}
   243  
   244  	var tail int64 = -1
   245  	if query.Tail != "all" {
   246  		tail, err = strconv.ParseInt(query.Tail, 0, 64)
   247  		if err != nil {
   248  			utils.BadRequest(w, "tail", query.Tail, err)
   249  			return
   250  		}
   251  	}
   252  
   253  	var since time.Time
   254  	if _, found := r.URL.Query()["since"]; found {
   255  		since, err = util.ParseInputTime(query.Since)
   256  		if err != nil {
   257  			utils.BadRequest(w, "since", query.Since, err)
   258  			return
   259  		}
   260  	}
   261  
   262  	var until time.Time
   263  	if _, found := r.URL.Query()["until"]; found {
   264  		// FIXME: until != since but the logs backend does not yet support until.
   265  		since, err = util.ParseInputTime(query.Until)
   266  		if err != nil {
   267  			utils.BadRequest(w, "until", query.Until, err)
   268  			return
   269  		}
   270  	}
   271  
   272  	options := &logs.LogOptions{
   273  		Details:    true,
   274  		Follow:     query.Follow,
   275  		Since:      since,
   276  		Tail:       tail,
   277  		Timestamps: query.Timestamps,
   278  	}
   279  
   280  	var wg sync.WaitGroup
   281  	options.WaitGroup = &wg
   282  
   283  	logChannel := make(chan *logs.LogLine, tail+1)
   284  	if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil {
   285  		utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
   286  		return
   287  	}
   288  	go func() {
   289  		wg.Wait()
   290  		close(logChannel)
   291  	}()
   292  
   293  	w.WriteHeader(http.StatusOK)
   294  	var builder strings.Builder
   295  	for ok := true; ok; ok = query.Follow {
   296  		for line := range logChannel {
   297  			if _, found := r.URL.Query()["until"]; found {
   298  				if line.Time.After(until) {
   299  					break
   300  				}
   301  			}
   302  
   303  			// Reset variables we're ready to loop again
   304  			builder.Reset()
   305  			header := [8]byte{}
   306  
   307  			switch line.Device {
   308  			case "stdout":
   309  				if !query.Stdout {
   310  					continue
   311  				}
   312  				header[0] = 1
   313  			case "stderr":
   314  				if !query.Stderr {
   315  					continue
   316  				}
   317  				header[0] = 2
   318  			default:
   319  				// Logging and moving on is the best we can do here. We may have already sent
   320  				// a Status and Content-Type to client therefore we can no longer report an error.
   321  				log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
   322  				continue
   323  			}
   324  
   325  			if query.Timestamps {
   326  				builder.WriteString(line.Time.Format(time.RFC3339))
   327  				builder.WriteRune(' ')
   328  			}
   329  			builder.WriteString(line.Msg)
   330  			// Build header and output entry
   331  			binary.BigEndian.PutUint32(header[4:], uint32(len(header)+builder.Len()))
   332  			if _, err := w.Write(header[:]); err != nil {
   333  				log.Errorf("unable to write log output header: %q", err)
   334  			}
   335  			if _, err := fmt.Fprint(w, builder.String()); err != nil {
   336  				log.Errorf("unable to write builder string: %q", err)
   337  			}
   338  			if flusher, ok := w.(http.Flusher); ok {
   339  				flusher.Flush()
   340  			}
   341  		}
   342  	}
   343  }