Skip to content

Commit 2ae8506

Browse files
committed
Add port binding utility
1 parent ba788d2 commit 2ae8506

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed

utils/port.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package utils
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/docker/docker/client"
7+
"github.com/docker/go-connections/nat"
8+
"github.com/pkg/errors"
9+
"net"
10+
"strconv"
11+
)
12+
13+
type PortDecision struct {
14+
dockerHost string
15+
exposed string
16+
port string
17+
remote bool
18+
}
19+
20+
func NewPortDecision(c *client.Client, port string) *PortDecision {
21+
url, err := client.ParseHostURL(c.DaemonHost())
22+
if err != nil {
23+
panic(err)
24+
}
25+
p := &PortDecision{port: port}
26+
if url.Scheme != "unix" {
27+
portNumber, err := GetFreePort()
28+
if err != nil {
29+
panic(err)
30+
}
31+
p.dockerHost = url.Hostname()
32+
p.exposed = strconv.Itoa(portNumber)
33+
p.remote = true
34+
} else {
35+
p.exposed = port
36+
}
37+
38+
return p
39+
}
40+
41+
func (p *PortDecision) Address(ctx context.Context, c *client.Client, id string) string {
42+
if p.remote {
43+
return p.dockerHost
44+
}
45+
46+
return MustString(DockerContainerAddress(ctx, c, id))
47+
}
48+
49+
func (p *PortDecision) Binding(ctx context.Context, c *client.Client, id string) (*PortBinding, error) {
50+
var port string
51+
host := p.Address(ctx, c, id)
52+
if p.remote {
53+
r, err := c.ContainerInspect(context.Background(), id)
54+
if err != nil {
55+
return nil, errors.Wrap(err, "failed to inspect container")
56+
}
57+
58+
defaultPort, err := nat.NewPort(nat.SplitProtoPort(p.port))
59+
if err != nil {
60+
return nil, errors.Wrap(err, "can't parse default port")
61+
}
62+
63+
p, ok := r.NetworkSettings.Ports[defaultPort]
64+
if !ok {
65+
return nil, errors.New(fmt.Sprintf("default port %s not exposed", defaultPort))
66+
}
67+
port = p[0].HostPort
68+
}
69+
70+
return &PortBinding{
71+
Host: host,
72+
Port: port,
73+
}, nil
74+
}
75+
76+
func (p *PortDecision) Map() nat.PortMap {
77+
if p.remote {
78+
return nat.PortMap{
79+
nat.Port(fmt.Sprintf("%s/tcp", p.port)): []nat.PortBinding{
80+
{
81+
HostIP: "0.0.0.0",
82+
HostPort: p.exposed,
83+
},
84+
},
85+
}
86+
}
87+
88+
return nil
89+
}
90+
91+
func (p *PortDecision) Port() string {
92+
return p.exposed
93+
}
94+
95+
func (p *PortDecision) Remote() bool {
96+
return p.remote
97+
}
98+
99+
func GetFreePort() (int, error) {
100+
addr, err := net.ResolveTCPAddr("tcp", ":0")
101+
if err != nil {
102+
return 0, err
103+
}
104+
105+
l, err := net.ListenTCP("tcp", addr)
106+
if err != nil {
107+
return 0, err
108+
}
109+
defer l.Close()
110+
111+
return l.Addr().(*net.TCPAddr).Port, nil
112+
}
113+
114+
type PortBinding struct {
115+
Host string
116+
Port string
117+
}

0 commit comments

Comments
 (0)