-
-
Notifications
You must be signed in to change notification settings - Fork 65
/
docker.go
147 lines (115 loc) 路 3.37 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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package gnomock
import (
"context"
"fmt"
"io/ioutil"
"strconv"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
)
const localhostAddr = "127.0.0.1"
type docker struct {
client *client.Client
}
func dockerConnect() (*docker, error) {
cli, err := client.NewEnvClient()
if err != nil {
return nil, fmt.Errorf("%w: %s", ErrEnvClient, err)
}
return &docker{cli}, nil
}
func (d *docker) pullImage(ctx context.Context, image string) error {
reader, err := d.client.ImagePull(ctx, image, types.ImagePullOptions{})
if err != nil {
return fmt.Errorf("can't pull image: %w", err)
}
defer func() {
closeErr := reader.Close()
if err == nil {
err = closeErr
}
}()
_, err = ioutil.ReadAll(reader)
if err != nil {
return fmt.Errorf("can't read server output: %w", err)
}
return nil
}
func (d *docker) startContainer(ctx context.Context, image string, ports NamedPorts, cfg *options) (*Container, error) {
exposedPorts := d.exposedPorts(ports)
containerConfig := &container.Config{
Image: image,
ExposedPorts: exposedPorts,
Env: cfg.env,
}
portBindings := d.portBindings(exposedPorts)
hostConfig := &container.HostConfig{PortBindings: portBindings}
resp, err := d.client.ContainerCreate(ctx, containerConfig, hostConfig, nil, "")
if err != nil {
return nil, fmt.Errorf("can't create container: %w", err)
}
err = d.client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
if err != nil {
return nil, fmt.Errorf("can't start container %s: %w", resp.ID, err)
}
containerJSON, err := d.client.ContainerInspect(ctx, resp.ID)
if err != nil {
return nil, fmt.Errorf("can't inspect container %s: %w", resp.ID, err)
}
boundNamedPorts, err := d.boundNamedPorts(containerJSON, ports)
if err != nil {
return nil, fmt.Errorf("can't find bound ports: %w", err)
}
container := &Container{
ID: containerJSON.ID,
Host: localhostAddr,
Ports: boundNamedPorts,
}
return container, nil
}
func (d *docker) exposedPorts(namedPorts NamedPorts) nat.PortSet {
exposedPorts := make(nat.PortSet)
for _, port := range namedPorts {
containerPort := fmt.Sprintf("%d/%s", port.Port, port.Protocol)
exposedPorts[nat.Port(containerPort)] = struct{}{}
}
return exposedPorts
}
func (d *docker) portBindings(exposedPorts nat.PortSet) nat.PortMap {
portBindings := make(nat.PortMap)
for port := range exposedPorts {
portBindings[port] = []nat.PortBinding{
{
HostIP: localhostAddr,
},
}
}
return portBindings
}
func (d *docker) boundNamedPorts(json types.ContainerJSON, namedPorts NamedPorts) (NamedPorts, error) {
boundNamedPorts := make(NamedPorts)
for containerPort, bindings := range json.NetworkSettings.Ports {
if len(bindings) == 0 {
continue
}
hostPortNum, err := strconv.Atoi(bindings[0].HostPort)
if err != nil {
return nil, err
}
portName, err := namedPorts.Find(containerPort.Proto(), containerPort.Int())
if err != nil {
return nil, err
}
boundNamedPorts[portName] = Port{containerPort.Proto(), hostPortNum}
}
return boundNamedPorts, nil
}
func (d *docker) stopContainer(ctx context.Context, id string) error {
err := d.client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true})
if err != nil {
return fmt.Errorf("can't stop container %s: %w", id, err)
}
return nil
}