File: containers_create.go

package info (click to toggle)
podman 5.6.2%2Bds1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 23,304 kB
  • sloc: sh: 4,493; python: 2,676; perl: 1,885; ansic: 1,484; makefile: 988; ruby: 42; csh: 8
file content (173 lines) | stat: -rw-r--r-- 5,561 bytes parent folder | download | duplicates (2)
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
}