resource/docker/docker.go
// Copyright © 2016 Asteris, LLC
//
// 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.
// +build !solaris
package docker
import (
"strings"
"time"
log "github.com/Sirupsen/logrus"
dc "github.com/fsouza/go-dockerclient"
"github.com/pkg/errors"
)
// APIClient provides access to docker
type APIClient interface {
FindImage(string) (*dc.Image, error)
PullImage(string, string) error
FindContainer(string) (*dc.Container, error)
CreateContainer(dc.CreateContainerOptions) (*dc.Container, error)
StartContainer(string, string) error
ConnectNetwork(string, *dc.Container) error
}
// VolumeClient manages Docker volumes
type VolumeClient interface {
FindVolume(string) (*dc.Volume, error)
CreateVolume(dc.CreateVolumeOptions) (*dc.Volume, error)
RemoveVolume(string) error
}
// Client provides api access to Docker
type Client struct {
*dc.Client
PullInactivityTimeout time.Duration
}
// NetworkClient manage Docker networks
type NetworkClient interface {
ListNetworks() ([]dc.Network, error)
FindNetwork(string) (*dc.Network, error)
CreateNetwork(dc.CreateNetworkOptions) (*dc.Network, error)
RemoveNetwork(string) error
}
// NewDockerClient returns a docker client with the default configuration
func NewDockerClient() (*Client, error) {
c, err := dc.NewClientFromEnv()
if err != nil {
return nil, errors.Wrap(err, "failed to create docker client from environment")
}
return &Client{Client: c}, nil
}
// FindImage finds a local docker image with the specified repo tag
func (c *Client) FindImage(repoTag string) (*dc.Image, error) {
// TODO: can I just call inspect with the repoTag?
images, err := c.Client.ListImages(dc.ListImagesOptions{All: true})
if err != nil {
return nil, errors.Wrap(err, "failed to find image")
}
log.WithFields(log.Fields{
"module": "docker",
"filter": repoTag,
"image_count": len(images),
}).Debug("image filter found images")
var imageID string
for _, image := range images {
if repoTag == image.ID {
imageID = image.ID
break
}
for _, tag := range image.RepoTags {
log.WithField("module", "docker").WithField("tag", tag).Debug("found tag")
if strings.EqualFold(repoTag, tag) {
imageID = image.ID
break
}
}
if imageID != "" {
break
}
}
if imageID != "" {
log.WithField("module", "docker").WithField("tag", repoTag).Debug("found image")
image, err := c.Client.InspectImage(imageID)
if err != nil {
return nil, errors.Wrapf(err, "failed to inspect image %s (%s)", repoTag, imageID)
}
return image, nil
}
log.WithField("module", "docker").WithField("tag", repoTag).Debug("could not find image")
return nil, nil
}
// PullImage pulls an image with the specified name and tag
func (c *Client) PullImage(name, tag string) error {
log.WithFields(log.Fields{
"module": "docker",
"name": name,
"tag": tag,
}).Debug("pulling")
opts := dc.PullImageOptions{
Repository: name,
Tag: tag,
InactivityTimeout: c.PullInactivityTimeout,
}
err := c.Client.PullImage(opts, dc.AuthConfiguration{})
if err != nil {
return errors.Wrap(err, "failed to pull image")
}
log.WithFields(log.Fields{
"module": "docker",
"name": name,
"tag": tag,
}).Debug("done pulling")
return nil
}
// FindContainer returns a container matching the specified name
func (c *Client) FindContainer(name string) (*dc.Container, error) {
opts := dc.ListContainersOptions{All: true}
containers, err := c.Client.ListContainers(opts)
if err != nil {
return nil, errors.Wrap(err, "failed to list containers")
}
var containerID string
for _, container := range containers {
// check if ID was specified first
if name == container.ID {
containerID = container.ID
break
}
// check container names for a match
for _, cname := range container.Names {
if strings.EqualFold(name, strings.TrimPrefix(cname, "/")) {
containerID = container.ID
break
}
}
if containerID != "" {
break
}
}
if containerID != "" {
log.WithField("module", "docker").WithField("name", name).Debug("found container")
container, err := c.Client.InspectContainer(containerID)
if err != nil {
return nil, errors.Wrapf(err, "failed to inspect container %s (%s)", name, containerID)
}
return container, nil
}
log.WithField("module", "docker").WithField("name", name).Debug("could not find container")
return nil, nil
}
// CreateContainer creates a container with the specified options
func (c *Client) CreateContainer(opts dc.CreateContainerOptions) (*dc.Container, error) {
name := opts.Name
container, err := c.FindContainer(name)
if err != nil {
return nil, err
}
// the container already exists
if container != nil {
log.WithField("module", "docker").WithField("name", name).Debug("container exists")
// stop the container if running
if container.State.Running {
log.WithField("module", "docker").WithFields(log.Fields{"name": name, "id": container.ID}).Debug("stopping container")
err = c.Client.StopContainer(container.ID, 60)
if err != nil {
return nil, errors.Wrapf(err, "failed to stop container %s (%s)", name, container.ID)
}
}
// remove the container
log.WithField("module", "docker").WithFields(log.Fields{"name": name, "id": container.ID}).Debug("removing container")
err = c.Client.RemoveContainer(dc.RemoveContainerOptions{ID: container.ID})
if err != nil {
return nil, errors.Wrapf(err, "failed to remove container %s (%s)", name, container.ID)
}
}
// create the container
log.WithField("module", "docker").WithField("name", name).Debug("creating container")
container, err = c.Client.CreateContainer(opts)
if err != nil {
return nil, errors.Wrapf(err, "failed to create container %s", name)
}
return container, err
}
// StartContainer starts the container with the specified ID
func (c *Client) StartContainer(name, containerID string) error {
log.WithField("module", "docker").WithFields(log.Fields{"name": name, "id": containerID}).Debug("starting container")
err := c.Client.StartContainer(containerID, nil)
if err != nil {
err = errors.Wrapf(err, "failed to start container %s (%s)", name, containerID)
}
return err
}
// CreateVolume creates a docker volume
func (c *Client) CreateVolume(opts dc.CreateVolumeOptions) (*dc.Volume, error) {
log.WithFields(log.Fields{
"module": "docker",
"function": "CreateVolume",
}).Debugf("creating volume %s: %+v", opts.Name, opts)
vol, err := c.Client.CreateVolume(opts)
if err != nil {
return nil, errors.Wrap(err, "failed to create volume")
}
return vol, nil
}
// RemoveVolume removes a docker volume
func (c *Client) RemoveVolume(name string) error {
log.WithFields(log.Fields{
"module": "docker",
"function": "RemoveVolume",
}).Debugf("removing volume %s", name)
err := c.Client.RemoveVolume(name)
if err != nil {
return errors.Wrap(err, "failed to remove volume")
}
return nil
}
// FindVolume finds the volume with the specified name
func (c *Client) FindVolume(name string) (*dc.Volume, error) {
volume, err := c.Client.InspectVolume(name)
if err != nil && err != dc.ErrNoSuchVolume {
return nil, errors.Wrap(err, "failed to inspect volume")
}
return volume, nil
}
// ConnectNetwork connects the container to a network
func (c *Client) ConnectNetwork(name string, container *dc.Container) error {
log.WithField("module", "docker").WithFields(
log.Fields{"name": container.Name, "network": name, "id": container.ID},
).Debug("connecting to network")
err := c.Client.ConnectNetwork(name, dc.NetworkConnectionOptions{Container: container.ID})
if err != nil {
err = errors.Wrapf(err, "failed to connect container %s (%s) to network %s", container.Name, container.ID, name)
}
return err
}
// FindNetwork finds the network with the specified name
func (c *Client) FindNetwork(name string) (*dc.Network, error) {
networks, err := c.Client.FilteredListNetworks(dc.NetworkFilterOpts{
"type": map[string]bool{"custom": true},
"name": map[string]bool{name: true},
})
if err != nil {
return nil, errors.Wrap(err, "failed to list networks")
}
for _, nw := range networks {
if strings.EqualFold(nw.Name, name) {
return &nw, nil
}
}
return nil, nil
}
// ListNetworks returns the docker networks
func (c *Client) ListNetworks() ([]dc.Network, error) {
return c.Client.ListNetworks()
}
// CreateNetwork creates a docker network
func (c *Client) CreateNetwork(opts dc.CreateNetworkOptions) (*dc.Network, error) {
log.WithFields(log.Fields{
"module": "docker",
"function": "CreateNetwork",
}).Debugf("creating network %s: %+v", opts.Name, opts)
nw, err := c.Client.CreateNetwork(opts)
if err != nil {
return nil, errors.Wrap(err, "failed to create network")
}
return nw, err
}
// RemoveNetwork removes a docker volume
func (c *Client) RemoveNetwork(name string) error {
log.WithFields(log.Fields{
"module": "docker",
"function": "RemoveNetwork",
}).Debugf("removing network %s", name)
nw, err := c.FindNetwork(name)
if err != nil {
return err
}
err = c.Client.RemoveNetwork(nw.ID)
if err != nil {
return errors.Wrap(err, "failed to remove network")
}
return nil
}