dockerutils/client.go

405 lines
11 KiB
Go

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"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
// 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),
platform: &v1.Platform{},
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.VolumeListOKBody, 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.VolumeListOKBody, 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.VolumeListOKBody{}, err
}
for _, volume := range volumes.Volumes {
foundVolumes[volume.Name] = true
}
for _, volumeName := range volumeNames {
if foundVolumes[volumeName] != true {
return volume.VolumeListOKBody{}, 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
}