/
docker.go
129 lines (105 loc) · 3.24 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
package testutil
import (
"bytes"
"encoding/json"
"fmt"
"os/exec"
"strconv"
"strings"
)
// Container tracks information about the docker container started for tests.
type Container struct {
ID string
Host string
Ports map[string]int
}
// StartContainer starts the specified container for running tests.
func StartContainer(image string, ports []string, args ...string) (*Container, error) {
arg := []string{"run", "-P", "-d"}
arg = append(arg, args...)
arg = append(arg, image)
cmd := exec.Command("docker", arg...)
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("could not start container %s: %w", image, err)
}
id := out.String()[:12]
c := Container{
ID: id,
Host: "0.0.0.0",
Ports: make(map[string]int, len(ports)),
}
for _, port := range ports {
_, hostPort, err := ExtractIPPort(id, port)
if err != nil {
// nolint
StopContainer(id)
return nil, fmt.Errorf("could not extract ip/port: %w", err)
}
portInt, err := strconv.Atoi(hostPort)
if err != nil {
// nolint
StopContainer(id)
return nil, fmt.Errorf("could not extract ip/port: %w", err)
}
c.Ports[port] = portInt
}
return &c, nil
}
// ExecInContainer executes a command in the specified container.
func ExecInContainer(id string, args ...string) ([]byte, error) {
arg := []string{"exec", "-t", id}
arg = append(arg, args...)
cmd, err := exec.Command("docker", arg...).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("could not execute command container %s: %w. %s", id, err, string(cmd))
}
return cmd, nil
}
// StopContainer stops and removes the specified container.
func StopContainer(id string) error {
if err := exec.Command("docker", "stop", id).Run(); err != nil {
return fmt.Errorf("could not stop container: %w", err)
}
if err := exec.Command("docker", "rm", id, "-v").Run(); err != nil {
return fmt.Errorf("could not remove container: %w", err)
}
return nil
}
// DumpContainerLogs outputs logs from the running docker container.
func DumpContainerLogs(id string) []byte {
out, err := exec.Command("docker", "logs", id).CombinedOutput()
if err != nil {
return nil
}
return out
}
// ExtractIPPort extracts container real port binded to docker application port.
func ExtractIPPort(id, port string) (hostIP, hostPort string, err error) {
tmpl := fmt.Sprintf("[{{range $k,$v := (index .NetworkSettings.Ports \"%s/tcp\")}}{{json $v}}{{end}}]", port)
// nolint
cmd := exec.Command("docker", "inspect", "-f", tmpl, id)
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return "", "", fmt.Errorf("could not inspect container %s: %w", id, err)
}
// When IPv6 is turned on with Docker.
// Got [{"HostIp":"0.0.0.0","HostPort":"49190"}{"HostIp":"::","HostPort":"49190"}]
// Need [{"HostIp":"0.0.0.0","HostPort":"49190"},{"HostIp":"::","HostPort":"49190"}]
data := strings.ReplaceAll(out.String(), "}{", "},{")
var docs []struct {
HostIP string
HostPort string
}
if err := json.Unmarshal([]byte(data), &docs); err != nil {
return "", "", fmt.Errorf("could not decode json: %w", err)
}
for _, doc := range docs {
if doc.HostIP != "::" {
return doc.HostIP, doc.HostPort, nil
}
}
return "", "", fmt.Errorf("could not locate ip/port")
}