-
Notifications
You must be signed in to change notification settings - Fork 2
/
docker.go
151 lines (136 loc) · 4.62 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
package main
import (
"context"
"errors"
"fmt"
"net"
dt "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
docker "github.com/docker/docker/client"
"github.com/wdullaer/dd-dns/types"
"tailscale.com/client/tailscale"
)
func syncDNSWithDocker(state *State) error {
args := filters.NewArgs()
args.Add("label", state.Config.DockerLabel)
args.Add("status", "running")
containerList, err := state.DockerClient.ContainerList(context.Background(), dt.ContainerListOptions{
Filters: args,
})
if err != nil {
return err
}
mappingList := make([]*types.DNSMapping, len(containerList))
for i, container := range containerList {
ip, err := getIP(&containerList[i], state.Config.DNSContent)
if err != nil {
state.Logger.Errorw("Failed to obtain IP address for container", "containerId", container.ID, "err", err)
continue
}
mappingList[i] = &types.DNSMapping{
Name: container.Labels[state.Config.DockerLabel],
IP: ip,
ContainerID: container.ID,
}
}
state.Logger.Infow("Setting new mappings", "mappings", mappingList)
return state.Store.ReplaceMappings(mappingList, state.Provider)
}
func processDockerEvent(event events.Message, state *State) error {
container, err := getContainerByID(state.DockerClient, event.Actor.ID)
if err != nil {
state.Logger.Errorw("Could not obtain container details", "err", err)
return nil
}
ip, err := getIP(container, state.Config.DNSContent)
if err != nil {
state.Logger.Errorw("Could not obtain container IP", "err", err)
return nil
}
mapping := &types.DNSMapping{
Name: event.Actor.Attributes[state.Config.DockerLabel],
IP: ip,
ContainerID: event.Actor.ID,
}
switch event.Action {
case "start":
state.Logger.Infow("Insert into store", "mapping", mapping)
err = state.Store.InsertMapping(mapping, state.Provider.AddHostnameMapping)
if err != nil {
return err
}
case "die":
state.Logger.Infow("Remove from store", "mapping", mapping)
err := state.Store.RemoveMapping(mapping, state.Provider.RemoveHostnameMapping)
if err != nil {
return err
}
default:
state.Logger.Warnw("Unsupported event", "event", event.Action)
}
return nil
}
func makeDockerChannels(client *docker.Client, config *config) (<-chan events.Message, <-chan error) {
args := filters.NewArgs()
args.Add("scope", "swarm")
args.Add("scope", "local")
// args.Add("type", "service") // service is created and deleted, we should probably special case this through some config
args.Add("type", "container")
// args.Add("type", "config") // TODO: check what triggers these events
args.Add("event", "start")
args.Add("event", "die")
// args.Add("event", "update") // Only services are updated
args.Add("label", config.DockerLabel)
// TODO: also listen to network/connect and network/disconnect messages, as these might change the IP of a container
return client.Events(context.Background(), dt.EventsOptions{
Filters: args,
})
}
// getContainerByID retrieves a Container Object. Returns an error if the container is not found
func getContainerByID(client *docker.Client, id string) (*dt.Container, error) {
args := filters.NewArgs()
args.Add("id", id)
containers, err := client.ContainerList(context.Background(), dt.ContainerListOptions{
Filters: args,
})
if err != nil {
return nil, err
}
if len(containers) == 0 {
return nil, fmt.Errorf("no container with ID %s could be found", id)
}
return &containers[0], nil
}
// getIP returns an IP address for a given container. How the IP is determined is driven by mode:
// - If mode is `container`: the IP address of the container in the first network is returned
// - If mode is `tailscale`: the IPv4 address of the current tailnet is returned
// - If mode is an IP address: that IP address is parsed and returned
func getIP(container *dt.Container, mode string) (net.IP, error) {
switch mode {
case "container":
// TODO: look at a docker label for the network to use (return first if not set)
for _, network := range container.NetworkSettings.Networks {
if network.IPAddress != "" {
return net.ParseIP(network.IPAddress), nil
}
}
return nil, errors.New("container has no internal IP addresses")
case "tailscale":
status, err := tailscale.StatusWithoutPeers(context.Background())
if status.CurrentTailnet == nil {
return nil, errors.New("not connected to tailscale")
}
if err != nil {
return nil, err
}
for _, ip := range status.TailscaleIPs {
if ip.Is4() {
return net.IP(ip.AsSlice()), nil
}
}
return nil, errors.New("no tailscale IPv4 address found")
default:
return net.ParseIP(mode), nil
}
}