diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 3820e78b..56560595 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -172,15 +172,16 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { GOptions: globalOpt, // #region for basic flags - Interactive: false, // TODO: update this after attach supports STDIN - TTY: false, // TODO: update this after attach supports STDIN - Detach: true, // TODO: current implementation of create does not support AttachStdin, AttachStdout, and AttachStderr flags - Restart: restart, // Restart policy to apply when a container exits. - Rm: req.HostConfig.AutoRemove, // Automatically remove container upon exit. - Pull: "missing", // nerdctl default. - StopSignal: stopSignal, - StopTimeout: stopTimeout, - CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file + Interactive: false, // TODO: update this after attach supports STDIN + TTY: false, // TODO: update this after attach supports STDIN + Detach: true, // TODO: current implementation of create does not support AttachStdin, AttachStdout, and AttachStderr flags + Restart: restart, // Restart policy to apply when a container exits. + Rm: req.HostConfig.AutoRemove, // Automatically remove container upon exit. + Pull: "missing", // nerdctl default. + StopSignal: stopSignal, + StopTimeout: stopTimeout, + CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file + OomKillDisable: req.HostConfig.OomKillDisable, // #endregion // #region for platform flags @@ -264,14 +265,19 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { if req.HostConfig.DNSOptions != nil { dnsOpt = req.HostConfig.DNSOptions } + + if req.NetworkDisabled { + networkMode = "none" + } netOpt := ncTypes.NetworkOptions{ Hostname: req.Hostname, - NetworkSlice: []string{networkMode}, // TODO: Set to none if "NetworkDisabled" is true in request + NetworkSlice: []string{networkMode}, DNSServers: req.HostConfig.DNS, // Custom DNS lookup servers. DNSResolvConfOptions: dnsOpt, // DNS options. DNSSearchDomains: req.HostConfig.DNSSearch, // Custom DNS search domains. PortMappings: portMappings, AddHost: req.HostConfig.ExtraHosts, // Extra hosts. + MACAddress: req.MacAddress, } ctx := namespaces.WithNamespace(r.Context(), h.Config.Namespace) diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 78320bb0..aa0a2c64 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -591,6 +591,63 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body.String()).Should(ContainSubstring("failed to parse")) }) + It("should set specified NetworkDisabled setting", func() { + body := []byte(`{ + "Image": "test-image", + "NetworkDisabled": true + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected network options + netOpt.NetworkSlice = []string{"none"} + + 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 the MACAddress to a user specified value", func() { + body := []byte(`{ + "Image": "test-image", + "MacAddress": "12:34:56:78:9a:bc" + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected network options + netOpt.MACAddress = "12:34:56:78:9a:bc" + 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 the OomKillDisable option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "OomKillDisable": true + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected network options + createOpt.OomKillDisable = 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)) + }) + Context("translate port mappings", func() { It("should return empty if port mappings is nil", func() { Expect(translatePortMappings(nil)).Should(BeEmpty()) diff --git a/api/types/container_types.go b/api/types/container_types.go index 346e8b84..75e42bb3 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -42,12 +42,12 @@ type ContainerConfig struct { Cmd []string `json:",omitempty"` // Command to run when starting the container // TODO Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy // TODO: ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (meaning treat as a command line) (Windows specific). - Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) - Volumes map[string]struct{} `json:",omitempty"` // List of volumes (mounts) used for the container - WorkingDir string `json:",omitempty"` // Current directory (PWD) in the command will be launched - Entrypoint []string `json:",omitempty"` // Entrypoint to run when starting the container - // TODO: NetworkDisabled bool `json:",omitempty"` // Is network disabled - // TODO: MacAddress string `json:",omitempty"` // Mac Address of the container + Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) + Volumes map[string]struct{} `json:",omitempty"` // List of volumes (mounts) used for the container + WorkingDir string `json:",omitempty"` // Current directory (PWD) in the command will be launched + Entrypoint []string `json:",omitempty"` // Entrypoint to run when starting the container + NetworkDisabled bool `json:",omitempty"` // Is network disabled + MacAddress string `json:",omitempty"` // Mac Address of the container // TODO: OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile Labels map[string]string `json:",omitempty"` // List of labels set to this container StopSignal string `json:",omitempty"` // Signal to stop a container @@ -82,7 +82,7 @@ type ContainerHostConfig struct { // 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: OomKillDisable bool // specifies whether to disable OOM Killer + 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 diff --git a/e2e/tests/container_create.go b/e2e/tests/container_create.go index 1cf4681b..fcd315b0 100644 --- a/e2e/tests/container_create.go +++ b/e2e/tests/container_create.go @@ -654,6 +654,101 @@ func ContainerCreate(opt *option.Option) { } } }) + It("should create a container with OomKillDisable set to true", func() { + // Define options + options.Cmd = []string{"sleep", "Infinity"} + options.HostConfig.OomKillDisable = true + + // Create container + statusCode, ctr := createContainer(uClient, url, testContainerName, options) + Expect(statusCode).Should(Equal(http.StatusCreated)) + Expect(ctr.ID).ShouldNot(BeEmpty()) + + // Start container + command.Run(opt, "start", testContainerName) + + // Inspect the container using native format to verify OomKillDisable + nativeResp := command.Stdout(opt, "inspect", "--mode=native", testContainerName) + var nativeInspect []map[string]interface{} + err := json.Unmarshal(nativeResp, &nativeInspect) + Expect(err).Should(BeNil()) + Expect(nativeInspect).Should(HaveLen(1)) + + // Navigate to the linux resources memory section + spec, ok := nativeInspect[0]["Spec"].(map[string]interface{}) + Expect(ok).Should(BeTrue()) + linux, ok := spec["linux"].(map[string]interface{}) + Expect(ok).Should(BeTrue()) + resources, ok := linux["resources"].(map[string]interface{}) + Expect(ok).Should(BeTrue()) + memory, ok := resources["memory"].(map[string]interface{}) + Expect(ok).Should(BeTrue()) + + // Verify OomKillDisable is set + oomKillDisable, ok := memory["disableOOMKiller"].(bool) + Expect(ok).Should(BeTrue()) + Expect(oomKillDisable).Should(BeTrue()) + }) + + It("should create a container with NetworkDisabled set to true", func() { + // Define options + options.Cmd = []string{"sleep", "Infinity"} + options.NetworkDisabled = true + + // Create container + statusCode, ctr := createContainer(uClient, url, testContainerName, options) + Expect(statusCode).Should(Equal(http.StatusCreated)) + Expect(ctr.ID).ShouldNot(BeEmpty()) + + // Start container + command.Run(opt, "start", testContainerName) + + // Inspect using the native format to verify network mode is "none" + nativeResp := command.Stdout(opt, "inspect", "--mode=native", testContainerName) + var nativeInspect []map[string]interface{} + err := json.Unmarshal(nativeResp, &nativeInspect) + Expect(err).Should(BeNil()) + Expect(nativeInspect).Should(HaveLen(1)) + + // Check that network is set to "none" in nerdctl/networks label + labels, ok := nativeInspect[0]["Labels"].(map[string]interface{}) + Expect(ok).Should(BeTrue()) + networks, ok := labels["nerdctl/networks"].(string) + Expect(ok).Should(BeTrue()) + Expect(networks).Should(ContainSubstring(`"none"`)) + }) + + It("should create a container with specified MAC address", func() { + // Define custom MAC address + macAddress := "02:42:ac:11:00:42" + + // Define options + options.Cmd = []string{"sleep", "Infinity"} + options.MacAddress = macAddress + + // Create container + statusCode, ctr := createContainer(uClient, url, testContainerName, options) + Expect(statusCode).Should(Equal(http.StatusCreated)) + Expect(ctr.ID).ShouldNot(BeEmpty()) + + // Start container + command.Run(opt, "start", testContainerName) + + // Inspect container using Docker-compatible format + resp := command.Stdout(opt, "inspect", testContainerName) + var inspect []*dockercompat.Container + err := json.Unmarshal(resp, &inspect) + Expect(err).Should(BeNil()) + Expect(inspect).Should(HaveLen(1)) + + // Verify MAC address in NetworkSettings + Expect(inspect[0].NetworkSettings.MacAddress).Should(Equal(macAddress)) + + // Also verify MAC address in the network details + for _, netDetails := range inspect[0].NetworkSettings.Networks { + Expect(netDetails.MacAddress).Should(Equal(macAddress)) + } + }) }) }