package dockermachine
import (
"bufio"
"bytes"
"net/http"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
dockerclient "github.com/docker/engine-api/client"
"github.com/docker/go-connections/tlsconfig"
docker "github.com/fsouza/go-dockerclient"
"github.com/openshift/origin/pkg/bootstrap/docker/errors"
"github.com/openshift/origin/pkg/bootstrap/docker/localcmd"
"k8s.io/kubernetes/pkg/util/net"
)
const (
defaultMachineMemory = 2048
defaultMachineProcessors = 2
)
// Builder can be used to create a new Docker machine on the local system
type Builder struct {
name string
memory int
processors int
}
// NewBuilder creates a Docker machine Builder object used to create a Docker machine
func NewBuilder() *Builder {
return &Builder{}
}
// Name sets the name of the Docker machine to build
func (b *Builder) Name(name string) *Builder {
b.name = name
return b
}
// Memory sets the amount of memory (in MB) to give a Docker machine when creating it
func (b *Builder) Memory(mem int) *Builder {
b.memory = mem
return b
}
// Processors sets the number of processors to give a Docker machine when creating it
func (b *Builder) Processors(proc int) *Builder {
b.processors = proc
return b
}
// Create creates a new Docker machine
func (b *Builder) Create() error {
if Exists(b.name) {
return ErrDockerMachineExists
}
if IsAvailable() {
return ErrDockerMachineNotAvailable
}
mem := b.memory
if mem == 0 {
mem = determineMachineMemory()
}
proc := b.processors
if proc == 0 {
proc = determineMachineProcessors()
}
return localcmd.New(dockerMachineBinary()).Args(
"create",
"--driver", "virtualbox",
"--virtualbox-cpu-count", strconv.Itoa(proc),
"--virtualbox-memory", strconv.Itoa(mem),
"--engine-insecure-registry", "172.30.0.0/16",
b.name).Run()
}
// IsRunning returns true if a Docker machine is running
func IsRunning(name string) bool {
err := localcmd.New(dockerMachineBinary()).Args("ip", name).Run()
return err == nil
}
// IP returns the IP address of the Docker machine
func IP(name string) (string, error) {
output, _, err := localcmd.New(dockerMachineBinary()).Args("ip", name).Output()
if err != nil {
return "", ErrDockerMachineExec("ip", err)
}
return strings.TrimSpace(output), nil
}
// Exists returns true if a Docker machine exists
func Exists(name string) bool {
err := localcmd.New(dockerMachineBinary()).Args("inspect", name).Run()
return err == nil
}
// Start starts up an existing Docker machine
func Start(name string) error {
err := localcmd.New(dockerMachineBinary()).Args("start", name).Run()
if err != nil {
return ErrDockerMachineExec("start", err)
}
return nil
}
// Client returns a Docker client for the given Docker machine
func Client(name string) (*docker.Client, *dockerclient.Client, error) {
output, _, err := localcmd.New(dockerMachineBinary()).Args("env", name).Output()
if err != nil {
return nil, nil, ErrDockerMachineExec("env", err)
}
scanner := bufio.NewScanner(bytes.NewBufferString(output))
var (
dockerHost, certPath string
tlsVerify bool
)
prefix := "export "
if runtime.GOOS == "windows" {
prefix = "SET "
}
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, prefix) {
line = strings.TrimPrefix(line, prefix)
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
switch strings.ToUpper(parts[0]) {
case "DOCKER_HOST":
dockerHost = strings.Trim(parts[1], "\"")
case "DOCKER_CERT_PATH":
certPath = strings.Trim(parts[1], "\"")
case "DOCKER_TLS_VERIFY":
tlsVerify = len(parts[1]) > 0
}
}
}
var client *docker.Client
if len(certPath) > 0 {
cert := filepath.Join(certPath, "cert.pem")
key := filepath.Join(certPath, "key.pem")
ca := filepath.Join(certPath, "ca.pem")
client, err = docker.NewVersionedTLSClient(dockerHost, cert, key, ca, "")
} else {
client, err = docker.NewVersionedClient(dockerHost, "")
}
if err != nil {
return nil, nil, errors.NewError("could not get Docker client for machine %s", name).WithCause(err)
}
client.SkipServerVersionCheck = true
var httpClient *http.Client
if len(certPath) > 0 {
tlscOptions := tlsconfig.Options{
CAFile: filepath.Join(certPath, "ca.pem"),
CertFile: filepath.Join(certPath, "cert.pem"),
KeyFile: filepath.Join(certPath, "key.pem"),
InsecureSkipVerify: !tlsVerify,
}
tlsc, tlsErr := tlsconfig.Client(tlscOptions)
if tlsErr != nil {
return nil, nil, errors.NewError("could not create TLS config client for machine %s", name).WithCause(tlsErr)
}
httpClient = &http.Client{
Transport: net.SetTransportDefaults(&http.Transport{
TLSClientConfig: tlsc,
}),
}
}
engineAPIClient, err := dockerclient.NewClient(dockerHost, "", httpClient, nil)
if err != nil {
return nil, nil, errors.NewError("cannot create Docker engine API client").WithCause(err)
}
return client, engineAPIClient, nil
}
// IsAvailable returns true if the docker-machine executable can be found in the PATH
func IsAvailable() bool {
_, err := exec.LookPath(dockerMachineBinary())
return err != nil
}
// determineMachineMemory determines a reasonable default for machine memory
// TODO: implement linux & windows
func determineMachineMemory() int {
if runtime.GOOS == "darwin" {
output, _, err := localcmd.New("sysctl").Args("-n", "hw.memsize").Output()
if err == nil {
mem, perr := strconv.ParseInt(strings.TrimSpace(output), 10, 64)
if perr == nil {
return int(mem / (1024 * 1024 * 2)) // half of available megs
}
}
}
return defaultMachineMemory
}
// determineMachineProcs determines a reasonable default for machine processors
// TODO: implement linux & windows
func determineMachineProcessors() int {
if runtime.GOOS == "darwin" {
output, _, err := localcmd.New("sysctl").Args("-n", "hw.logicalcpu").Output()
if err == nil {
cpus, aerr := strconv.Atoi(strings.TrimSpace(output))
if aerr == nil {
return cpus // use all cpus
}
}
}
return defaultMachineProcessors
}
func dockerMachineBinary() string {
if runtime.GOOS == "windows" {
return "docker-machine.exe"
}
return "docker-machine"
}