-
Notifications
You must be signed in to change notification settings - Fork 13
/
container.go
264 lines (240 loc) · 7.58 KB
/
container.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
/*
Package meli provides programming interface to interact with the docker daemon.
meli also has a command line application(cli) that is a faster and drop in alternative to docker-compose.
The installation instructions for the cli application can be found: https://github.com/komuw/meli#installingupgrading
Example usage:
package main
import (
"errors"
"github.com/sanity-io/litter"
"github.com/gogo/protobuf/vanity/command"
"context"
"log"
"os"
"github.com/docker/docker/client"
"github.com/komuw/meli"
)
func main() {
dc := &meli.DockerContainer{
ComposeService: meli.ComposeService{Image: "busybox"},
LogMedium: os.Stdout,
FollowLogs: true}
ctx := context.Background()
cli, err := client.NewEnvClient()
if err != nil {
log.Fatal(err, " :unable to intialize docker client")
}
defer cli.Close()
meli.LoadAuth() // read dockerhub info
err = meli.PullDockerImage(ctx, cli, dc)
log.Println(err)
}
*/
package meli
import (
"bufio"
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
)
// CreateContainer creates a docker container
func CreateContainer(ctx context.Context, cli APIclient, dc *DockerContainer) (bool, string, error) {
// 1. make labels
labelsMap := make(map[string]string)
if len(dc.ComposeService.Labels) > 0 {
for _, v := range dc.ComposeService.Labels {
onelabel := formatLabels(v)
labelsMap[onelabel[0]] = onelabel[1]
}
}
// reuse container if already running
// only reuse containers if we aren't rebuilding
meliService := labelsMap["meli_service"]
filters := filters.NewArgs()
filters.Add("label", fmt.Sprintf("meli_service=%s", meliService))
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
Quiet: true,
All: true,
Filters: filters})
if err != nil {
fmt.Println(" :unable to list containers")
}
if len(containers) > 0 {
if !dc.Rebuild {
dc.UpdateContainerID(containers[0].ID)
return true, containers[0].ID, nil
}
shadowErr := cli.ContainerRemove(ctx, containers[0].ID, types.ContainerRemoveOptions{Force: true})
if err != nil {
fmt.Println(shadowErr, " :unable to remove existing container, ", containers[0].ID)
}
}
// 2. make ports
portsMap := make(map[nat.Port]struct{})
portBindingMap := make(map[nat.Port][]nat.PortBinding)
if len(dc.ComposeService.Ports) > 0 {
for _, v := range dc.ComposeService.Ports {
oneport := formatPorts(v)
// issues/96
hostport := ""
containerport := ""
if len(oneport) == 1 {
hostport = ""
containerport = oneport[0]
} else {
hostport = oneport[0]
containerport = oneport[1]
}
myPortBinding := nat.PortBinding{HostPort: hostport}
port, shadowErr := nat.NewPort("tcp", containerport)
if shadowErr != nil {
fmt.Println(shadowErr, " :unable to create a nat.Port")
}
portsMap[port] = emptyStruct{}
portBindingMap[port] = []nat.PortBinding{myPortBinding}
}
}
// 3. create command
cmd := strslice.StrSlice{}
if dc.ComposeService.Command != "" {
sliceCommand := strings.Fields(dc.ComposeService.Command)
cmd = strslice.StrSlice(sliceCommand)
}
// 4. create restart policy
restartPolicy := container.RestartPolicy{}
if dc.ComposeService.Restart != "" {
// You cannot set MaximumRetryCount for the following restart policies;
// always, no, unless-stopped
if dc.ComposeService.Restart == "on-failure" {
restartPolicy = container.RestartPolicy{Name: dc.ComposeService.Restart, MaximumRetryCount: 3}
} else {
restartPolicy = container.RestartPolicy{Name: dc.ComposeService.Restart}
}
}
// 5. build image
imageNamePtr := &dc.ComposeService.Image
if dc.ComposeService.Build != (Buildstruct{}) {
imageName, shadowErr := BuildDockerImage(ctx, cli, dc)
if shadowErr != nil {
return false, "", errors.Wrapf(shadowErr, "unable to build image for service %v", dc.ServiceName)
}
// done this way so that we can manipulate the value of the
// imageName inside this scope
imageNamePtr = &imageName
}
imageName := *imageNamePtr
// 6. add volumes
volume := make(map[string]struct{})
binds := []string{}
if len(dc.ComposeService.Volumes) > 0 {
for _, v := range dc.ComposeService.Volumes {
vol := formatServiceVolumes(v, dc.DockerComposeFile)
volume[vol[1]] = emptyStruct{}
// TODO: handle other read/write modes
whatToBind := vol[0] + ":" + vol[1] + ":rw"
binds = append(binds, whatToBind)
}
}
// 7. process env_files
containerEnv := []string{}
envMap := map[string]string{}
if len(dc.ComposeService.EnvFile) > 0 {
dirWithComposeFile := filepath.Dir(dc.DockerComposeFile)
for _, v := range dc.ComposeService.EnvFile {
dotEnvFile := filepath.Join(dirWithComposeFile, v)
f, shadowErr := os.Open(dotEnvFile)
if shadowErr != nil {
return false, "", errors.Wrapf(shadowErr, "unable to open env file %v", dotEnvFile)
}
// TODO: replace env with a []string since ComposeService.Environment is a []string
env := parsedotenv(f)
for k, v := range env {
envMap[k] = v
}
}
}
// TODO: replace env in parseDotEnv.go with a []string since ComposeService.Environment is a []string
// that way we wont have to incur this for loop
for k, v := range envMap {
envMap[k] = v
containerEnv = append(containerEnv, fmt.Sprintf("%s=%s", k, v))
}
containerEnv = append(containerEnv, dc.ComposeService.Environment...)
containerCreateResp, err := cli.ContainerCreate(
ctx,
&container.Config{
Image: imageName,
Labels: labelsMap,
Env: containerEnv,
ExposedPorts: portsMap,
Cmd: cmd,
Volumes: volume},
&container.HostConfig{
DNS: []string{
"8.8.8.8",
"8.8.4.4",
"2001:4860:4860::8888",
"2001:4860:4860::8844"},
DNSSearch: []string{
"8.8.8.8",
"8.8.4.4",
"2001:4860:4860::8888",
"2001:4860:4860::8844"},
PublishAllPorts: false,
PortBindings: portBindingMap,
NetworkMode: container.NetworkMode(dc.NetworkName),
RestartPolicy: restartPolicy,
Binds: binds,
Links: dc.ComposeService.Links},
nil,
dc.ServiceName)
if err != nil {
return false, "", errors.Wrapf(err, "unable to create container for service %v", dc.ServiceName)
}
dc.UpdateContainerID(containerCreateResp.ID)
return false, containerCreateResp.ID, nil
}
// ContainerStart starts a docker container via docker daemon server
func ContainerStart(ctx context.Context, cli APIclient, dc *DockerContainer) error {
err := cli.ContainerStart(
ctx,
dc.ContainerID,
types.ContainerStartOptions{})
if err != nil {
return errors.Wrapf(err, "unable to start container %v of service %v", dc.ContainerID, dc.ServiceName)
}
return nil
}
// ContainerLogs returns the logs generated by a container in an io.ReadCloser.
func ContainerLogs(ctx context.Context, cli APIclient, dc *DockerContainer) error {
containerLogResp, err := cli.ContainerLogs(
ctx,
dc.ContainerID,
types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Timestamps: true,
Follow: dc.FollowLogs,
Details: true,
Tail: "all"})
if err != nil {
return errors.Wrapf(err, "unable to get logs for container %v of service %v", dc.ContainerID, dc.ServiceName)
}
scanner := bufio.NewScanner(containerLogResp)
for scanner.Scan() {
fmt.Fprintln(dc.LogMedium, dc.ServiceName, "::", scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Println(" :unable to log output for container", dc.ContainerID, err)
}
containerLogResp.Close()
return nil
}