Skip to content

Commit 9fda9cd

Browse files
authored
chore: add api options to container create (runfinch#122)
Signed-off-by: Arjun Raja Yogidas <[email protected]>
1 parent 606c16b commit 9fda9cd

File tree

4 files changed

+429
-32
lines changed

4 files changed

+429
-32
lines changed

api/handlers/container/create.go

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,22 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
107107
}
108108
}
109109

110+
// Annotations: TODO - available in nerdctl 2.0
111+
// Annotations are passed in as a map of strings,
112+
// but nerdctl expects an array of strings with format [annotations1=VALUE1, annotations2=VALUE2, ...].
113+
// annotations := []string{}
114+
// if req.HostConfig.Annotations != nil {
115+
// for key, val := range req.HostConfig.Annotations {
116+
// annotations = append(annotations, fmt.Sprintf("%s=%s", key, val))
117+
// }
118+
// }
119+
120+
ulimits := []string{}
121+
if req.HostConfig.Ulimits != nil {
122+
for _, ulimit := range req.HostConfig.Ulimits {
123+
ulimits = append(ulimits, ulimit.String())
124+
}
125+
}
110126
// Environment vars:
111127
env := []string{}
112128
if req.Env != nil {
@@ -119,6 +135,36 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
119135
capAdd = req.HostConfig.CapAdd
120136
}
121137

138+
capDrop := []string{}
139+
if req.HostConfig.CapDrop != nil {
140+
capDrop = req.HostConfig.CapDrop
141+
}
142+
143+
memoryReservation := ""
144+
if req.HostConfig.MemoryReservation != 0 {
145+
memoryReservation = fmt.Sprint(req.HostConfig.MemoryReservation)
146+
}
147+
148+
memorySwap := ""
149+
if req.HostConfig.MemorySwap != 0 {
150+
memorySwap = fmt.Sprint(req.HostConfig.MemorySwap)
151+
}
152+
153+
memorySwappiness := int64(-1)
154+
if req.HostConfig.MemorySwappiness > 0 {
155+
memorySwappiness = req.HostConfig.MemorySwappiness
156+
}
157+
158+
pidLimit := int64(-1)
159+
if req.HostConfig.PidsLimit > 0 {
160+
pidLimit = req.HostConfig.PidsLimit
161+
}
162+
163+
CpuQuota := int64(-1)
164+
if req.HostConfig.CPUQuota != 0 {
165+
CpuQuota = req.HostConfig.CPUQuota
166+
}
167+
122168
globalOpt := ncTypes.GlobalCommandOptions(*h.Config)
123169
createOpt := ncTypes.ContainerCreateOptions{
124170
Stdout: nil,
@@ -134,6 +180,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
134180
Pull: "missing", // nerdctl default.
135181
StopSignal: stopSignal,
136182
StopTimeout: stopTimeout,
183+
CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file
137184
// #endregion
138185

139186
// #region for platform flags
@@ -147,23 +194,26 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
147194
// #region for resource flags
148195
CPUShares: uint64(req.HostConfig.CPUShares), // CPU shares (relative weight)
149196
Memory: memory, // memory limit (in bytes)
150-
CPUQuota: -1, // nerdctl default.
151-
MemorySwappiness64: -1, // nerdctl default.
152-
PidsLimit: -1, // nerdctl default.
197+
CPUQuota: CpuQuota, // CPUQuota limits the CPU CFS (Completely Fair Scheduler) quota
198+
MemorySwappiness64: memorySwappiness, // Tuning container memory swappiness behaviour
199+
PidsLimit: pidLimit, // PidsLimit specifies the tune container pids limit
153200
Cgroupns: defaults.CgroupnsMode(), // nerdctl default.
201+
MemoryReservation: memoryReservation, // Memory soft limit (in bytes)
202+
MemorySwap: memorySwap, // Total memory usage (memory + swap); set `-1` to enable unlimited swap
203+
Ulimit: ulimits, // List of ulimits to be set in the container
204+
CPUPeriod: uint64(req.HostConfig.CPUPeriod),
154205
// #endregion
155206

156207
// #region for user flags
157-
User: req.User,
158-
GroupAdd: []string{}, // nerdctl default.
208+
User: req.User,
159209
// #endregion
160210

161211
// #region for security flags
162212
SecurityOpt: []string{}, // nerdctl default.
163213
CapAdd: capAdd,
164-
CapDrop: []string{}, // nerdctl default.
214+
CapDrop: capDrop,
215+
Privileged: req.HostConfig.Privileged,
165216
// #endregion
166-
167217
// #region for runtime flags
168218
Runtime: defaults.Runtime, // nerdctl default.
169219
// #endregion

api/handlers/container/create_test.go

Lines changed: 169 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,172 @@ var _ = Describe("Container Create API ", func() {
357357
Expect(rr.Body).Should(MatchJSON(jsonResponse))
358358
})
359359

360+
It("should set CPUPeriod create options for resources", func() {
361+
body := []byte(`{
362+
"Image": "test-image",
363+
"HostConfig": {
364+
"CpuPeriod": 100000
365+
}
366+
}`)
367+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
368+
369+
// expected create options
370+
createOpt.CPUPeriod = 100000
371+
372+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
373+
cid, nil)
374+
375+
// handler should return success message with 201 status code.
376+
h.create(rr, req)
377+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
378+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
379+
})
380+
381+
It("should set CpuQuota create options for resources", func() {
382+
body := []byte(`{
383+
"Image": "test-image",
384+
"HostConfig": {
385+
"CpuQuota": 50000
386+
}
387+
}`)
388+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
389+
390+
// expected create options
391+
createOpt.CPUQuota = 50000
392+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
393+
cid, nil)
394+
395+
// handler should return success message with 201 status code.
396+
h.create(rr, req)
397+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
398+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
399+
})
400+
401+
It("should set CpuQuota to -1 by default", func() {
402+
body := []byte(`{
403+
"Image": "test-image"
404+
}`)
405+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
406+
407+
// expected create options
408+
createOpt.CPUQuota = -1
409+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
410+
cid, nil)
411+
412+
// handler should return success message with 201 status code.
413+
h.create(rr, req)
414+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
415+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
416+
})
417+
418+
It("should set MemoryReservation, MemorySwap and MemorySwappiness create options for resources", func() {
419+
body := []byte(`{
420+
"Image": "test-image",
421+
"HostConfig": {
422+
"MemoryReservation": 209710,
423+
"MemorySwap": 514288000,
424+
"MemorySwappiness": 25
425+
}
426+
}`)
427+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
428+
429+
// expected create options
430+
createOpt.MemoryReservation = "209710"
431+
createOpt.MemorySwap = "514288000"
432+
createOpt.MemorySwappiness64 = 25
433+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
434+
cid, nil)
435+
436+
// handler should return success message with 201 status code.
437+
h.create(rr, req)
438+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
439+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
440+
})
441+
442+
It("should set CapDrop option", func() {
443+
body := []byte(`{
444+
"Image": "test-image",
445+
"HostConfig": {
446+
"CapDrop": ["MKNOD"]
447+
}
448+
}`)
449+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
450+
451+
// expected create options
452+
createOpt.CapDrop = []string{"MKNOD"}
453+
454+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
455+
cid, nil)
456+
457+
// handler should return success message with 201 status code.
458+
h.create(rr, req)
459+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
460+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
461+
})
462+
463+
It("should set Privileged option", func() {
464+
body := []byte(`{
465+
"Image": "test-image",
466+
"HostConfig": {
467+
"Privileged": true
468+
}
469+
}`)
470+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
471+
472+
// expected create options
473+
createOpt.Privileged = true
474+
475+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
476+
cid, nil)
477+
478+
// handler should return success message with 201 status code.
479+
h.create(rr, req)
480+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
481+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
482+
})
483+
484+
It("should set Ulimit option", func() {
485+
body := []byte(`{
486+
"Image": "test-image",
487+
"HostConfig": {
488+
"Ulimits": [{"Name": "nofile", "Soft": 1024, "Hard": 2048},{"Name": "nproc", "Soft": 1024, "Hard": 4048}]
489+
}
490+
}`)
491+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
492+
493+
// expected create options
494+
createOpt.Ulimit = []string{"nofile=1024:2048", "nproc=1024:4048"}
495+
496+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
497+
cid, nil)
498+
499+
// handler should return success message with 201 status code.
500+
h.create(rr, req)
501+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
502+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
503+
})
504+
505+
It("should set PidLimit option", func() {
506+
body := []byte(`{
507+
"Image": "test-image",
508+
"HostConfig": {
509+
"PidsLimit": 200
510+
}
511+
}`)
512+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
513+
514+
// expected create options
515+
createOpt.PidsLimit = 200
516+
517+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
518+
cid, nil)
519+
520+
// handler should return success message with 201 status code.
521+
h.create(rr, req)
522+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
523+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
524+
})
525+
360526
It("should return 404 if the image was not found", func() {
361527
body := []byte(`{"Image": "test-image"}`)
362528
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
@@ -542,17 +708,18 @@ func getDefaultCreateOpt(conf config.Config) types.ContainerCreateOptions {
542708
MemorySwappiness64: -1, // nerdctl default.
543709
PidsLimit: -1, // nerdctl default.
544710
Cgroupns: defaults.CgroupnsMode(), // nerdctl default.
711+
Ulimit: []string{},
545712
// #endregion
546713

547714
// #region for user flags
548-
User: "",
549-
GroupAdd: []string{}, // nerdctl default.
715+
User: "",
550716
// #endregion
551717

552718
// #region for security flags
553719
SecurityOpt: []string{}, // nerdctl default.
554720
CapAdd: []string{}, // nerdctl default.
555721
CapDrop: []string{}, // nerdctl default.
722+
Privileged: false,
556723
// #endregion
557724

558725
// #region for runtime flags

api/types/container_types.go

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/containerd/nerdctl/pkg/inspecttypes/dockercompat"
1212
dockertypes "github.com/docker/docker/api/types"
1313
"github.com/docker/go-connections/nat"
14+
"github.com/docker/go-units"
1415
)
1516

1617
// AttachOptions defines the available options for the container attach call.
@@ -57,52 +58,64 @@ type ContainerConfig struct {
5758
// HostConfig is from https://github.com/moby/moby/blob/v24.0.2/api/types/container/hostconfig.go#L376-L436
5859
type ContainerHostConfig struct {
5960
// Applicable to all platforms
60-
Binds []string // List of volume bindings for this container
61-
// TODO: ContainerIDFile string // File (path) where the containerId is written
62-
LogConfig LogConfig // Configuration of the logs for this container
63-
NetworkMode string // Network mode to use for the container
64-
PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host
65-
RestartPolicy RestartPolicy // Restart policy to be used for the container
66-
AutoRemove bool // Automatically remove container when it exits
61+
Binds []string // List of volume bindings for this container
62+
ContainerIDFile string // File (path) where the containerId is written
63+
LogConfig LogConfig // Configuration of the logs for this container
64+
NetworkMode string // Network mode to use for the container
65+
PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host
66+
RestartPolicy RestartPolicy // Restart policy to be used for the container
67+
AutoRemove bool // Automatically remove container when it exits
6768
// TODO: VolumeDriver string // Name of the volume driver used to mount volumes
68-
// TODO: VolumesFrom []string // List of volumes to take from other container
69+
// TODO: VolumesFrom []string // List of volumes to take from other container
6970
// TODO: ConsoleSize [2]uint // Initial console size (height,width)
7071
// TODO: Annotations map[string]string `json:",omitempty"` // Arbitrary non-identifying metadata attached to container and provided to the runtime
7172

7273
// Applicable to UNIX platforms
73-
CapAdd []string // List of kernel capabilities to add to the container
74-
// TODO: CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container
75-
// TODO: CgroupnsMode CgroupnsMode // Cgroup namespace mode to use for the container
74+
CapAdd []string // List of kernel capabilities to add to the container
75+
CapDrop []string // List of kernel capabilities to remove from the container
76+
// TODO: CgroupnsMode CgroupnsMode // Cgroup namespace mode to use for the container
7677
DNS []string `json:"Dns"` // List of DNS server to lookup
7778
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
7879
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
7980
ExtraHosts []string // List of extra hosts
80-
// TODO: GroupAdd []string // List of additional groups that the container process will run as
81-
// TODO: IpcMode IpcMode // IPC namespace to use for the container
81+
// TODO: GroupAdd []string // List of additional groups that the container process will run as
82+
// TODO: IpcMode IpcMode // IPC namespace to use for the container
8283
// TODO: Cgroup CgroupSpec // Cgroup to use for the container
8384
// TODO: Links []string // List of links (in the name:alias form)
84-
// TODO: OomScoreAdj int // Container preference for OOM-killing
85-
// TODO: PidMode PidMode // PID namespace to use for the container
86-
// TODO: Privileged bool // Is the container in privileged mode
85+
// TODO: OomKillDisable bool // specifies whether to disable OOM Killer
86+
// TODO: OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000)
87+
// TODO: PidMode string // PID namespace to use for the container
88+
Privileged bool // Is the container in privileged mode
89+
// TODO: ReadonlyRootfs bool // Is the container root filesystem in read-only
90+
// TODO: SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. (["key=value"])
91+
// TODO: Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container
92+
// TODO: UTSMode string // UTS namespace to use for the container
93+
// TODO: ShmSize int64 // Size of /dev/shm in bytes. The size must be greater than 0.
94+
// TODO: Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container
95+
// TODO: Runtime string `json:",omitempty"` // Runtime to use with this container
8796
// TODO: PublishAllPorts bool // Should docker publish all exposed port for the container
88-
// TODO: ReadonlyRootfs bool // Is the container root filesystem in read-only
89-
// TODO: SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux.
9097
// TODO: StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container.
91-
// TODO: Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container
92-
// TODO: UTSMode UTSMode // UTS namespace to use for the container
9398
// TODO: UsernsMode UsernsMode // The user namespace to use for the container
94-
// TODO: ShmSize int64 // Total shm memory usage
95-
// TODO: Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container
96-
// TODO: Runtime string `json:",omitempty"` // Runtime to use with this container
9799

98100
// Applicable to Windows
99101
// TODO: Isolation Isolation // Isolation technology of the container (e.g. default, hyperv)
100102

101103
// Contains container's resources (cgroups, ulimits)
102104
CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers)
103105
Memory int64 // Memory limit (in bytes)
106+
CPUPeriod int64 `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period
107+
CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota
108+
// TODO: CPUSetCPUs string `json:"CpusetCpus"` // CPUSetCPUs specifies the CPUs in which to allow execution (0-3, 0,1)
109+
// TODO: CPUSetMems string `json:"CpusetMems"` // CPUSetMems specifies the memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
110+
MemoryReservation int64 // MemoryReservation specifies the memory soft limit (in bytes)
111+
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap
112+
MemorySwappiness int64 // MemorySwappiness64 specifies the tune container memory swappiness (0 to 100) (default -1)
104113
// TODO: Resources
105114

115+
Ulimits []*Ulimit // List of ulimits to be set in the container
116+
// TODO: BlkioWeight uint16 // Block IO weight (relative weight vs. other containers)
117+
// TODO: Devices []DeviceMapping // List of devices to map inside the container
118+
PidsLimit int64 // Setting PIDs limit for a container; Set `0` or `-1` for unlimited, or `null` to not change.
106119
// Mounts specs used by the container
107120
// TODO: Mounts []mount.Mount `json:",omitempty"`
108121

@@ -249,3 +262,5 @@ type StatsJSON struct {
249262
// Networks request version >=1.21
250263
Networks map[string]dockertypes.NetworkStats `json:"networks,omitempty"`
251264
}
265+
266+
type Ulimit = units.Ulimit

0 commit comments

Comments
 (0)