-
Notifications
You must be signed in to change notification settings - Fork 22
/
docker.go
130 lines (119 loc) · 3.48 KB
/
docker.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package muniverse
import (
"encoding/json"
"errors"
"fmt"
"os/exec"
"runtime"
"strings"
"sync"
"github.com/unixpickle/essentials"
"golang.org/x/net/context"
)
// dockerRun starts a new environment Docker container.
//
// If volume is non-empty, it is treated as a local path
// and is mounted to "/downloaded_games".
func dockerRun(ctx context.Context, image, volume string,
spec *EnvSpec) (id string, err error) {
args := []string{
"run",
"-p",
portRange + ":9222",
"-p",
portRange + ":1337",
"--shm-size=200m",
"-d", // Run in detached mode.
"--rm", // Automatically delete the container.
"-i", // Give netcat a stdin to read from.
}
if volume != "" {
if strings.Contains(volume, ":") {
return "", errors.New("path contains colons: " + volume)
}
args = append(args, "-v", volume+":/downloaded_games")
}
args = append(args, image,
fmt.Sprintf("--window-size=%d,%d", spec.Width, spec.Height))
output, err := dockerCommand(ctx, args...)
if err != nil {
return "", essentials.AddCtx("docker run",
fmt.Errorf("%s (make sure docker is up-to-date)", err))
}
return strings.TrimSpace(string(output)), nil
}
// dockerBoundPorts returns a mapping from container ports
// to host ports.
//
// For example, the result might map "9222/tcp" to "9233".
func dockerBoundPorts(ctx context.Context,
containerID string) (mapping map[string]string, err error) {
defer essentials.AddCtxTo("docker inspect", &err)
rawJSON, err := dockerCommand(ctx, "inspect", containerID)
if err != nil {
return nil, err
}
var info []struct {
NetworkSettings struct {
Ports map[string][]struct {
HostPort string
}
}
}
if err := json.Unmarshal(rawJSON, &info); err != nil {
return nil, err
}
if len(info) != 1 {
return nil, errors.New("unexpected number of results")
}
rawMapping := info[0].NetworkSettings.Ports
mapping = map[string]string{}
for containerPort, hostPorts := range rawMapping {
if len(hostPorts) != 1 {
return nil, errors.New("unexpected number of host ports")
}
mapping[containerPort] = hostPorts[0].HostPort
}
return
}
// dockerIPAddress returns an IP address which can be used
// to connect to the given container.
//
// This is intended to fix an issue with Docker on Windows
// where exposed ports aren't available on localhost.
func dockerIPAddress(ctx context.Context, containerID string) (addr string, err error) {
if runtime.GOOS != "windows" {
return "localhost", nil
}
defer essentials.AddCtxTo("get IP of Docker VM", &err)
cmd := exec.CommandContext(ctx, "docker-machine", "ip", "default")
output, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
cleanIP := strings.TrimSpace(string(output))
// Will detect pretty much any error message.
if strings.ContainsAny(cleanIP, " \n\t") {
return "", errors.New("unexpected output: " + string(output))
}
return cleanIP, nil
}
// dockerLock is used to synchronize Docker calls.
//
// This exists to fix a race condition in Docker:
// https://github.com/docker/libnetwork/issues/1740.
var dockerLock sync.Mutex
// dockerCommand runs a Docker sub-command with the given
// arguments.
func dockerCommand(ctx context.Context, args ...string) (output []byte, err error) {
dockerLock.Lock()
defer dockerLock.Unlock()
output, err = exec.CommandContext(ctx, "docker", args...).Output()
if err != nil {
if eo, ok := err.(*exec.ExitError); ok && len(eo.Stderr) > 0 {
stderrMsg := strings.TrimSpace(string(eo.Stderr))
err = fmt.Errorf("%s: %s", eo.String(), stderrMsg)
}
}
return
}