Intial Commit
This commit is contained in:
commit
661f4e86a2
35
.drone.yml
Normal file
35
.drone.yml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: amd64
|
||||||
|
steps:
|
||||||
|
- name: unit test
|
||||||
|
image: docker.io/volkerraschek/build-image:latest
|
||||||
|
commands:
|
||||||
|
- make --jobs=$(nproc) test/coverage
|
||||||
|
volumes:
|
||||||
|
- name: dockerAPI
|
||||||
|
path: /var/run/docker.sock
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
- tag
|
||||||
|
- name: notify
|
||||||
|
image: drillster/drone-email
|
||||||
|
environment:
|
||||||
|
PLUGIN_HOST:
|
||||||
|
from_secret: smtp_host
|
||||||
|
PLUGIN_USERNAME:
|
||||||
|
from_secret: smtp_username
|
||||||
|
PLUGIN_PASSWORD:
|
||||||
|
from_secret: smtp_password
|
||||||
|
PLUGIN_FROM:
|
||||||
|
from_secret: smtp_mail_address
|
||||||
|
when:
|
||||||
|
status:
|
||||||
|
- changed
|
||||||
|
- failure
|
||||||
|
volumes:
|
||||||
|
- name: dockerAPI
|
||||||
|
host:
|
||||||
|
path: /var/run/docker.sock
|
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
Makefile eol=lf
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# directories
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# coverage files
|
||||||
|
coverage*
|
13
LICENSE
Normal file
13
LICENSE
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Copyright 2020 Markus Pesch
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
52
Makefile
Normal file
52
Makefile
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# GOPROXY settings
|
||||||
|
# If no GOPROXY environment variable available, the pre-defined GOPROXY from go
|
||||||
|
# env to download and validate go modules is used. Exclude downloading and
|
||||||
|
# validation of all private modules which are pre-defined in GOPRIVATE. If no
|
||||||
|
# GOPRIVATE variable defined, the variable of go env is used.
|
||||||
|
GOPROXY?=$(shell go env GOPROXY)
|
||||||
|
GOPRIVATE?=$(shell go env GOPRIVATE)
|
||||||
|
|
||||||
|
# CONTAINER_RUNTIME
|
||||||
|
# The CONTAINER_RUNTIME variable will be used to specified the path to a
|
||||||
|
# container runtime. This is needed to start and run a container images.
|
||||||
|
CONTAINER_RUNTIME?=$(shell which docker)
|
||||||
|
CONTAINER_IMAGE_VERSION?=latest
|
||||||
|
|
||||||
|
# TEST
|
||||||
|
# ==============================================================================
|
||||||
|
PHONY+=test/unit
|
||||||
|
test/unit:
|
||||||
|
go test -v -race -coverprofile=coverage.txt -covermode=atomic -timeout 600s -count=1 ./...
|
||||||
|
|
||||||
|
test/coverage: test/unit
|
||||||
|
go tool cover -html=coverage.txt
|
||||||
|
|
||||||
|
# CONTAINER STEPS - TEST
|
||||||
|
# ==============================================================================
|
||||||
|
PHONY+=container-run/test/unit
|
||||||
|
container-run/test/unit:
|
||||||
|
$(MAKE) container-run COMMAND=${@:container-run/%=%}
|
||||||
|
|
||||||
|
PHONY+=container-run/test/coverage
|
||||||
|
container-run/test/coverage:
|
||||||
|
$(MAKE) container-run COMMAND=${@:container-run/%=%}
|
||||||
|
|
||||||
|
# GENERAL CONTAINER COMMAND
|
||||||
|
# ==============================================================================
|
||||||
|
PHONY+=container-run
|
||||||
|
container-run:
|
||||||
|
${CONTAINER_RUNTIME} run \
|
||||||
|
--rm \
|
||||||
|
--volume ${PWD}:/workspace \
|
||||||
|
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
docker.io/volkerraschek/build-image:latest \
|
||||||
|
make ${COMMAND} \
|
||||||
|
GOPROXY=${GOPROXY} \
|
||||||
|
GOPRIVATE=${GOPRIVATE} \
|
||||||
|
VERSION=${VERSION:v%=%}
|
||||||
|
|
||||||
|
# PHONY
|
||||||
|
# ==============================================================================
|
||||||
|
# Declare the contents of the PHONY variable as phony. We keep that information
|
||||||
|
# in a variable so we can use it in if_changed.
|
||||||
|
.PHONY: ${PHONY}
|
74
README.md
Normal file
74
README.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# dockerutils
|
||||||
|
|
||||||
|
[![Build Status](https://drone.cryptic.systems/api/badges/volker.raschek/dockerutils/status.svg)](https://drone.cryptic.systems/volker.raschek/dockerutils)
|
||||||
|
|
||||||
|
dockerutils is a small library which extends the official docker library to
|
||||||
|
create and start images over a builder. Additionally the library provide
|
||||||
|
functions for easy removeing resources based on ids, names or labels which the
|
||||||
|
official library does not directly supports.
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
Install the library by the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get git.cryptic.systems/volker.raschek/dockerutils
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Example: Create and remove postgreSQL container
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "git.cryptic.systems/volker.raschek/dockerutils"
|
||||||
|
|
||||||
|
func noErr(err){
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main(){
|
||||||
|
dockerClient, err := dockerutils.New()
|
||||||
|
noErr(err)
|
||||||
|
|
||||||
|
postgresContainerID, err := dockerClient.NewBuilder("postgres:13-alpine").
|
||||||
|
Port(fmt.Sprintf("5432:5432/tcp", postgresHostPort)).
|
||||||
|
Pull().
|
||||||
|
AddEnv("PGTZ", "Europe/Berlin").
|
||||||
|
AddEnv("POSTGRES_PASSWORD", postgres).
|
||||||
|
AddEnv("TZ", "Europe/Berlin").
|
||||||
|
Mount("/etc/localtime", "/etc/localtime").
|
||||||
|
Start(context.Background())
|
||||||
|
noErr(err)
|
||||||
|
defer func(){dockerClient.ContainerRemoveByIDs(context.Background(), postgresContainerID)}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Create and remove container network
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.cryptic.systems/volker.raschek/dockerutils"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func noErr(err){
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main(){
|
||||||
|
dockerClient, err := dockerutils.New()
|
||||||
|
noErr(err)
|
||||||
|
|
||||||
|
containerNetwork, err := dockerClient.NetworkCreate(ctx, "my-network", tt.NetworkCreate{Labels: map[string]string{"key": "value"}})
|
||||||
|
noErr(err)
|
||||||
|
defer func(){dockerClient.NetworkRemove(context.Background(), containerNetwork.ID)}
|
||||||
|
}
|
||||||
|
```
|
236
builder.go
Normal file
236
builder.go
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder is a wrapper around the official docker API to start a container
|
||||||
|
// image.
|
||||||
|
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.Env = append(builder.containerConfig.Env, fmt.Sprintf("%v=%v", 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network add the container with aliasses to a specific network
|
||||||
|
func (builder *Builder) Network(networkName string, aliasses ...string) *Builder {
|
||||||
|
builder.networks[networkName] = aliasses
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network: portbinding Host->Container
|
||||||
|
exposedPorts, portBindings, err := nat.ParsePortSpecs(builder.ports)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unabel to parse ports: %w", err)
|
||||||
|
}
|
||||||
|
if len(portBindings) > 0 {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
builder.containerConfig.ExposedPorts = exposedPorts
|
||||||
|
builder.hostConfig.PortBindings = portBindings
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network: Add container to container networks
|
||||||
|
// Add the container to the first defined container network, if any one is
|
||||||
|
// defined. If no one is defined, the docker API will add the container to
|
||||||
|
// their default bridge docker0. The other networks will be added to the
|
||||||
|
// container after the container start.
|
||||||
|
var (
|
||||||
|
networkNames = make([]string, 0)
|
||||||
|
networks = make([]types.NetworkResource, 0)
|
||||||
|
)
|
||||||
|
if len(builder.networks) > 0 {
|
||||||
|
for networkName := range builder.networks {
|
||||||
|
networkNames = append(networkNames, networkName)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
networks, err = builder.client.NetworkListByNames(ctx, networkNames...)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
endpointSetting := &network.EndpointSettings{
|
||||||
|
NetworkID: networks[0].ID,
|
||||||
|
Aliases: builder.networks[networkNames[0]],
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.networkConfig.EndpointsConfig[networkNames[0]] = endpointSetting
|
||||||
|
|
||||||
|
networkNames = networkNames[1:]
|
||||||
|
networks = networks[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container: Create
|
||||||
|
resp, err := builder.client.ContainerCreate(
|
||||||
|
ctx,
|
||||||
|
builder.containerConfig,
|
||||||
|
builder.hostConfig,
|
||||||
|
builder.networkConfig,
|
||||||
|
builder.containerName,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to create container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = builder.client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
shutdownErr := builder.client.ContainerStopByIDs(ctx, 1*time.Second, resp.ID)
|
||||||
|
if shutdownErr != nil {
|
||||||
|
return "", fmt.Errorf("Unable to start container: %w\nUnable to remove container %s: %v\nManual cleanup necessary", err, resp.ID, shutdownErr)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("Unable to start container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network: Add more container networks
|
||||||
|
for i, networkName := range networkNames {
|
||||||
|
endpointSetting := &network.EndpointSettings{
|
||||||
|
NetworkID: networks[i].ID,
|
||||||
|
Aliases: builder.networks[networkName],
|
||||||
|
}
|
||||||
|
err := builder.client.NetworkConnect(ctx, networks[i].ID, resp.ID, endpointSetting)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to append container endpoint to network %v", networkName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if builder.waitForHealthy {
|
||||||
|
watcher := builder.client.GetWatcher()
|
||||||
|
errors := make(chan error, 1)
|
||||||
|
done := make(chan struct{})
|
||||||
|
err = watcher.AddListener(resp.ID, errors, done)
|
||||||
|
if err != nil {
|
||||||
|
containerRemoveError := builder.client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})
|
||||||
|
if containerRemoveError != nil {
|
||||||
|
return "", fmt.Errorf("error while watching for container status: %w - unable to remove container: %v", err, containerRemoveError)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("error while watching for container status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errors:
|
||||||
|
if err != nil {
|
||||||
|
containerRemoveError := builder.client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})
|
||||||
|
if containerRemoveError != nil {
|
||||||
|
return "", fmt.Errorf("%w - unable to remove container: %v", err, containerRemoveError)
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
case <-done:
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.ID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForHealthy set the option to wait during the start process until the
|
||||||
|
// container is healthy.
|
||||||
|
func (builder *Builder) WaitForHealthy() *Builder {
|
||||||
|
builder.waitForHealthy = true
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithName set the name of the container
|
||||||
|
func (builder *Builder) WithName(containerName string) *Builder {
|
||||||
|
builder.containerName = containerName
|
||||||
|
return builder
|
||||||
|
}
|
402
client.go
Normal file
402
client.go
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
package dockerutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/api/types/volume"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client from the docker API with additional functions
|
||||||
|
type Client struct {
|
||||||
|
*client.Client
|
||||||
|
watcher *Watcher
|
||||||
|
mutex *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close docker connection
|
||||||
|
func (client *Client) Close() error {
|
||||||
|
client.mutex.Lock()
|
||||||
|
defer client.mutex.Unlock()
|
||||||
|
if client.watcher != nil {
|
||||||
|
client.watcher.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerListByLabels returns only containers which match by given labels
|
||||||
|
func (client *Client) ContainerListByLabels(ctx context.Context, all bool, containerLabels map[string]string) ([]types.Container, error) {
|
||||||
|
filterArgs := filters.NewArgs()
|
||||||
|
for key, value := range containerLabels {
|
||||||
|
filterArgs.Add("label", fmt.Sprintf("%v=%v", key, value))
|
||||||
|
}
|
||||||
|
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
|
||||||
|
All: all,
|
||||||
|
Filters: filterArgs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if containers == nil {
|
||||||
|
return nil, fmt.Errorf("No containers found by given labels")
|
||||||
|
}
|
||||||
|
return containers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerListByNames returns only containers which match by given labels
|
||||||
|
func (client *Client) ContainerListByNames(ctx context.Context, all bool, containerNames ...string) ([]types.Container, error) {
|
||||||
|
filterArgs := filters.NewArgs()
|
||||||
|
for _, name := range containerNames {
|
||||||
|
filterArgs.Add("name", name)
|
||||||
|
}
|
||||||
|
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
|
||||||
|
All: all,
|
||||||
|
Filters: filterArgs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if containers == nil {
|
||||||
|
return nil, fmt.Errorf("No containers found by given names")
|
||||||
|
}
|
||||||
|
return containers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerRemoveByIDs deletes all containers which match by their container ids
|
||||||
|
func (client *Client) ContainerRemoveByIDs(ctx context.Context, containerIDs ...string) error {
|
||||||
|
for _, containerID := range containerIDs {
|
||||||
|
err := client.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{Force: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerRemoveByLabels deletes all containers which match by given labels
|
||||||
|
func (client *Client) ContainerRemoveByLabels(ctx context.Context, containerLabels map[string]string) error {
|
||||||
|
containers, err := client.ContainerListByLabels(ctx, true, containerLabels)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, container := range containers {
|
||||||
|
err := client.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{Force: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerRemoveByNames deletes all containers which match by their names
|
||||||
|
func (client *Client) ContainerRemoveByNames(ctx context.Context, containerNames ...string) error {
|
||||||
|
containers, err := client.ContainerListByNames(ctx, true, containerNames...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, container := range containers {
|
||||||
|
err := client.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{Force: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerStopByIDs deletes all containers which match by their container ids
|
||||||
|
func (client *Client) ContainerStopByIDs(ctx context.Context, timeout time.Duration, containerIDs ...string) error {
|
||||||
|
for _, containerID := range containerIDs {
|
||||||
|
err := client.ContainerStop(ctx, containerID, &timeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerStopByLabels shutdown containters which match by given labels
|
||||||
|
func (client *Client) ContainerStopByLabels(ctx context.Context, timeout time.Duration, containerLabels map[string]string) error {
|
||||||
|
containers, err := client.ContainerListByLabels(ctx, true, containerLabels)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, container := range containers {
|
||||||
|
err := client.ContainerStop(ctx, container.ID, &timeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerStopByNames shutdown containters matching by their names
|
||||||
|
func (client *Client) ContainerStopByNames(ctx context.Context, timeout time.Duration, containerNames ...string) error {
|
||||||
|
containers, err := client.ContainerListByNames(ctx, true, containerNames...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, container := range containers {
|
||||||
|
err := client.ContainerStop(ctx, container.ID, &timeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWatcher returns a watcher for container health states
|
||||||
|
func (client *Client) GetWatcher() *Watcher {
|
||||||
|
if client.watcher != nil {
|
||||||
|
return client.watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
client.mutex.Lock()
|
||||||
|
defer client.mutex.Unlock()
|
||||||
|
|
||||||
|
client.watcher = &Watcher{
|
||||||
|
client: client,
|
||||||
|
errorChannels: make(map[string]chan<- error),
|
||||||
|
doneChannels: make(map[string]chan<- struct{}),
|
||||||
|
errorMapper: make(map[string]ErrorMapper),
|
||||||
|
mutex: new(sync.RWMutex),
|
||||||
|
}
|
||||||
|
|
||||||
|
client.watcher.start()
|
||||||
|
return client.watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkListByLabels returns networks which match by given labels
|
||||||
|
func (client *Client) NetworkListByLabels(ctx context.Context, networkLabels map[string]string) ([]types.NetworkResource, error) {
|
||||||
|
args := filters.NewArgs()
|
||||||
|
for key, value := range networkLabels {
|
||||||
|
args.Add("label", fmt.Sprintf("%v=%v", key, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.NetworkList(ctx, types.NetworkListOptions{
|
||||||
|
Filters: args,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkListByNames returns networks which match by their names. If a
|
||||||
|
// network can not be found, the function returns an error
|
||||||
|
func (client *Client) NetworkListByNames(ctx context.Context, networkNames ...string) ([]types.NetworkResource, error) {
|
||||||
|
networks, err := client.NetworkList(ctx, types.NetworkListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
foundNetwork := make(map[string]bool, 0)
|
||||||
|
for _, networkName := range networkNames {
|
||||||
|
foundNetwork[networkName] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredNetworks := make([]types.NetworkResource, 0)
|
||||||
|
for _, networkName := range networkNames {
|
||||||
|
for _, network := range networks {
|
||||||
|
if network.Name == networkName {
|
||||||
|
filteredNetworks = append(filteredNetworks, network)
|
||||||
|
foundNetwork[networkName] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, networkName := range networkNames {
|
||||||
|
if !foundNetwork[networkName] {
|
||||||
|
return nil, fmt.Errorf("Network %v not found", networkName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredNetworks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkRemoveByLabels remove all networks which match by given labels
|
||||||
|
func (client *Client) NetworkRemoveByLabels(ctx context.Context, containerLabels map[string]string) error {
|
||||||
|
networks, err := client.NetworkListByLabels(ctx, containerLabels)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, network := range networks {
|
||||||
|
err := client.NetworkRemove(ctx, network.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkRemoveByNames remove all networks match by their names. If a
|
||||||
|
// network can not be found, the function returns an error
|
||||||
|
func (client *Client) NetworkRemoveByNames(ctx context.Context, networkNames ...string) error {
|
||||||
|
networks, err := client.NetworkListByNames(ctx, networkNames...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, network := range networks {
|
||||||
|
err := client.NetworkRemove(ctx, network.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkRemoveByIDs remove all networks match by their id
|
||||||
|
func (client *Client) NetworkRemoveByIDs(ctx context.Context, containerIDs ...string) error {
|
||||||
|
for _, containerID := range containerIDs {
|
||||||
|
err := client.NetworkRemove(ctx, containerID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder returns a new builder for containers
|
||||||
|
func (client *Client) NewBuilder(image string) *Builder {
|
||||||
|
return &Builder{
|
||||||
|
client: client,
|
||||||
|
containerConfig: &container.Config{
|
||||||
|
Image: image,
|
||||||
|
},
|
||||||
|
hostConfig: new(container.HostConfig),
|
||||||
|
networkConfig: &network.NetworkingConfig{
|
||||||
|
EndpointsConfig: make(map[string]*network.EndpointSettings, 0),
|
||||||
|
},
|
||||||
|
networks: make(map[string][]string, 0),
|
||||||
|
ports: make([]string, 0),
|
||||||
|
pull: false,
|
||||||
|
waitForHealthy: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull image
|
||||||
|
func (client *Client) Pull(ctx context.Context, image string, w io.Writer) error {
|
||||||
|
|
||||||
|
parts := strings.Split(image, "/")
|
||||||
|
switch len(parts) {
|
||||||
|
case 1:
|
||||||
|
image = fmt.Sprintf("docker.io/library/%v", parts[0])
|
||||||
|
case 2:
|
||||||
|
if strings.Compare(parts[0], "library") == 0 ||
|
||||||
|
strings.Compare(parts[0], "docker.io") == 0 {
|
||||||
|
image = fmt.Sprintf("docker.io/library/%v", parts[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readCloser, err := client.ImagePull(ctx, image, types.ImagePullOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(w, readCloser)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullQuiet image
|
||||||
|
func (client *Client) PullQuiet(ctx context.Context, image string) error {
|
||||||
|
return client.Pull(ctx, image, ioutil.Discard)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VolumeListByLabels returns volumes which match by given labels
|
||||||
|
func (client *Client) VolumeListByLabels(ctx context.Context, volumeLabels map[string]string) (volume.VolumesListOKBody, error) {
|
||||||
|
args := filters.NewArgs()
|
||||||
|
for key, value := range volumeLabels {
|
||||||
|
args.Add("label", fmt.Sprintf("%v=%v", key, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.VolumeList(ctx, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VolumeListByNames returns volumes which match by their names. If a
|
||||||
|
// volume can not be found, the function returns an error
|
||||||
|
func (client *Client) VolumeListByNames(ctx context.Context, volumeNames ...string) (volume.VolumesListOKBody, error) {
|
||||||
|
args := filters.NewArgs()
|
||||||
|
foundVolumes := make(map[string]bool, 0)
|
||||||
|
for _, volumeName := range volumeNames {
|
||||||
|
foundVolumes[volumeName] = false
|
||||||
|
args.Add("name", volumeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumes, err := client.VolumeList(ctx, args)
|
||||||
|
if err != nil {
|
||||||
|
return volume.VolumesListOKBody{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, volume := range volumes.Volumes {
|
||||||
|
foundVolumes[volume.Name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, volumeName := range volumeNames {
|
||||||
|
if foundVolumes[volumeName] != true {
|
||||||
|
return volume.VolumesListOKBody{}, fmt.Errorf("Volume %v not found", volumeName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return volumes, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// VolumeRemoveByLabels remove all volumes match by their labels
|
||||||
|
func (client *Client) VolumeRemoveByLabels(ctx context.Context, volumeLabels map[string]string) error {
|
||||||
|
volumes, err := client.VolumeListByLabels(ctx, volumeLabels)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, volume := range volumes.Volumes {
|
||||||
|
err := client.VolumeRemove(ctx, volume.Name, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VolumeRemoveByNames remove all volumes match by their names. If a
|
||||||
|
// volume can not be found, the function returns an error
|
||||||
|
func (client *Client) VolumeRemoveByNames(ctx context.Context, volumeNames ...string) error {
|
||||||
|
volumes, err := client.VolumeListByNames(ctx, volumeNames...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, volume := range volumes.Volumes {
|
||||||
|
err := client.VolumeRemove(ctx, volume.Name, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new dockerutil client
|
||||||
|
func New() (*Client, error) {
|
||||||
|
dockerClient, err := client.NewEnvClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
|
dockerClient,
|
||||||
|
nil,
|
||||||
|
new(sync.Mutex),
|
||||||
|
}, nil
|
||||||
|
}
|
443
client_test.go
Normal file
443
client_test.go
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
package dockerutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/volume"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestContainerCRUD
|
||||||
|
// Test the following API functions:
|
||||||
|
// - ContainerListByLabels
|
||||||
|
// - ContainerListByNames
|
||||||
|
// - ContainerRemoveByNames
|
||||||
|
// - ContainerRemoveByLabels
|
||||||
|
// - ContainerRemoveByIDs
|
||||||
|
func TestContainerCRUD(t *testing.T) {
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
require = require.New(t)
|
||||||
|
|
||||||
|
iterations = 5
|
||||||
|
|
||||||
|
cleanupLabels = map[string]string{
|
||||||
|
uuid.NewV4().String(): uuid.NewV4().String(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dockerClient, err := New()
|
||||||
|
require.NoError(err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create Containers
|
||||||
|
containerIDs := make([]string, 0)
|
||||||
|
containerNames := make([]string, 0)
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
containerName := uuid.NewV4().String()
|
||||||
|
containerID, err := dockerClient.NewBuilder("nginx:alpine").
|
||||||
|
Labels(cleanupLabels).
|
||||||
|
Port("80").
|
||||||
|
Pull().
|
||||||
|
WithName(containerName).
|
||||||
|
Start(ctx)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
containerNames = append(containerNames, containerName)
|
||||||
|
containerIDs = append(containerIDs, containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByLabels
|
||||||
|
containers, err := dockerClient.ContainerListByLabels(ctx, true, cleanupLabels)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(containers, iterations)
|
||||||
|
for _, container := range containers {
|
||||||
|
require.Contains(containerIDs, container.ID)
|
||||||
|
require.Contains(containerNames, strings.Split(container.Names[0], "/")[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByNames
|
||||||
|
containers, err = dockerClient.ContainerListByNames(ctx, true, containerNames...)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(containers, iterations)
|
||||||
|
for _, container := range containers {
|
||||||
|
require.Contains(containerIDs, container.ID)
|
||||||
|
require.Contains(containerNames, strings.Split(container.Names[0], "/")[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveByLabels
|
||||||
|
err = dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
containers, err = dockerClient.ContainerListByLabels(ctx, true, cleanupLabels)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(containers, 0)
|
||||||
|
|
||||||
|
// Create
|
||||||
|
containerIDs = make([]string, 0)
|
||||||
|
containerNames = make([]string, 0)
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
containerName := uuid.NewV4().String()
|
||||||
|
containerID, err := dockerClient.NewBuilder("nginx:alpine").
|
||||||
|
Labels(cleanupLabels).
|
||||||
|
Port("80").
|
||||||
|
Pull().
|
||||||
|
WithName(containerName).
|
||||||
|
Start(ctx)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
containerNames = append(containerNames, containerName)
|
||||||
|
containerIDs = append(containerIDs, containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveByNames
|
||||||
|
err = dockerClient.ContainerRemoveByNames(ctx, containerNames...)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
containers, err = dockerClient.ContainerListByNames(ctx, true, containerNames...)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(containers, 0)
|
||||||
|
|
||||||
|
// Create
|
||||||
|
containerIDs = make([]string, 0)
|
||||||
|
containerNames = make([]string, 0)
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
containerName := uuid.NewV4().String()
|
||||||
|
containerID, err := dockerClient.NewBuilder("nginx:alpine").
|
||||||
|
Labels(cleanupLabels).
|
||||||
|
Port("80").
|
||||||
|
Pull().
|
||||||
|
WithName(containerName).
|
||||||
|
Start(ctx)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
containerNames = append(containerNames, containerName)
|
||||||
|
containerIDs = append(containerIDs, containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveByID
|
||||||
|
err = dockerClient.ContainerRemoveByIDs(ctx, containerIDs...)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
containers, err = dockerClient.ContainerListByLabels(ctx, true, cleanupLabels)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(containers, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNetworkCRUD
|
||||||
|
// Test the following API functions:
|
||||||
|
// - NetworkListByLabels
|
||||||
|
// - NetworkListByNames
|
||||||
|
// - NetworkRemoveByLabels
|
||||||
|
// - NetworkRemoveByNames
|
||||||
|
// - NetworkRemoveByIDs
|
||||||
|
func TestNetworkCRUD(t *testing.T) {
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
require = require.New(t)
|
||||||
|
|
||||||
|
iterations = 5
|
||||||
|
|
||||||
|
cleanupLabels = map[string]string{
|
||||||
|
uuid.NewV4().String(): uuid.NewV4().String(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dockerClient, err := New()
|
||||||
|
require.NoError(err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create Networks
|
||||||
|
networkIDs := make([]string, 0)
|
||||||
|
networkNames := make([]string, 0)
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
networkName := uuid.NewV4().String()
|
||||||
|
resp, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{
|
||||||
|
Labels: cleanupLabels,
|
||||||
|
})
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
networkNames = append(networkNames, networkName)
|
||||||
|
networkIDs = append(networkIDs, resp.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByLabels
|
||||||
|
networks, err := dockerClient.NetworkListByLabels(ctx, cleanupLabels)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(networks, iterations)
|
||||||
|
for _, network := range networks {
|
||||||
|
require.Contains(networkIDs, network.ID)
|
||||||
|
require.Contains(networkNames, network.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByLabels, network with label does not exist
|
||||||
|
networks, err = dockerClient.NetworkListByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()})
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(networks, 0)
|
||||||
|
|
||||||
|
// ListByNames
|
||||||
|
networks, err = dockerClient.NetworkListByNames(ctx, networkNames...)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(networks, iterations)
|
||||||
|
for _, network := range networks {
|
||||||
|
require.Contains(networkIDs, network.ID)
|
||||||
|
require.Contains(networkNames, network.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByNames, network with names does not exist
|
||||||
|
networks, err = dockerClient.NetworkListByNames(ctx, uuid.NewV4().String(), uuid.NewV4().String())
|
||||||
|
require.Error(err)
|
||||||
|
require.Nil(networks)
|
||||||
|
|
||||||
|
// RemoveByLabels
|
||||||
|
err = dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
networks, err = dockerClient.NetworkListByLabels(ctx, cleanupLabels)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(networks, 0)
|
||||||
|
|
||||||
|
// RemoveByLabels, label does not exists
|
||||||
|
err = dockerClient.NetworkRemoveByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()})
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Create Networks
|
||||||
|
networkIDs = make([]string, 0)
|
||||||
|
networkNames = make([]string, 0)
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
networkName := uuid.NewV4().String()
|
||||||
|
resp, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{
|
||||||
|
Labels: cleanupLabels,
|
||||||
|
})
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
networkNames = append(networkNames, networkName)
|
||||||
|
networkIDs = append(networkIDs, resp.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveByNames
|
||||||
|
err = dockerClient.NetworkRemoveByNames(ctx, networkNames...)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
networks, err = dockerClient.NetworkListByNames(ctx, networkNames...)
|
||||||
|
require.Error(err)
|
||||||
|
require.Nil(networks)
|
||||||
|
|
||||||
|
// RemoveByNames, name does not exists
|
||||||
|
err = dockerClient.NetworkRemoveByNames(ctx, uuid.NewV4().String())
|
||||||
|
require.Error(err)
|
||||||
|
|
||||||
|
// Create Networks
|
||||||
|
networkIDs = make([]string, 0)
|
||||||
|
networkNames = make([]string, 0)
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
networkName := uuid.NewV4().String()
|
||||||
|
resp, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{
|
||||||
|
Labels: cleanupLabels,
|
||||||
|
})
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
networkNames = append(networkNames, networkName)
|
||||||
|
networkIDs = append(networkIDs, resp.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveByIDs
|
||||||
|
err = dockerClient.NetworkRemoveByIDs(ctx, networkIDs...)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
networks, err = dockerClient.NetworkListByNames(ctx, networkNames...)
|
||||||
|
require.Error(err)
|
||||||
|
require.Nil(networks)
|
||||||
|
|
||||||
|
// RemoveByID, id does not exists
|
||||||
|
err = dockerClient.NetworkRemoveByIDs(ctx, uuid.NewV4().String())
|
||||||
|
require.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVolumeCRUD(t *testing.T) {
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
require = require.New(t)
|
||||||
|
|
||||||
|
iterations = 5
|
||||||
|
|
||||||
|
cleanupLabels = map[string]string{
|
||||||
|
uuid.NewV4().String(): uuid.NewV4().String(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dockerClient, err := New()
|
||||||
|
require.NoError(err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
dockerClient.VolumeRemoveByLabels(ctx, cleanupLabels)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create Volumes
|
||||||
|
volumeNames := make([]string, 0)
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
volumeName := uuid.NewV4().String()
|
||||||
|
volume, err := dockerClient.VolumeCreate(ctx, volume.VolumesCreateBody{
|
||||||
|
Name: volumeName,
|
||||||
|
Labels: cleanupLabels,
|
||||||
|
})
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
volumeNames = append(volumeNames, volume.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByLabels
|
||||||
|
volumes, err := dockerClient.VolumeListByLabels(ctx, cleanupLabels)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(volumes.Volumes, iterations)
|
||||||
|
for _, volume := range volumes.Volumes {
|
||||||
|
require.Contains(volumeNames, volume.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByLabels, network with label does not exist
|
||||||
|
volumes, err = dockerClient.VolumeListByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()})
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(volumes.Volumes, 0)
|
||||||
|
|
||||||
|
// ListByNames
|
||||||
|
volumes, err = dockerClient.VolumeListByNames(ctx, volumeNames...)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(volumes.Volumes, iterations)
|
||||||
|
for _, volume := range volumes.Volumes {
|
||||||
|
require.Contains(volumeNames, volume.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListByNames, network with names does not exist
|
||||||
|
volumes, err = dockerClient.VolumeListByNames(ctx, uuid.NewV4().String(), uuid.NewV4().String())
|
||||||
|
require.Error(err)
|
||||||
|
require.Nil(volumes.Volumes)
|
||||||
|
|
||||||
|
// RemoveByLabels
|
||||||
|
err = dockerClient.VolumeRemoveByLabels(ctx, cleanupLabels)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
volumes, err = dockerClient.VolumeListByLabels(ctx, cleanupLabels)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(volumes.Volumes, 0)
|
||||||
|
|
||||||
|
// RemoveByLabels, labels does not exists
|
||||||
|
err = dockerClient.NetworkRemoveByLabels(ctx, map[string]string{uuid.NewV4().String(): uuid.NewV4().String()})
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Create Volumes
|
||||||
|
volumeNames = make([]string, 0)
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
volumeName := uuid.NewV4().String()
|
||||||
|
volume, err := dockerClient.VolumeCreate(ctx, volume.VolumesCreateBody{
|
||||||
|
Name: volumeName,
|
||||||
|
Labels: cleanupLabels,
|
||||||
|
})
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
volumeNames = append(volumeNames, volume.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveByNames
|
||||||
|
err = dockerClient.VolumeRemoveByNames(ctx, volumeNames...)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
volumes, err = dockerClient.VolumeListByNames(ctx, volumeNames...)
|
||||||
|
require.Error(err)
|
||||||
|
require.Nil(volumes.Volumes)
|
||||||
|
|
||||||
|
// RemoveByNames, name does not exists
|
||||||
|
err = dockerClient.NetworkRemoveByNames(ctx, uuid.NewV4().String())
|
||||||
|
require.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestContainerMultipleNetworks
|
||||||
|
// Test if a container can be accessed over multiple networks/ips.
|
||||||
|
func TestContainerMultipleNetworks(t *testing.T) {
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
require = require.New(t)
|
||||||
|
|
||||||
|
iterations = 5
|
||||||
|
|
||||||
|
cleanupLabels = map[string]string{
|
||||||
|
uuid.NewV4().String(): uuid.NewV4().String(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
dockerClient, err := New()
|
||||||
|
require.NoError(err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
dockerClient.ContainerRemoveByLabels(ctx, cleanupLabels)
|
||||||
|
dockerClient.NetworkRemoveByLabels(ctx, cleanupLabels)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create Containers
|
||||||
|
containerIDs := make([]string, 0)
|
||||||
|
containerNames := make([]string, 0)
|
||||||
|
containersNetworks := make(map[string]map[string][]string, 0)
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
containerName := uuid.NewV4().String()
|
||||||
|
|
||||||
|
containerNetworks := map[string][]string{
|
||||||
|
uuid.NewV4().String(): {
|
||||||
|
uuid.NewV4().String(),
|
||||||
|
uuid.NewV4().String(),
|
||||||
|
},
|
||||||
|
uuid.NewV4().String(): {
|
||||||
|
uuid.NewV4().String(),
|
||||||
|
uuid.NewV4().String(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
builder := dockerClient.NewBuilder("nginx:alpine").
|
||||||
|
Labels(cleanupLabels).
|
||||||
|
Port("80").
|
||||||
|
Pull().
|
||||||
|
WithName(containerName)
|
||||||
|
|
||||||
|
for networkName, aliasses := range containerNetworks {
|
||||||
|
_, err := dockerClient.NetworkCreate(ctx, networkName, types.NetworkCreate{
|
||||||
|
Labels: cleanupLabels,
|
||||||
|
})
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
builder.Network(networkName, aliasses...)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerID, err := builder.Start(ctx)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
containerNames = append(containerNames, containerName)
|
||||||
|
containerIDs = append(containerIDs, containerID)
|
||||||
|
containersNetworks[containerID] = containerNetworks
|
||||||
|
}
|
||||||
|
|
||||||
|
for containerID, containerNetworks := range containersNetworks {
|
||||||
|
for networkName := range containerNetworks {
|
||||||
|
networks, err := dockerClient.NetworkListByNames(ctx, networkName)
|
||||||
|
require.NoError(err)
|
||||||
|
for _, network := range networks {
|
||||||
|
if _, present := network.Containers[containerID]; !present {
|
||||||
|
require.Fail("Container %v not found in network %v", containerID, network.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
networkIPParts := strings.Split(network.Containers[containerID].IPv4Address, "/")
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%v", networkIPParts[0])
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
require.NoError(err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
require.Equal(http.StatusOK, resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
go.mod
Normal file
15
go.mod
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module git.cryptic.systems/volker.raschek/dockerutils
|
||||||
|
|
||||||
|
go 1.15
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
|
github.com/docker/docker v1.13.1
|
||||||
|
github.com/docker/go-connections v0.4.0
|
||||||
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/satori/go.uuid v1.2.0
|
||||||
|
github.com/stretchr/testify v1.6.1
|
||||||
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect
|
||||||
|
)
|
35
go.sum
Normal file
35
go.sum
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||||
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
|
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
|
||||||
|
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
|
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||||
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||||
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
|
||||||
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
166
watcher.go
Normal file
166
watcher.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
package dockerutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDied = errors.New("died")
|
||||||
|
ErrUnhealthy = errors.New("went unhealthy")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher is a helper to listen on docker API events to notify if a container
|
||||||
|
// is healthy or not.
|
||||||
|
type Watcher struct {
|
||||||
|
client *Client
|
||||||
|
errorChannels map[string]chan<- error
|
||||||
|
doneChannels map[string]chan<- struct{}
|
||||||
|
errorMapper map[string]ErrorMapper
|
||||||
|
mutex *sync.RWMutex
|
||||||
|
lastError error
|
||||||
|
cancelFunc context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watcher *Watcher) AddListenerWithErrorMapper(containerID string, errorChannel chan<- error, doneChannel chan<- struct{}, errorMapper ErrorMapper) error {
|
||||||
|
watcher.mutex.Lock()
|
||||||
|
defer watcher.mutex.Unlock()
|
||||||
|
|
||||||
|
if watcher.lastError != nil {
|
||||||
|
return watcher.lastError
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(20*time.Second))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
containerJSON, err := watcher.client.ContainerInspect(ctx, containerID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to check if container is already unhealthy: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if containerJSON.State.Dead || !containerJSON.State.Running {
|
||||||
|
go func() {
|
||||||
|
errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", containerID, ErrDied))
|
||||||
|
doneChannel <- struct{}{}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if containerJSON.State.Health == nil {
|
||||||
|
go func() {
|
||||||
|
doneChannel <- struct{}{}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch containerJSON.State.Health.Status {
|
||||||
|
case "starting":
|
||||||
|
watcher.errorChannels[containerID] = errorChannel
|
||||||
|
watcher.doneChannels[containerID] = doneChannel
|
||||||
|
watcher.errorMapper[containerID] = errorMapper
|
||||||
|
case "healthy":
|
||||||
|
go func() {
|
||||||
|
doneChannel <- struct{}{}
|
||||||
|
}()
|
||||||
|
case "unhealthy":
|
||||||
|
go func() {
|
||||||
|
errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", containerID, ErrUnhealthy))
|
||||||
|
doneChannel <- struct{}{}
|
||||||
|
}()
|
||||||
|
default:
|
||||||
|
go func() {
|
||||||
|
errorChannel <- errorMapper(fmt.Errorf("Container %v went in an unknown state during startup: %s", containerID, containerJSON.State.Health.Status))
|
||||||
|
doneChannel <- struct{}{}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watcher *Watcher) AddListener(containerID string, errorChannel chan<- error, doneChannel chan<- struct{}) error {
|
||||||
|
return watcher.AddListenerWithErrorMapper(containerID, errorChannel, doneChannel, defaultErrorMapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watcher *Watcher) removeListener(containerID string) {
|
||||||
|
watcher.mutex.Lock()
|
||||||
|
defer watcher.mutex.Unlock()
|
||||||
|
delete(watcher.doneChannels, containerID)
|
||||||
|
delete(watcher.errorChannels, containerID)
|
||||||
|
delete(watcher.errorMapper, containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watcher *Watcher) start() {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
watcher.cancelFunc = cancel
|
||||||
|
dockerEvents, errors := watcher.client.Events(ctx, types.EventsOptions{})
|
||||||
|
|
||||||
|
sendErrorFunc := func() {
|
||||||
|
watcher.mutex.Lock()
|
||||||
|
for containerID, errorChannel := range watcher.errorChannels {
|
||||||
|
go func(errorChannel chan<- error, doneChannel chan<- struct{}, err error, errorMapper ErrorMapper) {
|
||||||
|
errorChannel <- errorMapper(err)
|
||||||
|
doneChannel <- struct{}{}
|
||||||
|
}(errorChannel, watcher.doneChannels[containerID], watcher.lastError, watcher.errorMapper[containerID])
|
||||||
|
}
|
||||||
|
watcher.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-dockerEvents:
|
||||||
|
watcher.mutex.RLock()
|
||||||
|
errorChannel, present := watcher.errorChannels[event.Actor.ID]
|
||||||
|
if !present || event.Type != events.ContainerEventType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
doneChannel := watcher.doneChannels[event.Actor.ID]
|
||||||
|
errorMapper := watcher.errorMapper[event.Actor.ID]
|
||||||
|
watcher.mutex.RUnlock()
|
||||||
|
|
||||||
|
switch event.Action {
|
||||||
|
case "health_status: healthy":
|
||||||
|
go func() {
|
||||||
|
doneChannel <- struct{}{}
|
||||||
|
}()
|
||||||
|
watcher.removeListener(event.Actor.ID)
|
||||||
|
case "health_status: unhealthy":
|
||||||
|
go func() {
|
||||||
|
errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", event.Actor.ID, ErrUnhealthy))
|
||||||
|
doneChannel <- struct{}{}
|
||||||
|
}()
|
||||||
|
watcher.removeListener(event.Actor.ID)
|
||||||
|
case "die":
|
||||||
|
go func() {
|
||||||
|
errorChannel <- errorMapper(fmt.Errorf("Container %v: %w", event.Actor.ID, ErrDied))
|
||||||
|
doneChannel <- struct{}{}
|
||||||
|
}()
|
||||||
|
watcher.removeListener(event.Actor.ID)
|
||||||
|
}
|
||||||
|
case err := <-errors:
|
||||||
|
watcher.lastError = err
|
||||||
|
sendErrorFunc()
|
||||||
|
return
|
||||||
|
case <-ctx.Done():
|
||||||
|
watcher.lastError = fmt.Errorf("Watcher was canceled")
|
||||||
|
sendErrorFunc()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watcher *Watcher) stop() {
|
||||||
|
watcher.cancelFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorMapper func(error) error
|
||||||
|
|
||||||
|
func defaultErrorMapper(err error) error {
|
||||||
|
return err
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user