Files
podman/pkg/api/handlers/compat/containers.go
zhangguanzhang 3218736cff fix api fails with 'strconv.ParseUint: parsing "tcp": invalid syntax'
Signed-off-by: zhangguanzhang <zhangguanzhang@qq.com>
2020-06-10 00:03:04 +08:00

541 lines
14 KiB
Go

package compat
import (
"encoding/binary"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/signal"
"github.com/containers/libpod/pkg/util"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
"github.com/gorilla/schema"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
func RemoveContainer(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Force bool `schema:"force"`
Vols bool `schema:"v"`
Link bool `schema:"link"`
}{
// override any golang type defaults
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
if query.Link && !utils.IsLibpodRequest(r) {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
utils.ErrLinkNotSupport)
return
}
runtime := r.Context().Value("runtime").(*libpod.Runtime)
name := utils.GetName(r)
con, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
return
}
if err := runtime.RemoveContainer(r.Context(), con, query.Force, query.Vols); err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusNoContent, "")
}
func ListContainers(w http.ResponseWriter, r *http.Request) {
var (
containers []*libpod.Container
err error
)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
All bool `schema:"all"`
Limit int `schema:"limit"`
Size bool `schema:"size"`
Filters map[string][]string `schema:"filters"`
}{
// override any golang type defaults
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
if query.All {
containers, err = runtime.GetAllContainers()
} else {
containers, err = runtime.GetRunningContainers()
}
if err != nil {
utils.InternalServerError(w, err)
return
}
if _, found := r.URL.Query()["limit"]; found && query.Limit != -1 {
last := query.Limit
if len(containers) > last {
containers = containers[len(containers)-last:]
}
}
// TODO filters still need to be applied
var list = make([]*handlers.Container, len(containers))
for i, ctnr := range containers {
api, err := LibpodToContainer(ctnr, query.Size)
if err != nil {
utils.InternalServerError(w, err)
return
}
list[i] = api
}
utils.WriteResponse(w, http.StatusOK, list)
}
func GetContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Size bool `schema:"size"`
}{
// override any golang type defaults
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
name := utils.GetName(r)
ctnr, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
return
}
api, err := LibpodToContainerJSON(ctnr, query.Size)
if err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusOK, api)
}
func KillContainer(w http.ResponseWriter, r *http.Request) {
// /{version}/containers/(name)/kill
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Signal string `schema:"signal"`
}{
Signal: "KILL",
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
sig, err := signal.ParseSignalNameOrNumber(query.Signal)
if err != nil {
utils.InternalServerError(w, err)
return
}
name := utils.GetName(r)
con, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
return
}
state, err := con.State()
if err != nil {
utils.InternalServerError(w, err)
return
}
// If the Container is stopped already, send a 409
if state == define.ContainerStateStopped || state == define.ContainerStateExited {
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)))
return
}
err = con.Kill(uint(sig))
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name))
}
if utils.IsLibpodRequest(r) {
// the kill behavior for docker differs from podman in that they appear to wait
// for the Container to croak so the exit code is accurate immediately after the
// kill is sent. libpod does not. but we can add a wait here only for the docker
// side of things and mimic that behavior
if _, err = con.Wait(); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to wait for Container %s", con.ID()))
return
}
}
// Success
utils.WriteResponse(w, http.StatusNoContent, "")
}
func WaitContainer(w http.ResponseWriter, r *http.Request) {
var msg string
// /{version}/containers/(name)/wait
exitCode, err := utils.WaitContainer(w, r)
if err != nil {
return
}
utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
StatusCode: int(exitCode),
Error: struct {
Message string
}{
Message: msg,
},
})
}
func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value("decoder").(*schema.Decoder)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
query := struct {
Follow bool `schema:"follow"`
Stdout bool `schema:"stdout"`
Stderr bool `schema:"stderr"`
Since string `schema:"since"`
Until string `schema:"until"`
Timestamps bool `schema:"timestamps"`
Tail string `schema:"tail"`
}{
Tail: "all",
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
if !(query.Stdout || query.Stderr) {
msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest))
utils.Error(w, msg, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String()))
return
}
name := utils.GetName(r)
ctnr, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
return
}
var tail int64 = -1
if query.Tail != "all" {
tail, err = strconv.ParseInt(query.Tail, 0, 64)
if err != nil {
utils.BadRequest(w, "tail", query.Tail, err)
return
}
}
var since time.Time
if _, found := r.URL.Query()["since"]; found {
since, err = util.ParseInputTime(query.Since)
if err != nil {
utils.BadRequest(w, "since", query.Since, err)
return
}
}
var until time.Time
if _, found := r.URL.Query()["until"]; found {
// FIXME: until != since but the logs backend does not yet support until.
since, err = util.ParseInputTime(query.Until)
if err != nil {
utils.BadRequest(w, "until", query.Until, err)
return
}
}
options := &logs.LogOptions{
Details: true,
Follow: query.Follow,
Since: since,
Tail: tail,
Timestamps: query.Timestamps,
}
var wg sync.WaitGroup
options.WaitGroup = &wg
logChannel := make(chan *logs.LogLine, tail+1)
if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil {
utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
return
}
go func() {
wg.Wait()
close(logChannel)
}()
w.WriteHeader(http.StatusOK)
var frame strings.Builder
header := make([]byte, 8)
for ok := true; ok; ok = query.Follow {
for line := range logChannel {
if _, found := r.URL.Query()["until"]; found {
if line.Time.After(until) {
break
}
}
// Reset buffer we're ready to loop again
frame.Reset()
switch line.Device {
case "stdout":
if !query.Stdout {
continue
}
header[0] = 1
case "stderr":
if !query.Stderr {
continue
}
header[0] = 2
default:
// Logging and moving on is the best we can do here. We may have already sent
// a Status and Content-Type to client therefore we can no longer report an error.
log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID())
continue
}
if query.Timestamps {
frame.WriteString(line.Time.Format(time.RFC3339))
frame.WriteString(" ")
}
frame.WriteString(line.Msg)
binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
if _, err := w.Write(header[0:8]); err != nil {
log.Errorf("unable to write log output header: %q", err)
}
if _, err := io.WriteString(w, frame.String()); err != nil {
log.Errorf("unable to write frame string: %q", err)
}
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
}
}
}
func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error) {
imageId, imageName := l.Image()
var (
err error
sizeRootFs int64
sizeRW int64
state define.ContainerStatus
)
if state, err = l.State(); err != nil {
return nil, err
}
stateStr := state.String()
if stateStr == "configured" {
stateStr = "created"
}
if sz {
if sizeRW, err = l.RWSize(); err != nil {
return nil, err
}
if sizeRootFs, err = l.RootFsSize(); err != nil {
return nil, err
}
}
return &handlers.Container{Container: types.Container{
ID: l.ID(),
Names: []string{fmt.Sprintf("/%s", l.Name())},
Image: imageName,
ImageID: imageId,
Command: strings.Join(l.Command(), " "),
Created: l.CreatedTime().Unix(),
Ports: nil,
SizeRw: sizeRW,
SizeRootFs: sizeRootFs,
Labels: l.Labels(),
State: stateStr,
Status: "",
HostConfig: struct {
NetworkMode string `json:",omitempty"`
}{
"host"},
NetworkSettings: nil,
Mounts: nil,
},
ContainerCreateConfig: types.ContainerCreateConfig{},
}, nil
}
func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, error) {
_, imageName := l.Image()
inspect, err := l.Inspect(sz)
if err != nil {
return nil, err
}
i, err := json.Marshal(inspect.State)
if err != nil {
return nil, err
}
state := types.ContainerState{}
if err := json.Unmarshal(i, &state); err != nil {
return nil, err
}
// docker considers paused to be running
if state.Paused {
state.Running = true
}
h, err := json.Marshal(inspect.HostConfig)
if err != nil {
return nil, err
}
hc := container.HostConfig{}
if err := json.Unmarshal(h, &hc); err != nil {
return nil, err
}
g, err := json.Marshal(inspect.GraphDriver)
if err != nil {
return nil, err
}
graphDriver := types.GraphDriverData{}
if err := json.Unmarshal(g, &graphDriver); err != nil {
return nil, err
}
cb := types.ContainerJSONBase{
ID: l.ID(),
Created: l.CreatedTime().String(),
Path: "",
Args: nil,
State: &state,
Image: imageName,
ResolvConfPath: inspect.ResolvConfPath,
HostnamePath: inspect.HostnamePath,
HostsPath: inspect.HostsPath,
LogPath: l.LogPath(),
Node: nil,
Name: fmt.Sprintf("/%s", l.Name()),
RestartCount: 0,
Driver: inspect.Driver,
Platform: "linux",
MountLabel: inspect.MountLabel,
ProcessLabel: inspect.ProcessLabel,
AppArmorProfile: inspect.AppArmorProfile,
ExecIDs: inspect.ExecIDs,
HostConfig: &hc,
GraphDriver: graphDriver,
SizeRw: inspect.SizeRw,
SizeRootFs: &inspect.SizeRootFs,
}
stopTimeout := int(l.StopTimeout())
ports := make(nat.PortSet)
for p := range inspect.HostConfig.PortBindings {
splitp := strings.SplitN(p, "/", 2)
if len(splitp) != 2 {
return nil, errors.Errorf("PORT/PROTOCOL Format required for %q", p)
}
port, err := nat.NewPort(splitp[1], splitp[0])
if err != nil {
return nil, err
}
ports[port] = struct{}{}
}
config := container.Config{
Hostname: l.Hostname(),
Domainname: inspect.Config.DomainName,
User: l.User(),
AttachStdin: inspect.Config.AttachStdin,
AttachStdout: inspect.Config.AttachStdout,
AttachStderr: inspect.Config.AttachStderr,
ExposedPorts: ports,
Tty: inspect.Config.Tty,
OpenStdin: inspect.Config.OpenStdin,
StdinOnce: inspect.Config.StdinOnce,
Env: inspect.Config.Env,
Cmd: inspect.Config.Cmd,
Healthcheck: nil,
ArgsEscaped: false,
Image: imageName,
Volumes: nil,
WorkingDir: l.WorkingDir(),
Entrypoint: l.Entrypoint(),
NetworkDisabled: false,
MacAddress: "",
OnBuild: nil,
Labels: l.Labels(),
StopSignal: string(l.StopSignal()),
StopTimeout: &stopTimeout,
Shell: nil,
}
m, err := json.Marshal(inspect.Mounts)
if err != nil {
return nil, err
}
mounts := []types.MountPoint{}
if err := json.Unmarshal(m, &mounts); err != nil {
return nil, err
}
networkSettingsDefault := types.DefaultNetworkSettings{
EndpointID: "",
Gateway: "",
GlobalIPv6Address: "",
GlobalIPv6PrefixLen: 0,
IPAddress: "",
IPPrefixLen: 0,
IPv6Gateway: "",
MacAddress: l.Config().StaticMAC.String(),
}
networkSettings := types.NetworkSettings{
NetworkSettingsBase: types.NetworkSettingsBase{},
DefaultNetworkSettings: networkSettingsDefault,
Networks: nil,
}
c := types.ContainerJSON{
ContainerJSONBase: &cb,
Mounts: mounts,
Config: &config,
NetworkSettings: &networkSettings,
}
return &c, nil
}