package dockerhelper
import (
"bytes"
"errors"
"fmt"
"io"
"net"
"net/url"
"regexp"
"strconv"
"time"
"github.com/blang/semver"
dockerclient "github.com/docker/engine-api/client"
"github.com/docker/engine-api/types/registry"
docker "github.com/fsouza/go-dockerclient"
"github.com/golang/glog"
"golang.org/x/net/context"
"github.com/openshift/imagebuilder/imageprogress"
starterrors "github.com/openshift/origin/pkg/bootstrap/docker/errors"
)
const openShiftInsecureCIDR = "172.30.0.0/16"
// Helper provides utility functions to help with Docker
type Helper struct {
client *docker.Client
engineAPIClient *dockerclient.Client
}
// NewHelper creates a new Helper
func NewHelper(client *docker.Client, engineAPIClient *dockerclient.Client) *Helper {
return &Helper{
client: client,
engineAPIClient: engineAPIClient,
}
}
type RegistryConfig struct {
InsecureRegistryCIDRs []string
}
func hasCIDR(cidr string, listOfCIDRs []*registry.NetIPNet) bool {
glog.V(5).Infof("Looking for %q in %#v", cidr, listOfCIDRs)
for _, candidate := range listOfCIDRs {
candidateStr := (*net.IPNet)(candidate).String()
if candidateStr == cidr {
glog.V(5).Infof("Found %q", cidr)
return true
}
}
glog.V(5).Infof("Did not find %q", cidr)
return false
}
// HasInsecureRegistryArg checks whether the docker daemon is configured with
// the appropriate insecure registry argument
func (h *Helper) HasInsecureRegistryArg() (bool, error) {
glog.V(5).Infof("Retrieving Docker daemon info")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
if h.engineAPIClient == nil {
return false, fmt.Errorf("the Docker engine API client is not initialized")
}
info, err := h.engineAPIClient.Info(ctx)
defer cancel()
if err != nil {
glog.V(2).Infof("Could not retrieve Docker info: %v", err)
return false, err
}
glog.V(5).Infof("Docker daemon info: %#v", info)
registryConfig := info.RegistryConfig
if err != nil {
return false, err
}
return hasCIDR(openShiftInsecureCIDR, registryConfig.InsecureRegistryCIDRs), nil
}
var (
fedoraPackage = regexp.MustCompile("\\.fc[0-9_]*\\.")
rhelPackage = regexp.MustCompile("\\.el[0-9_]*\\.")
)
// Version returns the Docker version and whether it is a Red Hat distro version
func (h *Helper) Version() (*semver.Version, bool, error) {
glog.V(5).Infof("Retrieving Docker version")
env, err := h.client.Version()
if err != nil {
glog.V(2).Infof("Error retrieving version: %v", err)
return nil, false, err
}
glog.V(5).Infof("Docker version results: %#v", env)
versionStr := env.Get("Version")
if len(versionStr) == 0 {
return nil, false, errors.New("did not get a version")
}
glog.V(5).Infof("Version: %s", versionStr)
dockerVersion, err := semver.Parse(versionStr)
if err != nil {
glog.V(2).Infof("Error parsing Docker version %q", versionStr)
return nil, false, err
}
isRedHat := false
packageVersion := env.Get("PkgVersion")
if len(packageVersion) > 0 {
isRedHat = fedoraPackage.MatchString(packageVersion) || rhelPackage.MatchString(packageVersion)
}
return &dockerVersion, isRedHat, nil
}
// CheckAndPull checks whether a Docker image exists. If not, it pulls it.
func (h *Helper) CheckAndPull(image string, out io.Writer) error {
glog.V(5).Infof("Inspecting Docker image %q", image)
imageMeta, err := h.client.InspectImage(image)
if err == nil {
glog.V(5).Infof("Image %q found: %#v", image, imageMeta)
return nil
}
if err != docker.ErrNoSuchImage {
return starterrors.NewError("unexpected error inspecting image %s", image).WithCause(err)
}
glog.V(5).Infof("Image %q not found. Pulling", image)
fmt.Fprintf(out, "Pulling image %s\n", image)
logProgress := func(s string) {
fmt.Fprintf(out, "%s\n", s)
}
outputStream := imageprogress.NewPullWriter(logProgress)
if glog.V(5) {
outputStream = out
}
err = h.client.PullImage(docker.PullImageOptions{
Repository: image,
RawJSONStream: bool(!glog.V(5)),
OutputStream: outputStream,
}, docker.AuthConfiguration{})
if err != nil {
return starterrors.NewError("error pulling Docker image %s", image).WithCause(err)
}
fmt.Fprintf(out, "Image pull complete\n")
return nil
}
// GetContainerState returns whether a container exists and if it does whether it's running
func (h *Helper) GetContainerState(id string) (container *docker.Container, running bool, err error) {
glog.V(5).Infof("Inspecting docker container %q", id)
container, err = h.client.InspectContainer(id)
if err != nil {
if _, notFound := err.(*docker.NoSuchContainer); notFound {
glog.V(5).Infof("Container %q was not found", id)
err = nil
return
}
glog.V(5).Infof("An error occurred inspecting container %q: %v", id, err)
return
}
running = container.State.Running
glog.V(5).Infof("Container inspect result: %#v", container)
glog.V(5).Infof("Container running = %v", running)
return
}
// RemoveContainer removes the container with the given id
func (h *Helper) RemoveContainer(id string) error {
glog.V(5).Infof("Removing container %q", id)
err := h.client.RemoveContainer(docker.RemoveContainerOptions{
ID: id,
})
if err != nil {
return starterrors.NewError("cannot delete container %s", id).WithCause(err)
}
glog.V(5).Infof("Removed container %q", id)
return nil
}
// HostIP returns the IP of the Docker host if connecting via TCP
func (h *Helper) HostIP() string {
// By default, if the Docker client uses tcp, then use the Docker daemon's address
endpoint := h.client.Endpoint()
u, err := url.Parse(endpoint)
if err == nil && (u.Scheme == "tcp" || u.Scheme == "http" || u.Scheme == "https") {
glog.V(2).Infof("Using the Docker host %s for the server IP", endpoint)
if host, _, serr := net.SplitHostPort(u.Host); serr == nil {
return host
}
return u.Host
}
glog.V(5).Infof("Cannot use Docker endpoint (%s) because it is not using one of the following protocols: tcp, http, https", endpoint)
return ""
}
func (h *Helper) ContainerLog(container string, numLines int) string {
output := &bytes.Buffer{}
err := h.client.Logs(docker.LogsOptions{
Container: container,
Tail: strconv.Itoa(numLines),
OutputStream: output,
ErrorStream: output,
Stdout: true,
Stderr: true,
})
if err != nil {
glog.V(1).Infof("Error getting container %q log: %v", container, err)
}
return output.String()
}
func (h *Helper) StopAndRemoveContainer(container string) error {
err := h.client.StopContainer(container, 10)
if err != nil {
glog.V(2).Infof("Cannot stop container %s: %v", container, err)
}
return h.RemoveContainer(container)
}
func (h *Helper) ListContainerNames() ([]string, error) {
containers, err := h.client.ListContainers(docker.ListContainersOptions{All: true})
if err != nil {
return nil, err
}
names := []string{}
for _, c := range containers {
if len(c.Names) > 0 {
names = append(names, c.Names[0])
}
}
return names, nil
}