func postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } var ( out engine.Env job = eng.Job("create", r.Form.Get("name")) outWarnings []string stdoutBuffer = bytes.NewBuffer(nil) warnings = bytes.NewBuffer(nil) ) if err := job.DecodeEnv(r.Body); err != nil { return err } // Read container ID from the first line of stdout job.Stdout.Add(stdoutBuffer) // Read warnings from stderr job.Stderr.Add(warnings) if err := job.Run(); err != nil { return err } // Parse warnings from stderr scanner := bufio.NewScanner(warnings) for scanner.Scan() { outWarnings = append(outWarnings, scanner.Text()) } out.Set("Id", engine.Tail(stdoutBuffer, 1)) out.SetList("Warnings", outWarnings) return writeJSON(w, http.StatusCreated, out) }
func TestPostContainersCopyWhenContainerNotFound(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() r := httptest.NewRecorder() var copyData engine.Env copyData.Set("Resource", "/test.txt") copyData.Set("HostPath", ".") jsonData := bytes.NewBuffer(nil) if err := copyData.Encode(jsonData); err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", "/containers/id_not_found/copy", jsonData) if err != nil { t.Fatal(err) } req.Header.Add("Content-Type", "application/json") if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusNotFound { t.Fatalf("404 expected for id_not_found Container, received %v", r.Code) } }
func postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } var ( out engine.Env name = vars["name"] job = eng.Job("execCreate", name) stdoutBuffer = bytes.NewBuffer(nil) ) if err := job.DecodeEnv(r.Body); err != nil { return err } job.Stdout.Add(stdoutBuffer) // Register an instance of Exec in container. if err := job.Run(); err != nil { fmt.Fprintf(os.Stderr, "Error setting up exec command in container %s: %s\n", name, err) return err } // Return the ID out.Set("Id", engine.Tail(stdoutBuffer, 1)) return writeJSON(w, http.StatusCreated, out) }
func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } var ( config engine.Env env engine.Env job = eng.Job("commit", r.Form.Get("container")) stdoutBuffer = bytes.NewBuffer(nil) ) if err := config.Decode(r.Body); err != nil { utils.Errorf("%s", err) } if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { job.Setenv("pause", "1") } else { job.Setenv("pause", r.FormValue("pause")) } job.Setenv("repo", r.Form.Get("repo")) job.Setenv("tag", r.Form.Get("tag")) job.Setenv("author", r.Form.Get("author")) job.Setenv("comment", r.Form.Get("comment")) job.SetenvSubEnv("config", &config) job.Stdout.Add(stdoutBuffer) if err := job.Run(); err != nil { return err } env.Set("Id", engine.Tail(stdoutBuffer, 1)) return writeJSON(w, http.StatusCreated, env) }
func TestIPv6InterfaceAllocationAutoNetmaskLe80(t *testing.T) { input := engine.Env{} _, subnet, _ := net.ParseCIDR("2001:db8:1234:1234:1234::/80") // set global ipv6 input.Set("globalIPv6Network", subnet.String()) input.Set("RequestedMac", "ab:cd:ab:cd:ab:cd") output := newInterfaceAllocation(t, input) // ensure global ip with mac ip := net.ParseIP(output.Get("GlobalIPv6")) expected_ip := net.ParseIP("2001:db8:1234:1234:1234:abcd:abcd:abcd") if ip.String() != expected_ip.String() { t.Fatalf("Error ip %s should be %s", ip.String(), expected_ip.String()) } // ensure link local format ip = net.ParseIP(output.Get("LinkLocalIPv6")) expected_ip = net.ParseIP("fe80::a9cd:abff:fecd:abcd") if ip.String() != expected_ip.String() { t.Fatalf("Error ip %s should be %s", ip.String(), expected_ip.String()) } }
func TestPostContainersCopy(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() // Create a container and remove a file containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"touch", "/test.txt"}, }, t, ) containerRun(eng, containerID, t) r := httptest.NewRecorder() var copyData engine.Env copyData.Set("Resource", "/test.txt") copyData.Set("HostPath", ".") jsonData := bytes.NewBuffer(nil) if err := copyData.Encode(jsonData); err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", "/containers/"+containerID+"/copy", jsonData) if err != nil { t.Fatal(err) } req.Header.Add("Content-Type", "application/json") if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) if r.Code != http.StatusOK { t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } found := false for tarReader := tar.NewReader(r.Body); ; { h, err := tarReader.Next() if err != nil { if err == io.EOF { break } t.Fatal(err) } if h.Name == "test.txt" { found = true break } } if !found { t.Fatalf("The created test file has not been found in the copied output") } }
// Allocate a network interface func Allocate(job *engine.Job) engine.Status { var ( ip *net.IP err error id = job.Args[0] requestedIP = net.ParseIP(job.Getenv("RequestedIP")) ) if requestedIP != nil { ip, err = ipallocator.RequestIP(bridgeNetwork, &requestedIP) } else { ip, err = ipallocator.RequestIP(bridgeNetwork, nil) } if err != nil { return job.Error(err) } out := engine.Env{} out.Set("IP", ip.String()) out.Set("Mask", bridgeNetwork.Mask.String()) out.Set("Gateway", bridgeNetwork.IP.String()) out.Set("Bridge", bridgeIface) size, _ := bridgeNetwork.Mask.Size() out.SetInt("IPPrefixLen", size) currentInterfaces.Set(id, &networkInterface{ IP: *ip, }) out.WriteTo(job.Stdout) return engine.StatusOK }
func postContainersWait(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } var ( env engine.Env stdoutBuffer = bytes.NewBuffer(nil) job = eng.Job("wait", vars["name"]) ) job.Stdout.Add(stdoutBuffer) if err := job.Run(); err != nil { return err } env.Set("StatusCode", engine.Tail(stdoutBuffer, 1)) return writeJSON(w, http.StatusOK, env) }
func TestIPv6InterfaceAllocationAutoNetmaskGt80(t *testing.T) { input := engine.Env{} _, subnet, _ := net.ParseCIDR("2001:db8:1234:1234:1234::/81") // set global ipv6 input.Set("globalIPv6Network", subnet.String()) output := newInterfaceAllocation(t, input) // ensure low manually assigend global ip ip := net.ParseIP(output.Get("GlobalIPv6")) _, subnet, _ = net.ParseCIDR(fmt.Sprintf("%s/%d", subnet.IP.String(), 120)) if !subnet.Contains(ip) { t.Fatalf("Error ip %s not in subnet %s", ip.String(), subnet.String()) } }
// CmdCp copies files/folders from a path on the container to a directory on the host running the command. // // If HOSTDIR is '-', the data is written as a tar file to STDOUT. // // Usage: docker cp CONTAINER:PATH HOSTDIR func (cli *DockerCli) CmdCp(args ...string) error { cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data\nas a tar file to STDOUT.", true) cmd.Require(flag.Exact, 2) cmd.ParseFlags(args, true) var copyData engine.Env info := strings.Split(cmd.Arg(0), ":") if len(info) != 2 { return fmt.Errorf("Error: Path not specified") } copyData.Set("Resource", info[1]) copyData.Set("HostPath", cmd.Arg(1)) stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, nil) if stream != nil { defer stream.Close() } if statusCode == 404 { return fmt.Errorf("No such container: %v", info[0]) } if err != nil { return err } if statusCode == 200 { dest := copyData.Get("HostPath") if dest == "-" { _, err = io.Copy(cli.out, stream) } else { err = archive.Untar(stream, dest, &archive.TarOptions{NoLchown: true}) } if err != nil { return err } } return nil }
// Allocate a network interface func Allocate(job *engine.Job) engine.Status { var ( ip net.IP mac net.HardwareAddr err error id = job.Args[0] requestedIP = net.ParseIP(job.Getenv("RequestedIP")) ) if requestedIP != nil { ip, err = ipallocator.RequestIP(bridgeNetwork, requestedIP) } else { ip, err = ipallocator.RequestIP(bridgeNetwork, nil) } if err != nil { return job.Error(err) } // If no explicit mac address was given, generate a random one. if mac, err = net.ParseMAC(job.Getenv("RequestedMac")); err != nil { mac = generateMacAddr(ip) } out := engine.Env{} out.Set("IP", ip.String()) out.Set("Mask", bridgeNetwork.Mask.String()) out.Set("Gateway", bridgeNetwork.IP.String()) out.Set("MacAddress", mac.String()) out.Set("Bridge", bridgeIface) size, _ := bridgeNetwork.Mask.Size() out.SetInt("IPPrefixLen", size) currentInterfaces.Set(id, &networkInterface{ IP: ip, }) out.WriteTo(job.Stdout) return engine.StatusOK }
func postContainersRestore(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } if err := parseForm(r); err != nil { return err } job := eng.Job("restore", vars["name"], vars["checkpointID"]) job.SetenvBool("clone", r.Form.Get("clone") == "1") stdoutBuffer := bytes.NewBuffer(nil) job.Stdout.Add(stdoutBuffer) if err := job.Run(); err != nil { return err } var out engine.Env out.Set("Id", engine.Tail(stdoutBuffer, 1)) return writeJSON(w, http.StatusOK, out) }
func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var ( authConfig, err = ioutil.ReadAll(r.Body) job = eng.Job("auth") stdoutBuffer = bytes.NewBuffer(nil) ) if err != nil { return err } job.Setenv("authConfig", string(authConfig)) job.Stdout.Add(stdoutBuffer) if err = job.Run(); err != nil { return err } if status := engine.Tail(stdoutBuffer, 1); status != "" { var env engine.Env env.Set("Status", status) return writeJSON(w, http.StatusOK, env) } w.WriteHeader(http.StatusNoContent) return nil }
func postContainersCopy(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } var copyData engine.Env if err := checkForJson(r); err != nil { return err } if err := copyData.Decode(r.Body); err != nil { return err } if copyData.Get("Resource") == "" { return fmt.Errorf("Path cannot be empty") } origResource := copyData.Get("Resource") if copyData.Get("Resource")[0] == '/' { copyData.Set("Resource", copyData.Get("Resource")[1:]) } job := eng.Job("container_copy", vars["name"], copyData.Get("Resource")) job.Stdout.Add(w) w.Header().Set("Content-Type", "application/x-tar") if err := job.Run(); err != nil { log.Errorf("%s", err.Error()) if strings.Contains(strings.ToLower(err.Error()), "no such id") { w.WriteHeader(http.StatusNotFound) } else if strings.Contains(err.Error(), "no such file or directory") { return fmt.Errorf("Could not find the file %s in container %s", origResource, vars["name"]) } } return nil }
func TestIPv6InterfaceAllocationRequest(t *testing.T) { input := engine.Env{} _, subnet, _ := net.ParseCIDR("2001:db8:1234:1234:1234::/80") expected_ip := net.ParseIP("2001:db8:1234:1234:1234::1328") // set global ipv6 input.Set("globalIPv6Network", subnet.String()) input.Set("RequestedIPv6", expected_ip.String()) output := newInterfaceAllocation(t, input) // ensure global ip with mac ip := net.ParseIP(output.Get("GlobalIPv6")) if ip.String() != expected_ip.String() { t.Fatalf("Error ip %s should be %s", ip.String(), expected_ip.String()) } // retry -> fails for duplicated address input.SetBool("expectFail", true) output = newInterfaceAllocation(t, input) }
// Allocate an external port and map it to the interface func AllocatePort(job *engine.Job) engine.Status { var ( err error ip = defaultBindingIP id = job.Args[0] hostIP = job.Getenv("HostIP") hostPort = job.GetenvInt("HostPort") containerPort = job.GetenvInt("ContainerPort") proto = job.Getenv("Proto") network = currentInterfaces.Get(id) ) if hostIP != "" { ip = net.ParseIP(hostIP) if ip == nil { return job.Errorf("Bad parameter: invalid host ip %s", hostIP) } } // host ip, proto, and host port var container net.Addr switch proto { case "tcp": container = &net.TCPAddr{IP: network.IP, Port: containerPort} case "udp": container = &net.UDPAddr{IP: network.IP, Port: containerPort} default: return job.Errorf("unsupported address type %s", proto) } // // Try up to 10 times to get a port that's not already allocated. // // In the event of failure to bind, return the error that portmapper.Map // yields. // var host net.Addr for i := 0; i < MaxAllocatedPortAttempts; i++ { if host, err = portmapper.Map(container, ip, hostPort); err == nil { break } if allocerr, ok := err.(portallocator.ErrPortAlreadyAllocated); ok { // There is no point in immediately retrying to map an explicitly // chosen port. if hostPort != 0 { job.Logf("Failed to bind %s for container address %s: %s", allocerr.IPPort(), container.String(), allocerr.Error()) break } // Automatically chosen 'free' port failed to bind: move on the next. job.Logf("Failed to bind %s for container address %s. Trying another port.", allocerr.IPPort(), container.String()) } else { // some other error during mapping job.Logf("Received an unexpected error during port allocation: %s", err.Error()) break } } if err != nil { return job.Error(err) } network.PortMappings = append(network.PortMappings, host) out := engine.Env{} switch netAddr := host.(type) { case *net.TCPAddr: out.Set("HostIP", netAddr.IP.String()) out.SetInt("HostPort", netAddr.Port) case *net.UDPAddr: out.Set("HostIP", netAddr.IP.String()) out.SetInt("HostPort", netAddr.Port) } if _, err := out.WriteTo(job.Stdout); err != nil { return job.Error(err) } return engine.StatusOK }
// Regression test for https://github.com/docker/docker/issues/6231 func TestConstainersStartChunkedEncodingHostConfig(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() r := httptest.NewRecorder() var testData engine.Env testData.Set("Image", "docker-test-image") testData.SetAuto("Volumes", map[string]struct{}{"/foo": {}}) testData.Set("Cmd", "true") jsonData := bytes.NewBuffer(nil) if err := testData.Encode(jsonData); err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", "/containers/create?name=chunk_test", jsonData) if err != nil { t.Fatal(err) } req.Header.Add("Content-Type", "application/json") if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) var testData2 engine.Env testData2.SetAuto("Binds", []string{"/tmp:/foo"}) jsonData = bytes.NewBuffer(nil) if err := testData2.Encode(jsonData); err != nil { t.Fatal(err) } req, err = http.NewRequest("POST", "/containers/chunk_test/start", jsonData) if err != nil { t.Fatal(err) } req.Header.Add("Content-Type", "application/json") // This is a cheat to make the http request do chunked encoding // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite // http://golang.org/src/pkg/net/http/request.go?s=11980:12172 req.ContentLength = -1 if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) type config struct { HostConfig struct { Binds []string } } req, err = http.NewRequest("GET", "/containers/chunk_test/json", nil) if err != nil { t.Fatal(err) } r2 := httptest.NewRecorder() req.Header.Add("Content-Type", "application/json") if err := server.ServeRequest(eng, api.APIVERSION, r2, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) c := config{} json.Unmarshal(r2.Body.Bytes(), &c) if len(c.HostConfig.Binds) == 0 { t.Fatal("Chunked Encoding not handled") } if c.HostConfig.Binds[0] != "/tmp:/foo" { t.Fatal("Chunked encoding not properly handled, execpted binds to be /tmp:/foo, got:", c.HostConfig.Binds[0]) } }
// Allocate a network interface func Allocate(job *engine.Job) error { var ( ip net.IP mac net.HardwareAddr err error id = job.Args[0] requestedIP = net.ParseIP(job.Getenv("RequestedIP")) requestedIPv6 = net.ParseIP(job.Getenv("RequestedIPv6")) globalIPv6 net.IP ) ip, err = ipAllocator.RequestIP(bridgeIPv4Network, requestedIP) if err != nil { return err } // If no explicit mac address was given, generate a random one. if mac, err = net.ParseMAC(job.Getenv("RequestedMac")); err != nil { mac = generateMacAddr(ip) } if globalIPv6Network != nil { // If globalIPv6Network Size is at least a /80 subnet generate IPv6 address from MAC address netmask_ones, _ := globalIPv6Network.Mask.Size() if requestedIPv6 == nil && netmask_ones <= 80 { requestedIPv6 = make(net.IP, len(globalIPv6Network.IP)) copy(requestedIPv6, globalIPv6Network.IP) for i, h := range mac { requestedIPv6[i+10] = h } } globalIPv6, err = ipAllocator.RequestIP(globalIPv6Network, requestedIPv6) if err != nil { logrus.Errorf("Allocator: RequestIP v6: %v", err) return err } logrus.Infof("Allocated IPv6 %s", globalIPv6) } out := engine.Env{} out.Set("IP", ip.String()) out.Set("Mask", bridgeIPv4Network.Mask.String()) out.Set("Gateway", bridgeIPv4Network.IP.String()) out.Set("MacAddress", mac.String()) out.Set("Bridge", bridgeIface) size, _ := bridgeIPv4Network.Mask.Size() out.SetInt("IPPrefixLen", size) // If linklocal IPv6 localIPv6Net, err := linkLocalIPv6FromMac(mac.String()) if err != nil { return err } localIPv6, _, _ := net.ParseCIDR(localIPv6Net) out.Set("LinkLocalIPv6", localIPv6.String()) out.Set("MacAddress", mac.String()) if globalIPv6Network != nil { out.Set("GlobalIPv6", globalIPv6.String()) sizev6, _ := globalIPv6Network.Mask.Size() out.SetInt("GlobalIPv6PrefixLen", sizev6) out.Set("IPv6Gateway", bridgeIPv6Addr.String()) } currentInterfaces.Set(id, &networkInterface{ IP: ip, IPv6: globalIPv6, }) out.WriteTo(job.Stdout) return nil }
// Allocate an external port and map it to the interface func AllocatePort(job *engine.Job) error { var ( err error ip = defaultBindingIP id = job.Args[0] hostIP = job.Getenv("HostIP") hostPort = job.GetenvInt("HostPort") containerPort = job.GetenvInt("ContainerPort") proto = job.Getenv("Proto") network = currentInterfaces.Get(id) ) if hostIP != "" { ip = net.ParseIP(hostIP) if ip == nil { return fmt.Errorf("Bad parameter: invalid host ip %s", hostIP) } } // host ip, proto, and host port var container net.Addr switch proto { case "tcp": container = &net.TCPAddr{IP: network.IP, Port: containerPort} case "udp": container = &net.UDPAddr{IP: network.IP, Port: containerPort} default: return fmt.Errorf("unsupported address type %s", proto) } // // Try up to 10 times to get a port that's not already allocated. // // In the event of failure to bind, return the error that portmapper.Map // yields. // var host net.Addr for i := 0; i < MaxAllocatedPortAttempts; i++ { if host, err = portmapper.Map(container, ip, hostPort); err == nil { break } // There is no point in immediately retrying to map an explicitly // chosen port. if hostPort != 0 { logrus.Warnf("Failed to allocate and map port %d: %s", hostPort, err) break } logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1) } if err != nil { return err } network.PortMappings = append(network.PortMappings, host) out := engine.Env{} switch netAddr := host.(type) { case *net.TCPAddr: out.Set("HostIP", netAddr.IP.String()) out.SetInt("HostPort", netAddr.Port) case *net.UDPAddr: out.Set("HostIP", netAddr.IP.String()) out.SetInt("HostPort", netAddr.Port) } if _, err := out.WriteTo(job.Stdout); err != nil { return err } return nil }