Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add api options to container create #122

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 57 additions & 7 deletions api/handlers/container/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
}
}

// Annotations: TODO - available in nerdctl 2.0
// Annotations are passed in as a map of strings,
// but nerdctl expects an array of strings with format [annotations1=VALUE1, annotations2=VALUE2, ...].
// annotations := []string{}
// if req.HostConfig.Annotations != nil {
// for key, val := range req.HostConfig.Annotations {
// annotations = append(annotations, fmt.Sprintf("%s=%s", key, val))
// }
// }

ulimits := []string{}
if req.HostConfig.Ulimits != nil {
for _, ulimit := range req.HostConfig.Ulimits {
ulimits = append(ulimits, ulimit.String())
}
}
// Environment vars:
env := []string{}
if req.Env != nil {
Expand All @@ -119,6 +135,36 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
capAdd = req.HostConfig.CapAdd
}

capDrop := []string{}
if req.HostConfig.CapDrop != nil {
capDrop = req.HostConfig.CapDrop
}

memoryReservation := ""
if req.HostConfig.MemoryReservation != 0 {
memoryReservation = fmt.Sprint(req.HostConfig.MemoryReservation)
}

memorySwap := ""
if req.HostConfig.MemorySwap != 0 {
memorySwap = fmt.Sprint(req.HostConfig.MemorySwap)
}

memorySwappiness := int64(-1)
if req.HostConfig.MemorySwappiness > 0 {
memorySwappiness = req.HostConfig.MemorySwappiness
}

pidLimit := int64(-1)
if req.HostConfig.PidsLimit > 0 {
pidLimit = req.HostConfig.PidsLimit
}

CpuQuota := int64(-1)
if req.HostConfig.CPUQuota != 0 {
CpuQuota = req.HostConfig.CPUQuota
}

globalOpt := ncTypes.GlobalCommandOptions(*h.Config)
createOpt := ncTypes.ContainerCreateOptions{
Stdout: nil,
Expand All @@ -134,6 +180,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
Pull: "missing", // nerdctl default.
StopSignal: stopSignal,
StopTimeout: stopTimeout,
CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file
// #endregion

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

// #region for user flags
User: req.User,
GroupAdd: []string{}, // nerdctl default.
User: req.User,
// #endregion

// #region for security flags
SecurityOpt: []string{}, // nerdctl default.
CapAdd: capAdd,
CapDrop: []string{}, // nerdctl default.
CapDrop: capDrop,
Privileged: req.HostConfig.Privileged,
// #endregion

// #region for runtime flags
Runtime: defaults.Runtime, // nerdctl default.
// #endregion
Expand Down
171 changes: 169 additions & 2 deletions api/handlers/container/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,172 @@ var _ = Describe("Container Create API ", func() {
Expect(rr.Body).Should(MatchJSON(jsonResponse))
})

It("should set CPUPeriod create options for resources", func() {
body := []byte(`{
"Image": "test-image",
"HostConfig": {
"CpuPeriod": 100000
}
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
createOpt.CPUPeriod = 100000

service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
cid, nil)

// handler should return success message with 201 status code.
h.create(rr, req)
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
Expect(rr.Body).Should(MatchJSON(jsonResponse))
})

It("should set CpuQuota create options for resources", func() {
body := []byte(`{
"Image": "test-image",
"HostConfig": {
"CpuQuota": 50000
}
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
createOpt.CPUQuota = 50000
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
cid, nil)

// handler should return success message with 201 status code.
h.create(rr, req)
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
Expect(rr.Body).Should(MatchJSON(jsonResponse))
})

It("should set CpuQuota to -1 by default", func() {
body := []byte(`{
"Image": "test-image"
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
createOpt.CPUQuota = -1
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
cid, nil)

// handler should return success message with 201 status code.
h.create(rr, req)
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
Expect(rr.Body).Should(MatchJSON(jsonResponse))
})

It("should set MemoryReservation, MemorySwap and MemorySwappiness create options for resources", func() {
body := []byte(`{
"Image": "test-image",
"HostConfig": {
"MemoryReservation": 209710,
"MemorySwap": 514288000,
"MemorySwappiness": 25
}
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
createOpt.MemoryReservation = "209710"
createOpt.MemorySwap = "514288000"
createOpt.MemorySwappiness64 = 25
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
cid, nil)

// handler should return success message with 201 status code.
h.create(rr, req)
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
Expect(rr.Body).Should(MatchJSON(jsonResponse))
})

It("should set CapDrop option", func() {
body := []byte(`{
"Image": "test-image",
"HostConfig": {
"CapDrop": ["MKNOD"]
}
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
createOpt.CapDrop = []string{"MKNOD"}

service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
cid, nil)

// handler should return success message with 201 status code.
h.create(rr, req)
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
Expect(rr.Body).Should(MatchJSON(jsonResponse))
})

It("should set Privileged option", func() {
body := []byte(`{
"Image": "test-image",
"HostConfig": {
"Privileged": true
}
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
createOpt.Privileged = true

service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
cid, nil)

// handler should return success message with 201 status code.
h.create(rr, req)
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
Expect(rr.Body).Should(MatchJSON(jsonResponse))
})

It("should set Ulimit option", func() {
body := []byte(`{
"Image": "test-image",
"HostConfig": {
"Ulimits": [{"Name": "nofile", "Soft": 1024, "Hard": 2048},{"Name": "nproc", "Soft": 1024, "Hard": 4048}]
}
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
createOpt.Ulimit = []string{"nofile=1024:2048", "nproc=1024:4048"}

service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
cid, nil)

// handler should return success message with 201 status code.
h.create(rr, req)
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
Expect(rr.Body).Should(MatchJSON(jsonResponse))
})

It("should set PidLimit option", func() {
body := []byte(`{
"Image": "test-image",
"HostConfig": {
"PidsLimit": 200
}
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
createOpt.PidsLimit = 200

service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
cid, nil)

// handler should return success message with 201 status code.
h.create(rr, req)
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
Expect(rr.Body).Should(MatchJSON(jsonResponse))
})

It("should return 404 if the image was not found", func() {
body := []byte(`{"Image": "test-image"}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
Expand Down Expand Up @@ -542,17 +708,18 @@ func getDefaultCreateOpt(conf config.Config) types.ContainerCreateOptions {
MemorySwappiness64: -1, // nerdctl default.
PidsLimit: -1, // nerdctl default.
Cgroupns: defaults.CgroupnsMode(), // nerdctl default.
Ulimit: []string{},
// #endregion

// #region for user flags
User: "",
GroupAdd: []string{}, // nerdctl default.
User: "",
// #endregion

// #region for security flags
SecurityOpt: []string{}, // nerdctl default.
CapAdd: []string{}, // nerdctl default.
CapDrop: []string{}, // nerdctl default.
Privileged: false,
// #endregion

// #region for runtime flags
Expand Down
61 changes: 38 additions & 23 deletions api/types/container_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/containerd/nerdctl/pkg/inspecttypes/dockercompat"
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
)

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

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

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

// Contains container's resources (cgroups, ulimits)
CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers)
Memory int64 // Memory limit (in bytes)
CPUPeriod int64 `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period
CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota
// TODO: CPUSetCPUs string `json:"CpusetCpus"` // CPUSetCPUs specifies the CPUs in which to allow execution (0-3, 0,1)
// 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.
MemoryReservation int64 // MemoryReservation specifies the memory soft limit (in bytes)
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap
MemorySwappiness int64 // MemorySwappiness64 specifies the tune container memory swappiness (0 to 100) (default -1)
// TODO: Resources

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

Expand Down Expand Up @@ -249,3 +262,5 @@ type StatsJSON struct {
// Networks request version >=1.21
Networks map[string]dockertypes.NetworkStats `json:"networks,omitempty"`
}

type Ulimit = units.Ulimit
Loading
Loading