-
Notifications
You must be signed in to change notification settings - Fork 0
/
docker.go
147 lines (113 loc) · 3.08 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 main
import (
"context"
"fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"regexp"
"sort"
"strconv"
"strings"
)
type Client struct {
Client *client.Client
}
func (c *Client) getRunningContainers(receptionistLabel string) ([]Container, error) {
model := []Container{}
containers, err := c.Client.ContainerList(context.Background(), types.ContainerListOptions{})
if err != nil {
return model, err
}
for _, c := range containers {
var ports []*Port
if l, found := c.Labels[receptionistLabel]; found {
ports, err = getAllWantedPortsFromContainer(c, l)
if err != nil {
return nil, err
}
}
if len(ports) > 0 {
sortPorts(ports)
model = append(model, Container{ports, strings.TrimPrefix(c.Names[0], "/"), c.Image})
}
}
return model, nil
}
// getAllWantedPortsFromContainer extracts the publicly shared ports of a container.
// It returns a Port, which contains the publicly mounted port, the private container port, the name if provided
// via the RECEPTIONIST label, and the default path that requests should be routed.
func getAllWantedPortsFromContainer(c types.Container, l string) ([]*Port, error) {
//TODO: get the label string out of here, Parse the label value at startup and use that to hold the parsed data
// that can be injected into the Port when required.
var allPorts []*Port
for _, p := range c.Ports {
if p.PublicPort != 0 {
port := &Port{
PublicPort: p.PublicPort,
PrivatePort: p.PrivatePort,
Path: "/",
}
// Parse the RECEPTIONIST label add name and path to port if provided
err := populatePortMetaData(port, l)
if err != nil {
return nil, fmt.Errorf("unable to populate port name: %w", err)
}
allPorts = append(allPorts, port)
}
}
return allPorts, nil
}
type LabelElement struct {
Name string
Port string
Path string
}
func extractElementsFromLabel(s string) ([]LabelElement, error) {
regex := regexp.MustCompile(`(?P<Name>[^:]*):(?P<Port>[^:]+):?(?P<Path>[^:]*)`)
var elements []LabelElement
ports := strings.Split(s, ",")
for _, p := range ports {
els := regex.FindStringSubmatch(p)
if len(els) > 0 {
el := LabelElement{}
el.Name = els[1]
el.Port = els[2]
el.Path = els[3]
if el.Path == "" {
el.Path = "/"
}
if !strings.HasPrefix(el.Path, "/") {
el.Path = fmt.Sprintf("/%v", el.Path)
}
elements = append(elements, el)
}
}
return elements, nil
}
func populatePortMetaData(p *Port, label string) error {
labelElements, err := extractElementsFromLabel(label)
if err != nil {
return err
}
for _, e := range labelElements {
portUint, err := strconv.ParseUint(e.Port, 10, 16)
if err != nil {
return fmt.Errorf("unable to parse port number from string: %w", err)
}
if portUint == uint64(p.PrivatePort) {
p.Name = e.Name
p.Path = e.Path
}
}
return nil
}
func sortPorts(ports []*Port) {
sort.Slice(ports, func(i, j int) bool {
return ports[i].PublicPort < ports[j].PublicPort
})
}
func sortContainers(cs []Container) {
sort.Slice(cs, func(i, j int) bool {
return cs[i].Name < cs[j].Name
})
}