From 8e5d154408d0fcb2b0ea293e8aedfc2403438eaa Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Nov 2021 11:00:20 +0100 Subject: [PATCH 01/10] Add label icinga=testing for created networks So we can easily remove them using filters. --- it.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/it.go b/it.go index 81a6b0b..bb5fae0 100644 --- a/it.go +++ b/it.go @@ -70,7 +70,7 @@ func NewIT() *IT { }) } - if n, err := it.dockerClient.NetworkCreate(context.Background(), it.prefix, types.NetworkCreate{}); err != nil { + if n, err := it.dockerClient.NetworkCreate(context.Background(), it.prefix, types.NetworkCreate{Labels: map[string]string{"icinga": "testing"}}); err != nil { it.logger.Fatal("failed to create docker network", zap.String("network-name", it.prefix), zap.Error(err)) } else { it.logger.Debug("created docker network", zap.String("network-name", it.prefix), zap.String("network-id", n.ID)) From ac6af5c619ddcdc69b7ee78673381f175a1ce0eb Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Nov 2021 14:07:14 +0100 Subject: [PATCH 02/10] Log why Redis failed to start --- internal/services/redis/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/redis/docker.go b/internal/services/redis/docker.go index 1382dda..73f8074 100644 --- a/internal/services/redis/docker.go +++ b/internal/services/redis/docker.go @@ -82,7 +82,7 @@ func (r *dockerCreator) CreateRedisServer() services.RedisServerBase { err = r.dockerClient.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{}) if err != nil { - logger.Fatal("failed to start container") + logger.Fatal("failed to start container", zap.Error(err)) } logger.Debug("started container") From 456d63ffc0f6c835d82cc4879e13731546f07d31 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Nov 2021 14:08:50 +0100 Subject: [PATCH 03/10] Log why Icinga DB failed to create --- internal/services/icingadb/docker_binary.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/icingadb/docker_binary.go b/internal/services/icingadb/docker_binary.go index 09aba6a..630a6ed 100644 --- a/internal/services/icingadb/docker_binary.go +++ b/internal/services/icingadb/docker_binary.go @@ -118,7 +118,7 @@ func (i *dockerBinaryCreator) CreateIcingaDb( }, }, nil, containerName) if err != nil { - inst.logger.Fatal("failed to create icingadb container") + inst.logger.Fatal("failed to create icingadb container", zap.Error(err)) } inst.containerId = cont.ID inst.logger = inst.logger.With(zap.String("container-id", cont.ID)) From ba788d204ba302ca9339f82b62a314e37034bd42 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Nov 2021 14:09:02 +0100 Subject: [PATCH 04/10] Log why Icinga DB failed to start --- internal/services/icingadb/docker_binary.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/icingadb/docker_binary.go b/internal/services/icingadb/docker_binary.go index 630a6ed..b3f0d57 100644 --- a/internal/services/icingadb/docker_binary.go +++ b/internal/services/icingadb/docker_binary.go @@ -136,7 +136,7 @@ func (i *dockerBinaryCreator) CreateIcingaDb( err = i.dockerClient.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{}) if err != nil { - inst.logger.Fatal("failed to start container") + inst.logger.Fatal("failed to start container", zap.Error(err)) } inst.logger.Debug("started container") From 2ae8506a5691bd121c183f2cf777491232d7a6b5 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Nov 2021 14:10:36 +0100 Subject: [PATCH 05/10] Add port binding utility --- utils/port.go | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 utils/port.go diff --git a/utils/port.go b/utils/port.go new file mode 100644 index 0000000..c944732 --- /dev/null +++ b/utils/port.go @@ -0,0 +1,117 @@ +package utils + +import ( + "context" + "fmt" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/pkg/errors" + "net" + "strconv" +) + +type PortDecision struct { + dockerHost string + exposed string + port string + remote bool +} + +func NewPortDecision(c *client.Client, port string) *PortDecision { + url, err := client.ParseHostURL(c.DaemonHost()) + if err != nil { + panic(err) + } + p := &PortDecision{port: port} + if url.Scheme != "unix" { + portNumber, err := GetFreePort() + if err != nil { + panic(err) + } + p.dockerHost = url.Hostname() + p.exposed = strconv.Itoa(portNumber) + p.remote = true + } else { + p.exposed = port + } + + return p +} + +func (p *PortDecision) Address(ctx context.Context, c *client.Client, id string) string { + if p.remote { + return p.dockerHost + } + + return MustString(DockerContainerAddress(ctx, c, id)) +} + +func (p *PortDecision) Binding(ctx context.Context, c *client.Client, id string) (*PortBinding, error) { + var port string + host := p.Address(ctx, c, id) + if p.remote { + r, err := c.ContainerInspect(context.Background(), id) + if err != nil { + return nil, errors.Wrap(err, "failed to inspect container") + } + + defaultPort, err := nat.NewPort(nat.SplitProtoPort(p.port)) + if err != nil { + return nil, errors.Wrap(err, "can't parse default port") + } + + p, ok := r.NetworkSettings.Ports[defaultPort] + if !ok { + return nil, errors.New(fmt.Sprintf("default port %s not exposed", defaultPort)) + } + port = p[0].HostPort + } + + return &PortBinding{ + Host: host, + Port: port, + }, nil +} + +func (p *PortDecision) Map() nat.PortMap { + if p.remote { + return nat.PortMap{ + nat.Port(fmt.Sprintf("%s/tcp", p.port)): []nat.PortBinding{ + { + HostIP: "0.0.0.0", + HostPort: p.exposed, + }, + }, + } + } + + return nil +} + +func (p *PortDecision) Port() string { + return p.exposed +} + +func (p *PortDecision) Remote() bool { + return p.remote +} + +func GetFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", ":0") + if err != nil { + return 0, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + + return l.Addr().(*net.TCPAddr).Port, nil +} + +type PortBinding struct { + Host string + Port string +} From 43d342656fd0f8c3d9dac056d28555645f534843 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Nov 2021 14:11:00 +0100 Subject: [PATCH 06/10] Forward Redis port when Docker host is remote --- internal/services/redis/docker.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/services/redis/docker.go b/internal/services/redis/docker.go index 73f8074..ec6f248 100644 --- a/internal/services/redis/docker.go +++ b/internal/services/redis/docker.go @@ -15,6 +15,8 @@ import ( "time" ) +const PORT = "6379" + type dockerCreator struct { logger *zap.Logger dockerClient *client.Client @@ -55,9 +57,11 @@ func (r *dockerCreator) CreateRedisServer() services.RedisServerBase { panic(err) } + port := utils.NewPortDecision(r.dockerClient, PORT) + cont, err := r.dockerClient.ContainerCreate(context.Background(), &container.Config{ Image: dockerImage, - }, nil, &network.NetworkingConfig{ + }, &container.HostConfig{PortBindings: port.Map()}, &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ networkName: { NetworkID: r.dockerNetworkId, @@ -88,8 +92,8 @@ func (r *dockerCreator) CreateRedisServer() services.RedisServerBase { s := &dockerServer{ info: info{ - host: utils.MustString(utils.DockerContainerAddress(context.Background(), r.dockerClient, cont.ID)), - port: "6379", + host: port.Address(context.Background(), r.dockerClient, cont.ID), + port: port.Port(), }, redisDocker: r, logger: logger, From 6d3cdb314f40329f40784c5ad892d78f0095a15f Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Nov 2021 14:11:36 +0100 Subject: [PATCH 07/10] Forward MySQL port when Docker host is remote --- internal/services/mysql/docker.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/internal/services/mysql/docker.go b/internal/services/mysql/docker.go index e49f3fc..5b1db12 100644 --- a/internal/services/mysql/docker.go +++ b/internal/services/mysql/docker.go @@ -11,6 +11,8 @@ import ( "time" ) +const PORT = "3306" + type dockerCreator struct { *rootConnection logger *zap.Logger @@ -38,13 +40,14 @@ func NewDockerCreator(logger *zap.Logger, dockerClient *client.Client, container panic(err) } + port := utils.NewPortDecision(dockerClient, PORT) + rootPassword := utils.RandomString(16) cont, err := dockerClient.ContainerCreate(context.Background(), &container.Config{ - ExposedPorts: nil, - Env: []string{"MYSQL_ROOT_PASSWORD=" + rootPassword}, - Cmd: nil, - Image: dockerImage, - }, nil, &network.NetworkingConfig{ + Env: []string{"MYSQL_ROOT_PASSWORD=" + rootPassword}, + Cmd: nil, + Image: dockerImage, + }, &container.HostConfig{PortBindings: port.Map()}, &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ networkName: { Aliases: []string{"mysql"}, @@ -73,14 +76,14 @@ func NewDockerCreator(logger *zap.Logger, dockerClient *client.Client, container } logger.Debug("started mysql container") - containerAddress := utils.MustString(utils.DockerContainerAddress(context.Background(), dockerClient, cont.ID)) - d := &dockerCreator{ - rootConnection: newRootConnection(containerAddress, "3306", "root", rootPassword), - logger: logger, - client: dockerClient, - containerId: cont.ID, - containerName: containerName, + rootConnection: newRootConnection( + port.Address(context.Background(), dockerClient, cont.ID), port.Port(), "root", rootPassword, + ), + logger: logger, + client: dockerClient, + containerId: cont.ID, + containerName: containerName, } for attempt := 1; ; attempt++ { From 7e764b3cee8befcb24c63a682927d8ba7b5fcadf Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Nov 2021 14:12:06 +0100 Subject: [PATCH 08/10] Forward Icinga 2 port when Docker host is remote --- internal/services/icinga2/docker.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/internal/services/icinga2/docker.go b/internal/services/icinga2/docker.go index 761c792..118b2bc 100644 --- a/internal/services/icinga2/docker.go +++ b/internal/services/icinga2/docker.go @@ -16,6 +16,8 @@ import ( "time" ) +const PORT = "5665" + type dockerCreator struct { logger *zap.Logger dockerClient *client.Client @@ -59,11 +61,13 @@ func (i *dockerCreator) CreateIcinga2(name string) services.Icinga2Base { panic(err) } + port := utils.NewPortDecision(i.dockerClient, PORT) + cont, err := i.dockerClient.ContainerCreate(context.Background(), &container.Config{ Image: dockerImage, Hostname: name, Env: []string{"ICINGA_MASTER=1"}, - }, nil, &network.NetworkingConfig{ + }, &container.HostConfig{PublishAllPorts: port.Remote()}, &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ networkName: { NetworkID: i.dockerNetworkId, @@ -90,10 +94,15 @@ func (i *dockerCreator) CreateIcinga2(name string) services.Icinga2Base { } logger.Debug("started container") + binding, err := port.Binding(context.Background(), i.dockerClient, cont.ID) + if err != nil { + logger.Fatal("can't create port binding", zap.Error(err)) + } + n := &dockerInstance{ info: info{ - host: utils.MustString(utils.DockerContainerAddress(context.Background(), i.dockerClient, cont.ID)), - port: "5665", + host: binding.Host, + port: binding.Port, }, icinga2Docker: i, logger: logger, From 97030c5b81ba0c0aac44c3e258f2382c543a2dbf Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Nov 2021 16:33:31 +0100 Subject: [PATCH 09/10] Update Go modules --- go.mod | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8334ef3..69ce78a 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,13 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/containerd/containerd v1.5.5 // indirect github.com/docker/docker v20.10.8+incompatible - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-connections v0.4.0 github.com/go-redis/redis/v8 v8.11.3 github.com/go-sql-driver/mysql v1.6.0 github.com/gorilla/mux v1.8.0 // indirect github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.7.0 go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.7.0 // indirect From 25c34e4e3f05664d09b59bc5a173f4af534b3ec9 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Tue, 30 Nov 2021 10:12:04 +0100 Subject: [PATCH 10/10] Fix missing ports when Docker host is local --- utils/port.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/port.go b/utils/port.go index c944732..c38e04f 100644 --- a/utils/port.go +++ b/utils/port.go @@ -65,6 +65,8 @@ func (p *PortDecision) Binding(ctx context.Context, c *client.Client, id string) return nil, errors.New(fmt.Sprintf("default port %s not exposed", defaultPort)) } port = p[0].HostPort + } else { + port = p.port } return &PortBinding{