Example #1
1
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)
}
Example #2
0
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)
	}
}
Example #3
0
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)
}
Example #4
0
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)
}
Example #5
0
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())
	}

}
Example #6
0
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")
	}
}
Example #7
0
// 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
}
Example #8
0
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)
}
Example #9
0
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())
	}
}
Example #10
0
File: cp.go Project: jbbarth/docker
// 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
}
Example #11
0
// 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
}
Example #12
0
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)
}
Example #13
0
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
}
Example #14
0
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
}
Example #15
0
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)
}
Example #16
0
// 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
}
Example #17
0
// 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])
	}
}
Example #18
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
}
Example #19
0
// 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
}