Skip to content

Commit eaf6720

Browse files
committed
Add container CreateOpt for setting port forwards
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
1 parent 524ee57 commit eaf6720

File tree

2 files changed

+106
-2
lines changed

2 files changed

+106
-2
lines changed

container/create.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"io/ioutil"
7+
"io"
88
"net/http"
99
"net/url"
1010

@@ -34,6 +34,56 @@ func WithCreatePlatform(platform string) CreateOption {
3434
}
3535
}
3636

37+
// WithCreateHostConfigOpt allows you to set a function that modifies the
38+
// HostConfig of the container being created.
39+
func WithCreateHostConfigOpt(f func(*containerapi.HostConfig)) CreateOption {
40+
return func(cfg *CreateConfig) {
41+
f(&cfg.Spec.HostConfig)
42+
}
43+
}
44+
45+
// WithCreateNetworkConfigOpt allows you to set a function that modifies the
46+
// NetworkingConfig of the container being created.
47+
func WithCreateNetworkConfigOpt(f func(*containerapi.NetworkingConfig)) CreateOption {
48+
return func(cfg *CreateConfig) {
49+
f(&cfg.Spec.NetworkConfig)
50+
}
51+
}
52+
53+
// WithCreateConfigOpt allows you to set a function that modifies the
54+
// Config of the container being created.
55+
func WithCreateConfigOpt(f func(*containerapi.Config)) CreateOption {
56+
return func(cfg *CreateConfig) {
57+
f(&cfg.Spec.Config)
58+
}
59+
}
60+
61+
// WithCreatePortForwarding adds the specified port forward to the container's
62+
// configuration.
63+
// In this case the 'port' is the container port to forward to the host.
64+
func WithCreatePortForwarding(proto string, port int, hostBindings ...containerapi.PortBinding) CreateOption {
65+
return func(cfg *CreateConfig) {
66+
portSpec := fmt.Sprintf("%d/%s", port, proto)
67+
68+
WithCreateConfigOpt(func(c *containerapi.Config) {
69+
if c.ExposedPorts == nil {
70+
c.ExposedPorts = map[string]struct{}{}
71+
}
72+
c.ExposedPorts[portSpec] = struct{}{}
73+
})(cfg)
74+
75+
WithCreateHostConfigOpt(func(hc *containerapi.HostConfig) {
76+
bindings := hc.PortBindings
77+
if bindings == nil {
78+
bindings = containerapi.PortMap{}
79+
}
80+
81+
bindings[fmt.Sprintf("%d/%s", port, proto)] = hostBindings
82+
hc.PortBindings = bindings
83+
})(cfg)
84+
}
85+
}
86+
3787
// Create creates a container using the provided image.
3888
func (s *Service) Create(ctx context.Context, img string, opts ...CreateOption) (*Container, error) {
3989
c := CreateConfig{
@@ -78,7 +128,7 @@ func (s *Service) Create(ctx context.Context, img string, opts ...CreateOption)
78128
}
79129
defer resp.Body.Close()
80130

81-
data, err := ioutil.ReadAll(resp.Body)
131+
data, err := io.ReadAll(resp.Body)
82132
if err != nil {
83133
return nil, errdefs.Wrap(err, "error reading response body")
84134
}

container/create_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
"strings"
66
"testing"
77

8+
"github.com/cpuguy83/go-docker/container/containerapi"
89
"github.com/cpuguy83/go-docker/errdefs"
910
"github.com/cpuguy83/go-docker/testutils"
1011
"gotest.tools/v3/assert"
12+
"gotest.tools/v3/assert/cmp"
1113
)
1214

1315
func TestCreate(t *testing.T) {
@@ -36,4 +38,56 @@ func TestCreate(t *testing.T) {
3638
inspect, err := c.Inspect(ctx)
3739
assert.NilError(t, err)
3840
assert.Equal(t, name, strings.TrimPrefix(inspect.Name, "/"))
41+
42+
t.Run("port bindings", func(t *testing.T) {
43+
t.Parallel()
44+
45+
c, err := s.Create(ctx, "busybox:latest",
46+
WithCreateName(name),
47+
WithCreatePortForwarding("tcp", 80),
48+
WithCreatePortForwarding("udp", 81),
49+
WithCreatePortForwarding("udp", 82, containerapi.PortBinding{HostIP: "127.0.0.1"}),
50+
WithCreateCmd("top"),
51+
)
52+
assert.NilError(t, err)
53+
defer func() {
54+
assert.Check(t, s.Remove(ctx, c.ID(), WithRemoveForce))
55+
}()
56+
57+
assert.Assert(t, c.ID() != "")
58+
assert.NilError(t, c.Start(ctx))
59+
60+
inspect, err := c.Inspect(ctx)
61+
assert.NilError(t, err)
62+
assert.Check(t, cmp.Equal(inspect.Config.ExposedPorts["80/tcp"], struct{}{}))
63+
64+
port80, ok := inspect.NetworkSettings.Ports["80/tcp"]
65+
assert.Check(t, ok)
66+
port81, ok := inspect.NetworkSettings.Ports["81/udp"]
67+
assert.Check(t, ok)
68+
69+
port82, ok := inspect.NetworkSettings.Ports["82/udp"]
70+
assert.Check(t, ok)
71+
assert.Check(t, cmp.Equal(port82[0].HostIP, "127.0.0.1"))
72+
73+
// Depending on the version of docker (and ipv6 support), there maybe be one
74+
// or two bindings.
75+
var hostPort string
76+
for _, bind := range port80 {
77+
if bind.HostPort != "" {
78+
hostPort = bind.HostPort
79+
break
80+
}
81+
}
82+
assert.Check(t, hostPort != "", "expected a host port binding for 80/tcp")
83+
84+
hostPort = ""
85+
for _, bind := range port81 {
86+
if bind.HostPort != "" {
87+
hostPort = bind.HostPort
88+
break
89+
}
90+
}
91+
assert.Check(t, hostPort != "", "expected a host port binding for 81/udp")
92+
})
3993
}

0 commit comments

Comments
 (0)