mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-08-14 12:49:36 +00:00
The Docker API 1.44 deprecates the fields HairpinMode, LinkLocalIPv6Address,
LinkLocalIPv6PrefixLen, SecondaryIPAddresses, SecondaryIPv6Addresses available in
NetworkSettings when calling GET /containers/{id}/json and will be removed in a future release.
You should instead look for the default network in NetworkSettings.Networks.
The fields are removed in 1.52. Version gate SecondaryIPAddresses, SecondaryIPv6Addresses
in the handler and update test. HairpinMode, LinkLocalIPv6Address, LinkLocalIPv6PrefixLen
are not returned by the compat endpoint as the response is serialized
to the moby/moby/api structure missing these fields.
Fixes: https://redhat.atlassian.net/browse/RUN-3323
Signed-off-by: Marek Simek <msimek@redhat.com>
916 lines
26 KiB
Go
916 lines
26 KiB
Go
//go:build !remote && (linux || freebsd)
|
|
|
|
package compat
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/netip"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/docker/go-connections/nat"
|
|
"github.com/docker/go-units"
|
|
"github.com/moby/moby/api/types/container"
|
|
"github.com/moby/moby/api/types/network"
|
|
"github.com/moby/moby/api/types/storage"
|
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/sirupsen/logrus"
|
|
"go.podman.io/podman/v6/libpod"
|
|
"go.podman.io/podman/v6/libpod/define"
|
|
"go.podman.io/podman/v6/pkg/api/handlers"
|
|
"go.podman.io/podman/v6/pkg/api/handlers/utils"
|
|
"go.podman.io/podman/v6/pkg/api/handlers/utils/apiutil"
|
|
api "go.podman.io/podman/v6/pkg/api/types"
|
|
"go.podman.io/podman/v6/pkg/domain/entities"
|
|
"go.podman.io/podman/v6/pkg/domain/filters"
|
|
"go.podman.io/podman/v6/pkg/domain/infra/abi"
|
|
"go.podman.io/podman/v6/pkg/ps"
|
|
"go.podman.io/podman/v6/pkg/signal"
|
|
"go.podman.io/podman/v6/pkg/specgenutil"
|
|
"go.podman.io/podman/v6/pkg/util"
|
|
)
|
|
|
|
func RemoveContainer(w http.ResponseWriter, r *http.Request) {
|
|
decoder := utils.GetDecoder(r)
|
|
query := struct {
|
|
Force bool `schema:"force"`
|
|
Ignore bool `schema:"ignore"`
|
|
Depend bool `schema:"depend"`
|
|
Link bool `schema:"link"`
|
|
Timeout *uint `schema:"timeout"`
|
|
DockerVolumes bool `schema:"v"`
|
|
LibpodVolumes bool `schema:"volumes"`
|
|
}{
|
|
// override any golang type defaults
|
|
}
|
|
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
|
|
return
|
|
}
|
|
|
|
options := entities.RmOptions{
|
|
Force: query.Force,
|
|
Ignore: query.Ignore,
|
|
}
|
|
if utils.IsLibpodRequest(r) {
|
|
options.Volumes = query.LibpodVolumes
|
|
options.Timeout = query.Timeout
|
|
options.Depend = query.Depend
|
|
} else {
|
|
if query.Link {
|
|
utils.Error(w, http.StatusBadRequest, utils.ErrLinkNotSupport)
|
|
return
|
|
}
|
|
options.Volumes = query.DockerVolumes
|
|
}
|
|
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
// Now use the ABI implementation to prevent us from having duplicate
|
|
// code.
|
|
containerEngine := abi.ContainerEngine{Libpod: runtime}
|
|
name := utils.GetName(r)
|
|
reports, err := containerEngine.ContainerRm(r.Context(), []string{name}, options)
|
|
if err != nil {
|
|
if errors.Is(err, define.ErrNoSuchCtr) {
|
|
utils.ContainerNotFound(w, name, err)
|
|
return
|
|
}
|
|
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
if len(reports) > 0 && reports[0].Err != nil {
|
|
err = reports[0].Err
|
|
if errors.Is(err, define.ErrNoSuchCtr) {
|
|
utils.ContainerNotFound(w, name, err)
|
|
return
|
|
}
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
if utils.IsLibpodRequest(r) {
|
|
utils.WriteResponse(w, http.StatusOK, reports)
|
|
return
|
|
}
|
|
utils.WriteResponse(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func ListContainers(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
decoder := utils.GetDecoder(r)
|
|
query := struct {
|
|
All bool `schema:"all"`
|
|
Limit int `schema:"limit"`
|
|
Size bool `schema:"size"`
|
|
}{
|
|
// override any golang type defaults
|
|
}
|
|
|
|
filterMap, err := util.PrepareFilters(r)
|
|
if err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to decode filter parameters for %s: %w", r.URL.String(), err))
|
|
return
|
|
}
|
|
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
|
|
return
|
|
}
|
|
|
|
filterFuncs := make([]libpod.ContainerFilter, 0, len(*filterMap))
|
|
all := query.All || query.Limit > 0
|
|
if len(*filterMap) > 0 {
|
|
for k, v := range *filterMap {
|
|
generatedFunc, err := filters.GenerateContainerFilterFuncs(k, v, runtime)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
filterFuncs = append(filterFuncs, generatedFunc)
|
|
}
|
|
}
|
|
|
|
// Docker thinks that if status is given as an input, then we should override
|
|
// the all setting and always deal with all containers.
|
|
if len((*filterMap)["status"]) > 0 {
|
|
all = true
|
|
}
|
|
if !all {
|
|
runningOnly, err := filters.GenerateContainerFilterFuncs("status", []string{define.ContainerStateRunning.String()}, runtime)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
filterFuncs = append(filterFuncs, runningOnly)
|
|
}
|
|
|
|
containers, err := runtime.GetContainers(false, filterFuncs...)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
if _, found := r.URL.Query()["limit"]; found && query.Limit > 0 {
|
|
// Sort the libpod containers
|
|
sort.Sort(ps.SortCreateTime{SortContainers: containers})
|
|
// we should perform the lopping before we start getting
|
|
// the expensive information on containers
|
|
if len(containers) > query.Limit {
|
|
containers = containers[:query.Limit]
|
|
}
|
|
}
|
|
list := make([]*handlers.Container, 0, len(containers))
|
|
// only include health if API version is >= 1.52 or no version is given (latest)
|
|
includeHealth := false
|
|
if _, err := utils.SupportedVersion(r, ">= 1.52.0"); err == nil || errors.Is(err, apiutil.ErrVersionNotGiven) {
|
|
includeHealth = true
|
|
}
|
|
for _, ctnr := range containers {
|
|
api, err := LibpodToContainer(ctnr, query.Size, includeHealth)
|
|
if err != nil {
|
|
if errors.Is(err, define.ErrNoSuchCtr) {
|
|
// container was removed between the initial fetch of the list and conversion
|
|
logrus.Debugf("Container %s removed between initial fetch and conversion, ignoring in output", ctnr.ID())
|
|
continue
|
|
}
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
list = append(list, api)
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, list)
|
|
}
|
|
|
|
func GetContainer(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
decoder := utils.GetDecoder(r)
|
|
query := struct {
|
|
Size bool `schema:"size"`
|
|
}{
|
|
// override any golang type defaults
|
|
}
|
|
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
|
|
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
|
|
}
|
|
if _, err := utils.SupportedVersion(r, ">=1.52.0"); err == nil || errors.Is(err, apiutil.ErrVersionNotGiven) {
|
|
if api.NetworkSettings != nil {
|
|
api.NetworkSettings.SecondaryIPAddresses = nil
|
|
api.NetworkSettings.SecondaryIPv6Addresses = nil
|
|
}
|
|
}
|
|
utils.WriteResponse(w, http.StatusOK, api)
|
|
}
|
|
|
|
func KillContainer(w http.ResponseWriter, r *http.Request) {
|
|
// /{version}/containers/(name)/kill
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
decoder := utils.GetDecoder(r)
|
|
query := struct {
|
|
Signal string `schema:"signal"`
|
|
}{
|
|
Signal: "KILL",
|
|
}
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
|
|
return
|
|
}
|
|
|
|
// Now use the ABI implementation to prevent us from having duplicate
|
|
// code.
|
|
containerEngine := abi.ContainerEngine{Libpod: runtime}
|
|
name := utils.GetName(r)
|
|
options := entities.KillOptions{
|
|
Signal: query.Signal,
|
|
}
|
|
report, err := containerEngine.ContainerKill(r.Context(), []string{name}, options)
|
|
if err != nil {
|
|
if errors.Is(err, define.ErrCtrStateInvalid) ||
|
|
errors.Is(err, define.ErrCtrStopped) {
|
|
utils.Error(w, http.StatusConflict, err)
|
|
return
|
|
}
|
|
if errors.Is(err, define.ErrNoSuchCtr) {
|
|
utils.ContainerNotFound(w, name, err)
|
|
return
|
|
}
|
|
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
if len(report) > 0 && report[0].Err != nil {
|
|
if errors.Is(report[0].Err, define.ErrCtrStateInvalid) ||
|
|
errors.Is(report[0].Err, define.ErrCtrStopped) {
|
|
utils.Error(w, http.StatusConflict, report[0].Err)
|
|
return
|
|
}
|
|
utils.InternalServerError(w, report[0].Err)
|
|
return
|
|
}
|
|
// Docker waits for the container to stop if the signal is 0 or
|
|
// SIGKILL.
|
|
if !utils.IsLibpodRequest(r) {
|
|
sig, err := signal.ParseSignalNameOrNumber(query.Signal)
|
|
if err != nil {
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
if sig == 0 || sig == syscall.SIGKILL {
|
|
opts := entities.WaitOptions{
|
|
Conditions: []string{define.ContainerStateExited.String(), define.ContainerStateStopped.String()},
|
|
Interval: time.Millisecond * 250,
|
|
}
|
|
if _, err := containerEngine.ContainerWait(r.Context(), []string{name}, opts); err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
// Success
|
|
utils.WriteResponse(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func WaitContainer(w http.ResponseWriter, r *http.Request) {
|
|
// /{version}/containers/(name)/wait
|
|
utils.WaitContainerDocker(w, r)
|
|
}
|
|
|
|
//nolint:staticcheck // LegacyNetworkSettings is deprecated but kept for Docker API compat < v1.52
|
|
func convertSecondaryIPPrefixLen(input *define.InspectNetworkSettings, output *handlers.LegacyNetworkSettings) {
|
|
for index, ip := range input.SecondaryIPAddresses {
|
|
output.SecondaryIPAddresses[index].PrefixLen = ip.PrefixLength
|
|
}
|
|
for index, ip := range input.SecondaryIPv6Addresses {
|
|
output.SecondaryIPv6Addresses[index].PrefixLen = ip.PrefixLength
|
|
}
|
|
}
|
|
|
|
func LibpodToContainer(l *libpod.Container, sz bool, includeHealth bool) (*handlers.Container, error) {
|
|
imageID, imageName := l.Image()
|
|
|
|
var (
|
|
err error
|
|
sizeRootFs int64
|
|
sizeRW int64
|
|
state define.ContainerStatus
|
|
status string
|
|
hostConfig = struct {
|
|
NetworkMode string `json:",omitempty"`
|
|
Annotations map[string]string `json:",omitempty"`
|
|
}{}
|
|
)
|
|
|
|
if state, err = l.State(); err != nil {
|
|
return nil, err
|
|
}
|
|
stateStr := state.String()
|
|
|
|
// Some docker states are not the same as ours. This makes sure the state string stays true to the Docker API.
|
|
// "stopped" and "stopping" are podman-internal states with no Docker equivalent;
|
|
// map them to the nearest Docker state so the compat API never leaks internal states.
|
|
switch state {
|
|
case define.ContainerStateCreated:
|
|
stateStr = define.ContainerStateConfigured.String()
|
|
case define.ContainerStateStopped:
|
|
stateStr = define.ContainerStateExited.String()
|
|
case define.ContainerStateStopping:
|
|
stateStr = define.ContainerStateRunning.String()
|
|
}
|
|
|
|
switch state {
|
|
case define.ContainerStateConfigured, define.ContainerStateCreated:
|
|
status = "Created"
|
|
case define.ContainerStateStopped, define.ContainerStateExited:
|
|
exitCode, _, err := l.ExitCode()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
finishedTime, err := l.FinishedTime()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
status = fmt.Sprintf("Exited (%d) %s ago", exitCode, units.HumanDuration(time.Since(finishedTime)))
|
|
case define.ContainerStateRunning, define.ContainerStatePaused:
|
|
startedTime, err := l.StartedTime()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
status = fmt.Sprintf("Up %s", units.HumanDuration(time.Since(startedTime)))
|
|
if state == define.ContainerStatePaused {
|
|
status += " (Paused)"
|
|
}
|
|
case define.ContainerStateRemoving:
|
|
status = "Removal In Progress"
|
|
case define.ContainerStateStopping:
|
|
status = "Stopping"
|
|
default:
|
|
status = "Unknown"
|
|
}
|
|
|
|
if sz {
|
|
if sizeRW, err = l.RWSize(); err != nil {
|
|
return nil, err
|
|
}
|
|
if sizeRootFs, err = l.RootFsSize(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
inspect, err := l.Inspect(false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ports := []container.PortSummary{}
|
|
for portKey, bindings := range inspect.NetworkSettings.Ports {
|
|
portNum, proto, ok := strings.Cut(portKey, "/")
|
|
if !ok {
|
|
return nil, fmt.Errorf("PORT/PROTOCOL format required for %q", portKey)
|
|
}
|
|
|
|
containerPort, err := strconv.Atoi(portNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(bindings) == 0 {
|
|
// Exposed but not published
|
|
ports = append(ports, container.PortSummary{
|
|
PrivatePort: uint16(containerPort),
|
|
Type: proto,
|
|
})
|
|
} else {
|
|
for _, b := range bindings {
|
|
hostPortInt, err := strconv.Atoi(b.HostPort)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid HostPort: %v", err)
|
|
}
|
|
addr := netip.Addr{}
|
|
if b.HostIP != "" {
|
|
addr, err = netip.ParseAddr(b.HostIP)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid HostIP: %v", err)
|
|
}
|
|
}
|
|
|
|
ports = append(ports, container.PortSummary{
|
|
IP: addr,
|
|
PrivatePort: uint16(containerPort),
|
|
PublicPort: uint16(hostPortInt),
|
|
Type: proto,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
n, err := json.Marshal(inspect.NetworkSettings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
networkSettings := container.NetworkSettingsSummary{}
|
|
if err := json.Unmarshal(n, &networkSettings); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
m, err := json.Marshal(inspect.Mounts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mounts := []container.MountPoint{}
|
|
if err := json.Unmarshal(m, &mounts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var healthSummary *container.HealthSummary
|
|
if includeHealth {
|
|
healthSummary = &container.HealthSummary{
|
|
Status: container.NoHealthcheck,
|
|
}
|
|
if l.HasHealthCheck() {
|
|
switch state {
|
|
case define.ContainerStateRunning, define.ContainerStatePaused,
|
|
define.ContainerStateConfigured, define.ContainerStateCreated,
|
|
define.ContainerStateStopping:
|
|
if inspect.State.Health != nil && inspect.State.Health.Status != "" {
|
|
healthSummary.Status = container.HealthStatus(inspect.State.Health.Status)
|
|
healthSummary.FailingStreak = inspect.State.Health.FailingStreak
|
|
} else {
|
|
healthSummary.Status = container.Starting
|
|
}
|
|
default:
|
|
healthSummary.Status = container.NoHealthcheck
|
|
}
|
|
}
|
|
}
|
|
|
|
hc := inspect.HostConfig
|
|
if hc != nil {
|
|
hostConfig.NetworkMode = inspect.HostConfig.NetworkMode
|
|
hostConfig.Annotations = inspect.HostConfig.Annotations
|
|
}
|
|
|
|
return &handlers.Container{
|
|
Summary: container.Summary{
|
|
ID: l.ID(),
|
|
Names: []string{fmt.Sprintf("/%s", l.Name())},
|
|
Image: imageName,
|
|
ImageID: "sha256:" + imageID,
|
|
Command: strings.Join(l.Command(), " "),
|
|
Created: l.CreatedTime().Unix(),
|
|
Ports: ports,
|
|
SizeRw: sizeRW,
|
|
SizeRootFs: sizeRootFs,
|
|
Labels: l.Labels(),
|
|
State: container.ContainerState(stateStr),
|
|
Status: status,
|
|
Health: healthSummary,
|
|
HostConfig: hostConfig,
|
|
NetworkSettings: &networkSettings,
|
|
Mounts: mounts,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
//nolint:staticcheck // LegacyImageInspect is deprecated but kept for Docker API compat < v1.52
|
|
func LibpodToContainerJSON(l *libpod.Container, sz bool) (*handlers.LegacyImageInspect, error) {
|
|
imageID, imageName := l.Image()
|
|
inspect, err := l.Inspect(sz)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Docker uses UTC
|
|
if inspect != nil && inspect.State != nil {
|
|
inspect.State.StartedAt = inspect.State.StartedAt.UTC()
|
|
inspect.State.FinishedAt = inspect.State.FinishedAt.UTC()
|
|
}
|
|
i, err := json.Marshal(inspect.State)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
state := container.State{}
|
|
if err := json.Unmarshal(i, &state); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// docker considers paused to be running
|
|
if state.Paused {
|
|
state.Running = true
|
|
}
|
|
|
|
// map our statuses to Docker's statuses
|
|
switch string(state.Status) {
|
|
case define.ContainerStateConfigured.String(), define.ContainerStateCreated.String():
|
|
state.Status = "created"
|
|
case define.ContainerStateRunning.String(), define.ContainerStateStopping.String():
|
|
state.Status = "running"
|
|
case define.ContainerStatePaused.String():
|
|
state.Status = "paused"
|
|
case define.ContainerStateRemoving.String():
|
|
state.Status = "removing"
|
|
case define.ContainerStateStopped.String(), define.ContainerStateExited.String():
|
|
state.Status = "exited"
|
|
default:
|
|
state.Status = "" // unknown state
|
|
}
|
|
|
|
if l.HasHealthCheck() {
|
|
state.Health = &container.Health{}
|
|
if inspect.State.Health != nil {
|
|
if inspect.State.Health.Status != "" {
|
|
state.Health.Status = container.HealthStatus(inspect.State.Health.Status)
|
|
} else if state.Status == "running" || state.Status == "created" {
|
|
state.Health.Status = container.Starting
|
|
}
|
|
state.Health.FailingStreak = inspect.State.Health.FailingStreak
|
|
log := inspect.State.Health.Log
|
|
|
|
for _, item := range log {
|
|
res := &container.HealthcheckResult{}
|
|
s, err := time.Parse(time.RFC3339Nano, item.Start)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
e, err := time.Parse(time.RFC3339Nano, item.End)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res.Start = s
|
|
res.End = e
|
|
res.ExitCode = item.ExitCode
|
|
res.Output = item.Output
|
|
state.Health.Log = append(state.Health.Log, res)
|
|
}
|
|
}
|
|
}
|
|
|
|
formatCapabilities(inspect.HostConfig.CapDrop)
|
|
formatCapabilities(inspect.HostConfig.CapAdd)
|
|
|
|
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
|
|
}
|
|
sort.Strings(hc.Binds)
|
|
|
|
// Map CgroupMode to CgroupnsMode for Docker API compatibility
|
|
switch inspect.HostConfig.CgroupMode {
|
|
case "private":
|
|
hc.CgroupnsMode = container.CgroupnsModePrivate
|
|
case "host":
|
|
hc.CgroupnsMode = container.CgroupnsModeHost
|
|
}
|
|
|
|
// k8s-file == json-file
|
|
if hc.LogConfig.Type == define.KubernetesLogging {
|
|
hc.LogConfig.Type = define.JSONLogging
|
|
}
|
|
|
|
graphDriver := storage.DriverData{
|
|
Name: inspect.GraphDriver.Name,
|
|
Data: inspect.GraphDriver.Data,
|
|
}
|
|
|
|
cb := handlers.LegacyImageInspect{ //nolint:staticcheck // LegacyImageInspect is deprecated but kept for Docker API compat < v1.52
|
|
InspectResponse: container.InspectResponse{
|
|
ID: l.ID(),
|
|
Created: l.CreatedTime().UTC().Format(time.RFC3339Nano), // Docker uses UTC
|
|
Path: inspect.Path,
|
|
Args: inspect.Args,
|
|
State: &state,
|
|
Image: "sha256:" + imageID,
|
|
ResolvConfPath: inspect.ResolvConfPath,
|
|
HostnamePath: inspect.HostnamePath,
|
|
HostsPath: inspect.HostsPath,
|
|
LogPath: l.LogPath(),
|
|
Name: fmt.Sprintf("/%s", l.Name()),
|
|
RestartCount: int(inspect.RestartCount),
|
|
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,
|
|
},
|
|
}
|
|
|
|
// set Path and Args
|
|
processArgs := l.Config().Spec.Process.Args
|
|
if len(processArgs) > 0 {
|
|
cb.Path = processArgs[0]
|
|
}
|
|
if len(processArgs) > 1 {
|
|
cb.Args = processArgs[1:]
|
|
}
|
|
stopTimeout := int(l.StopTimeout())
|
|
|
|
var healthcheck *container.HealthConfig
|
|
if inspect.Config.Healthcheck != nil {
|
|
healthcheck = &container.HealthConfig{
|
|
Test: inspect.Config.Healthcheck.Test,
|
|
Interval: inspect.Config.Healthcheck.Interval,
|
|
Timeout: inspect.Config.Healthcheck.Timeout,
|
|
StartPeriod: inspect.Config.Healthcheck.StartPeriod,
|
|
Retries: inspect.Config.Healthcheck.Retries,
|
|
}
|
|
}
|
|
|
|
// Convert to moby PortSet.
|
|
var exposedPorts network.PortSet
|
|
if len(inspect.Config.ExposedPorts) > 0 {
|
|
exposedPorts = make(network.PortSet, len(inspect.Config.ExposedPorts))
|
|
for p := range inspect.Config.ExposedPorts {
|
|
mp, err := network.ParsePort(p)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid exposed port %q: %w", p, err)
|
|
}
|
|
exposedPorts[mp] = struct{}{}
|
|
}
|
|
}
|
|
|
|
config := handlers.ContainerConfig{
|
|
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: exposedPorts,
|
|
Tty: inspect.Config.Tty,
|
|
OpenStdin: inspect.Config.OpenStdin,
|
|
StdinOnce: inspect.Config.StdinOnce,
|
|
Env: inspect.Config.Env,
|
|
Cmd: l.Command(),
|
|
Healthcheck: healthcheck,
|
|
ArgsEscaped: false,
|
|
Image: imageName,
|
|
Volumes: nil,
|
|
WorkingDir: l.WorkingDir(),
|
|
Entrypoint: l.Entrypoint(),
|
|
NetworkDisabled: false,
|
|
OnBuild: nil,
|
|
Labels: l.Labels(),
|
|
StopSignal: strconv.Itoa(int(l.StopSignal())),
|
|
StopTimeout: &stopTimeout,
|
|
Shell: nil,
|
|
},
|
|
MacAddress: "",
|
|
}
|
|
|
|
m, err := json.Marshal(inspect.Mounts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mounts := []container.MountPoint{}
|
|
if err := json.Unmarshal(m, &mounts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p, err := json.Marshal(inspect.NetworkSettings.Ports)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ports := nat.PortMap{}
|
|
if err := json.Unmarshal(p, &ports); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
n, err := json.Marshal(inspect.NetworkSettings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
networkSettings := handlers.LegacyNetworkSettings{} //nolint:staticcheck // LegacyNetworkSettings is deprecated but kept for Docker API compat < v1.52
|
|
if err := json.Unmarshal(n, &networkSettings); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
convertSecondaryIPPrefixLen(inspect.NetworkSettings, &networkSettings)
|
|
|
|
// do not report null instead use an empty map
|
|
if networkSettings.Networks == nil {
|
|
networkSettings.Networks = map[string]*network.EndpointSettings{}
|
|
}
|
|
|
|
cb.Mounts = mounts
|
|
cb.Config = &config
|
|
cb.NetworkSettings = &networkSettings
|
|
return &cb, nil
|
|
}
|
|
|
|
func formatCapabilities(slice []string) {
|
|
for i := range slice {
|
|
slice[i] = strings.TrimPrefix(slice[i], "CAP_")
|
|
}
|
|
}
|
|
|
|
func RenameContainer(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
decoder := utils.GetDecoder(r)
|
|
|
|
name := utils.GetName(r)
|
|
query := struct {
|
|
Name string `schema:"name"`
|
|
}{}
|
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
|
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
|
|
return
|
|
}
|
|
|
|
ctr, err := runtime.LookupContainer(name)
|
|
if err != nil {
|
|
utils.ContainerNotFound(w, name, err)
|
|
return
|
|
}
|
|
|
|
if _, err := runtime.RenameContainer(r.Context(), ctr, query.Name); err != nil {
|
|
if errors.Is(err, define.ErrPodExists) || errors.Is(err, define.ErrCtrExists) {
|
|
utils.Error(w, http.StatusConflict, err)
|
|
return
|
|
}
|
|
utils.InternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
utils.WriteResponse(w, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func UpdateContainer(w http.ResponseWriter, r *http.Request) {
|
|
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
|
name := utils.GetName(r)
|
|
|
|
ctr, err := runtime.LookupContainer(name)
|
|
if err != nil {
|
|
utils.ContainerNotFound(w, name, err)
|
|
return
|
|
}
|
|
|
|
options := new(container.UpdateConfig)
|
|
if err := utils.ReadJSONFromBody(r, &options); err != nil {
|
|
utils.Error(w, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
|
|
// Only handle the bits of update that Docker uses as examples.
|
|
// For example, the update API claims to be able to update devices for
|
|
// existing containers... Which I am very dubious about.
|
|
// Ignore bits like that unless someone asks us for them.
|
|
|
|
// We're going to be editing this, so we have to deep-copy to not affect
|
|
// the container's own resources
|
|
resources := new(spec.LinuxResources)
|
|
oldResources := ctr.LinuxResources()
|
|
if oldResources != nil {
|
|
if err := libpod.JSONDeepCopy(oldResources, resources); err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("copying old resource limits: %w", err))
|
|
return
|
|
}
|
|
}
|
|
|
|
// CPU limits
|
|
cpu := resources.CPU
|
|
if cpu == nil {
|
|
cpu = new(spec.LinuxCPU)
|
|
}
|
|
useCPU := false
|
|
if options.CPUShares != 0 {
|
|
shares := uint64(options.CPUShares)
|
|
cpu.Shares = &shares
|
|
useCPU = true
|
|
}
|
|
if options.CPUPeriod != 0 {
|
|
period := uint64(options.CPUPeriod)
|
|
cpu.Period = &period
|
|
useCPU = true
|
|
}
|
|
if options.CPUQuota != 0 {
|
|
cpu.Quota = &options.CPUQuota
|
|
useCPU = true
|
|
}
|
|
if options.CPURealtimeRuntime != 0 {
|
|
cpu.RealtimeRuntime = &options.CPURealtimeRuntime
|
|
useCPU = true
|
|
}
|
|
if options.CPURealtimePeriod != 0 {
|
|
period := uint64(options.CPURealtimePeriod)
|
|
cpu.RealtimePeriod = &period
|
|
useCPU = true
|
|
}
|
|
if options.CpusetCpus != "" {
|
|
cpu.Cpus = options.CpusetCpus
|
|
useCPU = true
|
|
}
|
|
if options.CpusetMems != "" {
|
|
cpu.Mems = options.CpusetMems
|
|
useCPU = true
|
|
}
|
|
if useCPU {
|
|
resources.CPU = cpu
|
|
}
|
|
|
|
// Memory limits
|
|
mem := resources.Memory
|
|
if mem == nil {
|
|
mem = new(spec.LinuxMemory)
|
|
}
|
|
useMem := false
|
|
if options.Memory != 0 {
|
|
mem.Limit = &options.Memory
|
|
useMem = true
|
|
}
|
|
if options.MemorySwap != 0 {
|
|
mem.Swap = &options.MemorySwap
|
|
useMem = true
|
|
}
|
|
if options.MemoryReservation != 0 {
|
|
mem.Reservation = &options.MemoryReservation
|
|
useMem = true
|
|
}
|
|
if useMem {
|
|
resources.Memory = mem
|
|
}
|
|
|
|
// PIDs limit
|
|
if options.PidsLimit != nil {
|
|
if resources.Pids == nil {
|
|
resources.Pids = new(spec.LinuxPids)
|
|
}
|
|
resources.Pids.Limit = options.PidsLimit
|
|
}
|
|
|
|
// Blkio Weight
|
|
if options.BlkioWeight != 0 {
|
|
if resources.BlockIO == nil {
|
|
resources.BlockIO = new(spec.LinuxBlockIO)
|
|
}
|
|
resources.BlockIO.Weight = &options.BlkioWeight
|
|
}
|
|
|
|
// Restart policy
|
|
localPolicy := string(options.RestartPolicy.Name)
|
|
restartPolicy := &localPolicy
|
|
|
|
var restartRetries *uint
|
|
if options.RestartPolicy.MaximumRetryCount != 0 {
|
|
localRetries := uint(options.RestartPolicy.MaximumRetryCount)
|
|
restartRetries = &localRetries
|
|
}
|
|
|
|
// Rlimits
|
|
var rlimits []spec.POSIXRlimit
|
|
if len(options.Ulimits) > 0 {
|
|
ulimits := make([]string, len(options.Ulimits))
|
|
for i, u := range options.Ulimits {
|
|
ulimits[i] = u.String()
|
|
}
|
|
rlimits, err = specgenutil.GenRlimits(ulimits)
|
|
if err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("generating rlimits: %w", err))
|
|
return
|
|
}
|
|
}
|
|
|
|
updateOptions := &entities.ContainerUpdateOptions{
|
|
Resources: resources,
|
|
ChangedHealthCheckConfiguration: &define.UpdateHealthCheckConfig{},
|
|
RestartPolicy: restartPolicy,
|
|
RestartRetries: restartRetries,
|
|
Rlimits: rlimits,
|
|
}
|
|
|
|
if err := ctr.Update(updateOptions); err != nil {
|
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("updating container: %w", err))
|
|
return
|
|
}
|
|
|
|
responseStruct := container.UpdateResponse{}
|
|
utils.WriteResponse(w, http.StatusOK, responseStruct)
|
|
}
|