1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
//go:build !remote
package libpod
import (
"encoding/json"
"errors"
"fmt"
"math"
"net/http"
"strconv"
"github.com/containers/podman/v5/libpod"
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/pkg/api/handlers/utils"
api "github.com/containers/podman/v5/pkg/api/types"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/containers/podman/v5/pkg/specgen"
"github.com/containers/podman/v5/pkg/specgen/generate"
"github.com/containers/podman/v5/pkg/specgenutil"
"github.com/containers/storage"
"github.com/opencontainers/runtime-spec/specs-go"
)
// The JSON decoder correctly cannot decode (overflow) negative values (e.g., `-1`) for fields of type `uint64`,
// as `-1` is used to represent `max` in `POSIXRlimit`. To address this, we use `tmpSpecGenerator` to decode the request body.
// The `tmpSpecGenerator` overrides the `POSIXRlimit` type with a `tmpRlimit` type that uses the `json.Number` type for decoding values.
// The `tmpRlimit` is then parsed into the `POSIXRlimit` type and assigned to the `SpecGenerator`.
// This serves as a workaround for the issue (https://github.com/containers/podman/issues/24886).
type tmpSpecGenerator struct {
specgen.SpecGenerator
Rlimits []tmpRlimit `json:"r_limits,omitempty"`
}
type tmpRlimit struct {
// Type of the rlimit to set
Type string `json:"type"`
// Hard is the hard limit for the specified type
Hard json.Number `json:"hard"`
// Soft is the soft limit for the specified type
Soft json.Number `json:"soft"`
}
// CreateContainer takes a specgenerator and makes a container. It returns
// the new container ID on success along with any warnings.
func CreateContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
conf, err := runtime.GetConfigNoCopy()
if err != nil {
utils.InternalServerError(w, err)
return
}
// copy vars here and not leak config pointers into specgen
noHosts := conf.Containers.NoHosts
privileged := conf.Containers.Privileged
// we have to set the default before we decode to make sure the correct default is set when the field is unset
tmpSg := tmpSpecGenerator{
SpecGenerator: specgen.SpecGenerator{
ContainerNetworkConfig: specgen.ContainerNetworkConfig{
UseImageHosts: &noHosts,
},
ContainerSecurityConfig: specgen.ContainerSecurityConfig{
Umask: conf.Containers.Umask,
Privileged: &privileged,
},
ContainerHealthCheckConfig: specgen.ContainerHealthCheckConfig{
HealthLogDestination: define.DefaultHealthCheckLocalDestination,
HealthMaxLogCount: define.DefaultHealthMaxLogCount,
HealthMaxLogSize: define.DefaultHealthMaxLogSize,
},
},
}
if err := json.NewDecoder(r.Body).Decode(&tmpSg); err != nil {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decode(): %w", err))
return
}
sg := tmpSg.SpecGenerator
rLimits, err := parseRLimits(tmpSg.Rlimits)
if err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("invalid rlimit: %w", err))
return
}
sg.Rlimits = rLimits
if sg.Passwd == nil {
t := true
sg.Passwd = &t
}
// need to check for memory limit to adjust swap
if sg.ResourceLimits != nil && sg.ResourceLimits.Memory != nil {
s := ""
var l int64
if sg.ResourceLimits.Memory.Swap != nil {
s = strconv.Itoa(int(*sg.ResourceLimits.Memory.Swap))
}
if sg.ResourceLimits.Memory.Limit != nil {
l = *sg.ResourceLimits.Memory.Limit
}
specgenutil.LimitToSwap(sg.ResourceLimits.Memory, s, l)
}
warn, err := generate.CompleteSpec(r.Context(), runtime, &sg)
if err != nil {
if errors.Is(err, storage.ErrImageUnknown) {
utils.Error(w, http.StatusNotFound, fmt.Errorf("no such image: %w", err))
return
}
utils.InternalServerError(w, err)
return
}
rtSpec, spec, opts, err := generate.MakeContainer(r.Context(), runtime, &sg, false, nil)
if err != nil {
if errors.Is(err, storage.ErrImageUnknown) {
utils.Error(w, http.StatusNotFound, fmt.Errorf("no such image: %w", err))
return
}
utils.InternalServerError(w, err)
return
}
ctr, err := generate.ExecuteCreate(r.Context(), runtime, rtSpec, spec, false, opts...)
if err != nil {
utils.InternalServerError(w, err)
return
}
response := entities.ContainerCreateResponse{ID: ctr.ID(), Warnings: warn}
utils.WriteJSON(w, http.StatusCreated, response)
}
// parseRLimits parses slice of tmpLimit to slice of specs.POSIXRlimit.
func parseRLimits(rLimits []tmpRlimit) ([]specs.POSIXRlimit, error) {
rl := []specs.POSIXRlimit{}
// The "soft" and "hard" values are expected to be of type uint64.
// The JSON decoder cannot cast -1 as to uint64.
// We need to convert them to uint64, and handle the special case of -1
// which indicates an max value.
parseLimitNumber := func(limit json.Number) (uint64, error) {
limitString := limit.String()
if limitString == "-1" {
// uint64(-1) overflow to max uint64 value
return math.MaxUint64, nil
}
return strconv.ParseUint(limitString, 10, 64)
}
for _, rLimit := range rLimits {
soft, err := parseLimitNumber(rLimit.Soft)
if err != nil {
return nil, fmt.Errorf("invalid value for POSIXRlimit.soft: %w", err)
}
hard, err := parseLimitNumber(rLimit.Hard)
if err != nil {
return nil, fmt.Errorf("invalid value for POSIXRlimit.hard: %w", err)
}
if rLimit.Type == "" {
return nil, fmt.Errorf("invalid value for POSIXRlimit.type: %w", err)
}
rl = append(rl, specs.POSIXRlimit{
Type: rLimit.Type,
Soft: soft,
Hard: hard,
})
}
return rl, nil
}
|