201 lines
5.8 KiB
Go
201 lines
5.8 KiB
Go
|
package dockerutils
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"time"
|
||
|
|
||
|
"github.com/docker/docker/api/types"
|
||
|
"github.com/docker/docker/api/types/container"
|
||
|
"github.com/docker/docker/api/types/network"
|
||
|
"github.com/docker/go-connections/nat"
|
||
|
)
|
||
|
|
||
|
type Builder struct {
|
||
|
client *Client
|
||
|
containerConfig *container.Config
|
||
|
containerName string
|
||
|
hostConfig *container.HostConfig
|
||
|
networkConfig *network.NetworkingConfig
|
||
|
networks map[string][]string
|
||
|
ports []string
|
||
|
pull bool
|
||
|
waitForHealthy bool
|
||
|
}
|
||
|
|
||
|
// AddEnv to the container
|
||
|
func (builder *Builder) AddEnv(key string, value string) *Builder {
|
||
|
builder.containerConfig.Env = append(builder.containerConfig.Env, fmt.Sprintf("%v=%v", key, value))
|
||
|
return builder
|
||
|
}
|
||
|
|
||
|
// AddLabel to the container
|
||
|
func (builder *Builder) AddLabel(key string, value string) *Builder {
|
||
|
builder.containerConfig.Labels[key] = value
|
||
|
return builder
|
||
|
}
|
||
|
|
||
|
// Env set environment variables to the container
|
||
|
func (builder *Builder) Env(env map[string]string) *Builder {
|
||
|
builder.containerConfig.Labels = make(map[string]string)
|
||
|
for key, value := range env {
|
||
|
builder.containerConfig.Labels[key] = value
|
||
|
}
|
||
|
return builder
|
||
|
}
|
||
|
|
||
|
// Labels set labels to the container
|
||
|
func (builder *Builder) Labels(labels map[string]string) *Builder {
|
||
|
builder.containerConfig.Labels = labels
|
||
|
return builder
|
||
|
}
|
||
|
|
||
|
// Memory defines a memory limit for the container
|
||
|
func (builder *Builder) Memory(limit int64) *Builder {
|
||
|
builder.hostConfig.Memory = limit
|
||
|
return builder
|
||
|
}
|
||
|
|
||
|
// Mount a source volume or hostpath into the filesystem of the container
|
||
|
func (builder *Builder) Mount(source string, dest string) *Builder {
|
||
|
builder.hostConfig.Binds = append(builder.hostConfig.Binds, fmt.Sprintf("%v:%v", source, dest))
|
||
|
return builder
|
||
|
}
|
||
|
|
||
|
// Mounts a set of source volumes or hostpath into the filesystem of the
|
||
|
// container
|
||
|
func (builder *Builder) Mounts(mounts map[string]string) *Builder {
|
||
|
for source, dest := range mounts {
|
||
|
builder.Mount(source, dest)
|
||
|
}
|
||
|
return builder
|
||
|
}
|
||
|
|
||
|
// Port defines a port forwarding from the host machine to the container
|
||
|
// Examples:
|
||
|
// - 8080:8080
|
||
|
// - 10.6.231.10:8080:8080
|
||
|
// - 10.6.231.10:8080:8080/tcp
|
||
|
func (builder *Builder) Port(port string) *Builder {
|
||
|
builder.ports = append(builder.ports, port)
|
||
|
return builder
|
||
|
}
|
||
|
|
||
|
// Ports defines a set port forwarding rules from the host machine to the
|
||
|
// container
|
||
|
// Examples:
|
||
|
// - 8080:8080
|
||
|
// - 10.6.231.10:8080:8080
|
||
|
// - 10.6.231.10:8080:8080/tcp
|
||
|
func (builder *Builder) Ports(ports []string) *Builder {
|
||
|
builder.ports = ports
|
||
|
return builder
|
||
|
}
|
||
|
|
||
|
// Pull the image if absent
|
||
|
func (builder *Builder) Pull() *Builder {
|
||
|
builder.pull = true
|
||
|
return builder
|
||
|
}
|
||
|
|
||
|
// Start the container
|
||
|
func (builder *Builder) Start(ctx context.Context) (string, error) {
|
||
|
|
||
|
// Pull container image if absent
|
||
|
if builder.pull {
|
||
|
err := builder.client.PullQuiet(ctx, builder.containerConfig.Image)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check if ports are available
|
||
|
exposedPorts, portBindings, err := nat.ParsePortSpecs(builder.ports)
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("Failed to parse ports %v: %v", builder.ports, err)
|
||
|
}
|
||
|
|
||
|
if len(portBindings) > 0 {
|
||
|
builder.containerConfig.ExposedPorts = exposedPorts
|
||
|
builder.hostConfig.PortBindings = portBindings
|
||
|
}
|
||
|
|
||
|
// add endpoint settings to existing networks
|
||
|
networkExist := make(map[string]bool, 0)
|
||
|
for networkName := range builder.networks {
|
||
|
networkExist[networkName] = false
|
||
|
}
|
||
|
|
||
|
networks, err := builder.client.NetworkList(ctx, types.NetworkListOptions{})
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("Failed to list networks: %v", err)
|
||
|
}
|
||
|
|
||
|
for _, nw := range networks {
|
||
|
if aliases, present := builder.networks[nw.Name]; present {
|
||
|
networkExist[nw.Name] = true
|
||
|
builder.networkConfig.EndpointsConfig[nw.Name] = &network.EndpointSettings{
|
||
|
Aliases: aliases,
|
||
|
NetworkID: nw.ID,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for nw, found := range networkExist {
|
||
|
if !found {
|
||
|
return "", fmt.Errorf("Failed to add endpoint settings for network %v. It does not exist", nw)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// create container
|
||
|
resp, err := builder.client.ContainerCreate(ctx, builder.containerConfig, builder.hostConfig, builder.networkConfig, builder.containerName)
|
||
|
if err != nil {
|
||
|
return "", fmt.Errorf("Failed to create container %v: %v", builder.containerName, err)
|
||
|
}
|
||
|
|
||
|
// start container
|
||
|
err = builder.client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
|
||
|
if err != nil {
|
||
|
stopError := builder.client.ContainerStopByIDs(ctx, time.Second, resp.ID)
|
||
|
if stopError != nil {
|
||
|
return "", fmt.Errorf("Failed to start container %v: %v\nUnable to remove container %v. Manual cleanup necessary", builder.containerName, err, builder.containerName)
|
||
|
}
|
||
|
return "", fmt.Errorf("Failed to start container %v: %v", builder.containerName, err)
|
||
|
}
|
||
|
|
||
|
// wait for healthy
|
||
|
if builder.waitForHealthy {
|
||
|
|
||
|
watcher := builder.client.GetWatcher()
|
||
|
errorChannel := make(chan error, 1)
|
||
|
doneChannel := make(chan struct{})
|
||
|
err = watcher.AddListener(resp.ID, errorChannel, doneChannel)
|
||
|
if err != nil {
|
||
|
containerRemoveError := builder.client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})
|
||
|
if containerRemoveError != nil {
|
||
|
return "", fmt.Errorf("Failed while watching status for container %v: %v\nUnable to remove container %v: %v", resp.ID, err, resp.ID, containerRemoveError)
|
||
|
}
|
||
|
return "", fmt.Errorf("Failed while watching status for container %v: %v", resp.ID, err)
|
||
|
}
|
||
|
|
||
|
select {
|
||
|
case err := <-errorChannel:
|
||
|
if err != nil {
|
||
|
containerRemoveError := builder.client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})
|
||
|
if containerRemoveError != nil {
|
||
|
return "", fmt.Errorf("Unable to remove container %v: %v", resp.ID, containerRemoveError)
|
||
|
}
|
||
|
return "", err
|
||
|
}
|
||
|
case <-doneChannel:
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return resp.ID, nil
|
||
|
}
|
||
|
|
||
|
func (builder *Builder) WaitForHealthy() *Builder {
|
||
|
builder.waitForHealthy = true
|
||
|
return builder
|
||
|
}
|