-
Notifications
You must be signed in to change notification settings - Fork 16
/
docker.go
157 lines (137 loc) · 4.95 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
148
149
150
151
152
153
154
155
156
157
package traefikkop
import (
"context"
"fmt"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/traefik/traefik/v2/pkg/provider/docker"
)
// Copied from traefik. See docker provider package for original impl
type dockerCache struct {
client client.APIClient
list []types.Container
details map[string]types.ContainerJSON
}
// Must be 0 for unix socket?
// Non-zero throws an error
const defaultTimeout = time.Duration(0)
func createDockerClient(endpoint string) (client.APIClient, error) {
opts, err := getClientOpts(endpoint)
if err != nil {
return nil, err
}
httpHeaders := map[string]string{
"User-Agent": "traefik-kop " + Version,
}
opts = append(opts, client.WithHTTPHeaders(httpHeaders))
apiVersion := docker.DockerAPIVersion
SwarmMode := false
if SwarmMode {
apiVersion = docker.SwarmAPIVersion
}
opts = append(opts, client.WithVersion(apiVersion))
return client.NewClientWithOpts(opts...)
}
func getClientOpts(endpoint string) ([]client.Opt, error) {
// we currently do not support ssh, so skip helper setup
opts := []client.Opt{
client.WithHost(endpoint),
client.WithTimeout(time.Duration(defaultTimeout)),
}
return opts, nil
}
// looks up the docker container by finding the matching service or router traefik label
func (dc *dockerCache) findContainerByServiceName(svcType string, svcName string, routerName string) (types.ContainerJSON, error) {
svcName = strings.TrimSuffix(svcName, "@docker")
routerName = strings.TrimSuffix(routerName, "@docker")
if dc.list == nil {
var err error
dc.list, err = dc.client.ContainerList(context.Background(), types.ContainerListOptions{})
if err != nil {
return types.ContainerJSON{}, errors.Wrap(err, "failed to list containers")
}
}
for _, c := range dc.list {
var container types.ContainerJSON
var ok bool
if container, ok = dc.details[c.ID]; !ok {
var err error
container, err = dc.client.ContainerInspect(context.Background(), c.ID)
if err != nil {
return types.ContainerJSON{}, errors.Wrapf(err, "failed to inspect container %s", c.ID)
}
dc.details[c.ID] = container
}
// check labels
svcNeedle := fmt.Sprintf("traefik.%s.services.%s.", svcType, svcName)
routerNeedle := fmt.Sprintf("traefik.%s.routers.%s.", svcType, routerName)
for k := range container.Config.Labels {
if strings.HasPrefix(k, svcNeedle) || (routerName != "" && strings.HasPrefix(k, routerNeedle)) {
logrus.Debugf("found container '%s' (%s) for service '%s'", container.Name, container.ID, svcName)
return container, nil
}
}
}
return types.ContainerJSON{}, errors.Errorf("service label not found for %s/%s", svcType, svcName)
}
// Check if the port is explicitly set via label
func isPortSet(container types.ContainerJSON, svcType string, svcName string) string {
svcName = strings.TrimSuffix(svcName, "@docker")
needle := fmt.Sprintf("traefik.%s.services.%s.loadbalancer.server.port", svcType, svcName)
return container.Config.Labels[needle]
}
// getPortBinding checks the docker container config for a port binding for the
// service. Currently this will only work if a single port is mapped/exposed.
//
// i.e., it looks for the following from a docker-compose service:
//
// ports:
// - 5555:5555
//
// If more than one port is bound (e.g., for a service like minio), then this
// detection will fail. Instead, the user should explicitly set the port in the
// label.
func getPortBinding(container types.ContainerJSON) (string, error) {
numBindings := len(container.HostConfig.PortBindings)
if numBindings > 1 {
return "", errors.Errorf("found more than one host-port binding for container '%s' (%s)", container.Name, portBindingString(container.HostConfig.PortBindings))
}
for _, v := range container.HostConfig.PortBindings {
if len(v) > 1 {
return "", errors.Errorf("found more than one host-port binding for container '%s' (%s)", container.Name, portBindingString(container.HostConfig.PortBindings))
}
return v[0].HostPort, nil
}
// check for a randomly set port via --publish-all
if container.NetworkSettings != nil && len(container.NetworkSettings.Ports) == 1 {
for _, v := range container.NetworkSettings.Ports {
if len(v) > 0 {
port := v[0].HostPort
if port != "" {
if len(v) > 1 {
logrus.Warnf("found %d port(s); trying the first one", len(v))
}
return port, nil
}
}
}
}
return "", errors.Errorf("no host-port binding found for container '%s'", container.Name)
}
// Convert host:container port binding map to a compact printable string
func portBindingString(bindings nat.PortMap) string {
s := []string{}
for k, v := range bindings {
if len(v) > 0 {
containerPort := strings.TrimSuffix(string(k), "/tcp")
containerPort = strings.TrimSuffix(string(containerPort), "/udp")
s = append(s, fmt.Sprintf("%s:%s", v[0].HostPort, containerPort))
}
}
return strings.Join(s, ", ")
}