Merge pull request #827 from nathanleclaire/libmachine_provision_merge

Add provisioning based on OS
This commit is contained in:
Evan Hazlett 2015-03-21 22:29:57 -04:00
commit 3392671988
29 changed files with 1590 additions and 898 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
docker-machine*
*.log

View File

@ -30,6 +30,7 @@ import (
_ "github.com/docker/machine/drivers/vmwarevcloudair"
_ "github.com/docker/machine/drivers/vmwarevsphere"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/state"
@ -39,33 +40,24 @@ import (
type machineConfig struct {
machineName string
machineDir string
caCertPath string
caKeyPath string
clientCertPath string
machineUrl string
clientKeyPath string
serverCertPath string
clientCertPath string
caCertPath string
caKeyPath string
serverKeyPath string
machineUrl string
swarmMaster bool
swarmHost string
swarmDiscovery string
AuthOptions auth.AuthOptions
SwarmOptions swarm.SwarmOptions
}
type hostListItem struct {
Name string
Active bool
DriverName string
State state.State
URL string
SwarmMaster bool
SwarmDiscovery string
}
type certPathInfo struct {
CaCertPath string
CaKeyPath string
ClientCertPath string
ClientKeyPath string
Name string
Active bool
DriverName string
State state.State
URL string
SwarmOptions swarm.SwarmOptions
}
func sortHostListItemsByName(items []hostListItem) {
@ -407,17 +399,25 @@ func cmdCreate(c *cli.Context) {
}
hostOptions := &libmachine.HostOptions{
DriverOptions: c,
AuthOptions: &auth.AuthOptions{
CaCertPath: certInfo.CaCertPath,
PrivateKeyPath: certInfo.CaKeyPath,
ClientCertPath: certInfo.ClientCertPath,
ClientKeyPath: certInfo.ClientKeyPath,
ServerCertPath: filepath.Join(utils.GetMachineDir(), name, "server.pem"),
ServerKeyPath: filepath.Join(utils.GetMachineDir(), name, "server-key.pem"),
},
EngineOptions: &engine.EngineOptions{},
SwarmOptions: &swarm.SwarmOptions{
Master: c.GlobalBool("swarm-master"),
Discovery: c.GlobalString("swarm-discovery"),
Address: c.GlobalString("swarm-addr"),
Host: c.GlobalString("swarm-host"),
IsSwarm: c.Bool("swarm"),
Master: c.Bool("swarm-master"),
Discovery: c.String("swarm-discovery"),
Address: c.String("swarm-addr"),
Host: c.String("swarm-host"),
},
}
host, err := mcn.Create(name, driver, hostOptions)
host, err := mcn.Create(name, driver, hostOptions, c)
if err != nil {
log.Errorf("Error creating machine: %s", err)
log.Warn("You will want to check the provider to make sure the machine and associated resources were properly removed.")
@ -456,10 +456,10 @@ func cmdConfig(c *cli.Context) {
}
if c.Bool("swarm") {
if !cfg.swarmMaster {
if !cfg.SwarmOptions.Master {
log.Fatalf("%s is not a swarm master", cfg.machineName)
}
u, err := url.Parse(cfg.swarmHost)
u, err := url.Parse(cfg.SwarmOptions.Host)
if err != nil {
log.Fatal(err)
}
@ -563,13 +563,14 @@ func cmdLs(c *cli.Context) {
swarmInfo := make(map[string]string)
for _, host := range hostList {
swarmOptions := host.HostOptions.SwarmOptions
if !quiet {
if host.SwarmOptions.Master {
swarmMasters[host.SwarmOptions.Discovery] = host.Name
if swarmOptions.Master {
swarmMasters[swarmOptions.Discovery] = host.Name
}
if host.SwarmOptions.Discovery != "" {
swarmInfo[host.Name] = host.SwarmOptions.Discovery
if swarmOptions.Discovery != "" {
swarmInfo[host.Name] = swarmOptions.Discovery
}
go getHostState(*host, defaultStore, hostListItems)
@ -596,9 +597,9 @@ func cmdLs(c *cli.Context) {
swarmInfo := ""
if item.SwarmDiscovery != "" {
swarmInfo = swarmMasters[item.SwarmDiscovery]
if item.SwarmMaster {
if item.SwarmOptions.Discovery != "" {
swarmInfo = swarmMasters[item.SwarmOptions.Discovery]
if item.SwarmOptions.Master {
swarmInfo = fmt.Sprintf("%s (master)", swarmInfo)
}
}
@ -674,10 +675,10 @@ func cmdEnv(c *cli.Context) {
dockerHost := cfg.machineUrl
if c.Bool("swarm") {
if !cfg.swarmMaster {
if !cfg.SwarmOptions.Master {
log.Fatalf("%s is not a swarm master", cfg.machineName)
}
u, err := url.Parse(cfg.swarmHost)
u, err := url.Parse(cfg.SwarmOptions.Host)
if err != nil {
log.Fatal(err)
}
@ -1032,13 +1033,12 @@ func getHostState(host libmachine.Host, store libmachine.Store, hostListItems ch
}
hostListItems <- hostListItem{
Name: host.Name,
Active: isActive,
DriverName: host.Driver.DriverName(),
State: currentState,
URL: url,
SwarmMaster: host.SwarmOptions.Master,
SwarmDiscovery: host.SwarmOptions.Discovery,
Name: host.Name,
Active: isActive,
DriverName: host.Driver.DriverName(),
State: currentState,
URL: url,
SwarmOptions: *host.HostOptions.SwarmOptions,
}
}
@ -1096,16 +1096,15 @@ func getMachineConfig(c *cli.Context) (*machineConfig, error) {
return &machineConfig{
machineName: name,
machineDir: machineDir,
caCertPath: caCert,
caKeyPath: caKey,
clientCertPath: clientCert,
clientKeyPath: clientKey,
serverCertPath: serverCert,
serverKeyPath: serverKey,
machineUrl: machineUrl,
swarmMaster: machine.SwarmOptions.Master,
swarmHost: machine.SwarmOptions.Host,
swarmDiscovery: machine.SwarmOptions.Discovery,
clientKeyPath: clientKey,
clientCertPath: clientCert,
serverCertPath: serverCert,
caKeyPath: caKey,
caCertPath: caCert,
serverKeyPath: serverKey,
AuthOptions: *machine.HostOptions.AuthOptions,
SwarmOptions: *machine.HostOptions.SwarmOptions,
}, nil
}
@ -1113,7 +1112,7 @@ func getMachineConfig(c *cli.Context) (*machineConfig, error) {
// codegangsta/cli will not set the cert paths if the storage-path
// is set to something different so we cannot use the paths
// in the global options. le sigh.
func getCertPathInfo(c *cli.Context) certPathInfo {
func getCertPathInfo(c *cli.Context) libmachine.CertPathInfo {
// setup cert paths
caCertPath := c.GlobalString("tls-ca-cert")
caKeyPath := c.GlobalString("tls-ca-key")
@ -1136,7 +1135,7 @@ func getCertPathInfo(c *cli.Context) certPathInfo {
clientKeyPath = filepath.Join(utils.GetMachineCertDir(), "key.pem")
}
return certPathInfo{
return libmachine.CertPathInfo{
CaCertPath: caCertPath,
CaKeyPath: caKeyPath,
ClientCertPath: clientCertPath,

View File

@ -7,17 +7,16 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/codegangsta/cli"
drivers "github.com/docker/machine/drivers"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/provider"
"github.com/docker/machine/state"
)
@ -88,7 +87,16 @@ func getDefaultTestHost() (*libmachine.Host, error) {
Discovery: "",
Address: "",
}
host, err := libmachine.NewHost(hostTestName, hostTestDriverName, hostTestStorePath, hostTestCaCert, hostTestPrivateKey, engineOptions, swarmOptions)
authOptions := &auth.AuthOptions{
CaCertPath: hostTestCaCert,
PrivateKeyPath: hostTestPrivateKey,
}
hostOptions := &libmachine.HostOptions{
EngineOptions: engineOptions,
SwarmOptions: swarmOptions,
AuthOptions: authOptions,
}
host, err := libmachine.NewHost(hostTestName, hostTestDriverName, hostOptions)
if err != nil {
return nil, err
}
@ -116,113 +124,6 @@ func (d DriverOptionsMock) Int(key string) int {
func (d DriverOptionsMock) Bool(key string) bool {
return d.Data[key].(bool)
}
type FakeDriver struct {
MockState state.State
}
func (d *FakeDriver) DriverName() string {
return "fakedriver"
}
func (d *FakeDriver) AuthorizePort(ports []*drivers.Port) error {
return nil
}
func (d *FakeDriver) DeauthorizePort(ports []*drivers.Port) error {
return nil
}
func (d *FakeDriver) SetConfigFromFlags(flags drivers.DriverOptions) error {
return nil
}
func (d *FakeDriver) GetURL() (string, error) {
return "", nil
}
func (d *FakeDriver) GetMachineName() string {
return ""
}
func (d *FakeDriver) GetProviderType() provider.ProviderType {
return provider.None
}
func (d *FakeDriver) GetIP() (string, error) {
return "", nil
}
func (d *FakeDriver) GetSSHHostname() (string, error) {
return "", nil
}
func (d *FakeDriver) GetSSHKeyPath() string {
return ""
}
func (d *FakeDriver) GetSSHPort() (int, error) {
return 0, nil
}
func (d *FakeDriver) GetSSHUsername() string {
return ""
}
func (d *FakeDriver) GetState() (state.State, error) {
return d.MockState, nil
}
func (d *FakeDriver) PreCreateCheck() error {
return nil
}
func (d *FakeDriver) Create() error {
return nil
}
func (d *FakeDriver) Remove() error {
return nil
}
func (d *FakeDriver) Start() error {
d.MockState = state.Running
return nil
}
func (d *FakeDriver) Stop() error {
d.MockState = state.Stopped
return nil
}
func (d *FakeDriver) Restart() error {
return nil
}
func (d *FakeDriver) Kill() error {
return nil
}
func (d *FakeDriver) Upgrade() error {
return nil
}
func (d *FakeDriver) StartDocker() error {
return nil
}
func (d *FakeDriver) StopDocker() error {
return nil
}
func (d *FakeDriver) GetDockerConfigDir() string {
return ""
}
func (d *FakeDriver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
return &exec.Cmd{}, nil
}
func TestGetHostState(t *testing.T) {
defer cleanup()
@ -237,40 +138,46 @@ func TestGetHostState(t *testing.T) {
{
Name: "foo",
DriverName: "fakedriver",
Driver: &FakeDriver{
Driver: &fakedriver.FakeDriver{
MockState: state.Running,
},
StorePath: store.GetPath(),
SwarmOptions: &swarm.SwarmOptions{
Master: false,
Address: "",
Discovery: "",
HostOptions: &libmachine.HostOptions{
SwarmOptions: &swarm.SwarmOptions{
Master: false,
Address: "",
Discovery: "",
},
},
},
{
Name: "bar",
DriverName: "fakedriver",
Driver: &FakeDriver{
Driver: &fakedriver.FakeDriver{
MockState: state.Stopped,
},
StorePath: store.GetPath(),
SwarmOptions: &swarm.SwarmOptions{
Master: false,
Address: "",
Discovery: "",
HostOptions: &libmachine.HostOptions{
SwarmOptions: &swarm.SwarmOptions{
Master: false,
Address: "",
Discovery: "",
},
},
},
{
Name: "baz",
DriverName: "fakedriver",
Driver: &FakeDriver{
Driver: &fakedriver.FakeDriver{
MockState: state.Running,
},
StorePath: store.GetPath(),
SwarmOptions: &swarm.SwarmOptions{
Master: false,
Address: "",
Discovery: "",
HostOptions: &libmachine.HostOptions{
SwarmOptions: &swarm.SwarmOptions{
Master: false,
Address: "",
Discovery: "",
},
},
},
}
@ -315,7 +222,7 @@ func TestRunActionForeachMachine(t *testing.T) {
{
Name: "foo",
DriverName: "fakedriver",
Driver: &FakeDriver{
Driver: &fakedriver.FakeDriver{
MockState: state.Running,
},
StorePath: storePath,
@ -323,7 +230,7 @@ func TestRunActionForeachMachine(t *testing.T) {
{
Name: "bar",
DriverName: "fakedriver",
Driver: &FakeDriver{
Driver: &fakedriver.FakeDriver{
MockState: state.Stopped,
},
StorePath: storePath,
@ -335,7 +242,7 @@ func TestRunActionForeachMachine(t *testing.T) {
// virtualbox... (to test serial actions)
// It's actually FakeDriver!
DriverName: "virtualbox",
Driver: &FakeDriver{
Driver: &fakedriver.FakeDriver{
MockState: state.Stopped,
},
StorePath: storePath,
@ -343,7 +250,7 @@ func TestRunActionForeachMachine(t *testing.T) {
{
Name: "spam",
DriverName: "virtualbox",
Driver: &FakeDriver{
Driver: &fakedriver.FakeDriver{
MockState: state.Running,
},
StorePath: storePath,
@ -351,7 +258,7 @@ func TestRunActionForeachMachine(t *testing.T) {
{
Name: "eggs",
DriverName: "fakedriver",
Driver: &FakeDriver{
Driver: &fakedriver.FakeDriver{
MockState: state.Stopped,
},
StorePath: storePath,
@ -359,7 +266,7 @@ func TestRunActionForeachMachine(t *testing.T) {
{
Name: "ham",
DriverName: "fakedriver",
Driver: &FakeDriver{
Driver: &fakedriver.FakeDriver{
MockState: state.Running,
},
StorePath: storePath,
@ -428,7 +335,6 @@ func TestCmdConfig(t *testing.T) {
flags := getTestDriverFlags()
hostOptions := &libmachine.HostOptions{
DriverOptions: flags,
EngineOptions: &engine.EngineOptions{},
SwarmOptions: &swarm.SwarmOptions{
Master: false,
@ -436,9 +342,10 @@ func TestCmdConfig(t *testing.T) {
Address: "",
Host: "",
},
AuthOptions: &auth.AuthOptions{},
}
host, err := mcn.Create("test-a", "none", hostOptions)
host, err := mcn.Create("test-a", "none", hostOptions, flags)
if err != nil {
t.Fatal(err)
}
@ -525,7 +432,6 @@ func TestCmdEnvBash(t *testing.T) {
}
hostOptions := &libmachine.HostOptions{
DriverOptions: flags,
EngineOptions: &engine.EngineOptions{},
SwarmOptions: &swarm.SwarmOptions{
Master: false,
@ -533,9 +439,10 @@ func TestCmdEnvBash(t *testing.T) {
Address: "",
Host: "",
},
AuthOptions: &auth.AuthOptions{},
}
host, err := mcn.Create("test-a", "none", hostOptions)
host, err := mcn.Create("test-a", "none", hostOptions, flags)
if err != nil {
t.Fatal(err)
}
@ -620,7 +527,6 @@ func TestCmdEnvFish(t *testing.T) {
}
hostOptions := &libmachine.HostOptions{
DriverOptions: flags,
EngineOptions: &engine.EngineOptions{},
SwarmOptions: &swarm.SwarmOptions{
Master: false,
@ -628,9 +534,10 @@ func TestCmdEnvFish(t *testing.T) {
Address: "",
Host: "",
},
AuthOptions: &auth.AuthOptions{},
}
host, err := mcn.Create("test-a", "none", hostOptions)
host, err := mcn.Create("test-a", "none", hostOptions, flags)
if err != nil {
t.Fatal(err)
}

View File

@ -158,6 +158,7 @@ func (e *EC2) awsApiCall(v url.Values) (*http.Response, error) {
fmt.Printf("client encountered error while doing the request: %s", err.Error())
return resp, fmt.Errorf("client encountered error while doing the request: %s", err)
}
if resp.StatusCode != http.StatusOK {
return resp, newAwsApiResponseError(*resp)
}

View File

@ -0,0 +1,115 @@
package fakedriver
import (
"os/exec"
"github.com/docker/machine/drivers"
"github.com/docker/machine/provider"
"github.com/docker/machine/state"
)
type FakeDriver struct {
MockState state.State
}
func (d *FakeDriver) DriverName() string {
return "fakedriver"
}
func (d *FakeDriver) AuthorizePort(ports []*drivers.Port) error {
return nil
}
func (d *FakeDriver) DeauthorizePort(ports []*drivers.Port) error {
return nil
}
func (d *FakeDriver) SetConfigFromFlags(flags drivers.DriverOptions) error {
return nil
}
func (d *FakeDriver) GetURL() (string, error) {
return "", nil
}
func (d *FakeDriver) GetMachineName() string {
return ""
}
func (d *FakeDriver) GetProviderType() provider.ProviderType {
return provider.None
}
func (d *FakeDriver) GetIP() (string, error) {
return "1.2.3.4", nil
}
func (d *FakeDriver) GetSSHHostname() (string, error) {
return "", nil
}
func (d *FakeDriver) GetSSHKeyPath() string {
return ""
}
func (d *FakeDriver) GetSSHPort() (int, error) {
return 0, nil
}
func (d *FakeDriver) GetSSHUsername() string {
return ""
}
func (d *FakeDriver) GetState() (state.State, error) {
return d.MockState, nil
}
func (d *FakeDriver) PreCreateCheck() error {
return nil
}
func (d *FakeDriver) Create() error {
return nil
}
func (d *FakeDriver) Remove() error {
return nil
}
func (d *FakeDriver) Start() error {
d.MockState = state.Running
return nil
}
func (d *FakeDriver) Stop() error {
d.MockState = state.Stopped
return nil
}
func (d *FakeDriver) Restart() error {
return nil
}
func (d *FakeDriver) Kill() error {
return nil
}
func (d *FakeDriver) Upgrade() error {
return nil
}
func (d *FakeDriver) StartDocker() error {
return nil
}
func (d *FakeDriver) StopDocker() error {
return nil
}
func (d *FakeDriver) GetDockerConfigDir() string {
return ""
}
func (d *FakeDriver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
return &exec.Cmd{}, nil
}

View File

@ -0,0 +1 @@
package fakedriver

14
libmachine/auth/auth.go Normal file
View File

@ -0,0 +1,14 @@
package auth
type AuthOptions struct {
StorePath string
CaCertPath string
CaCertRemotePath string
ServerCertPath string
ServerKeyPath string
ClientKeyPath string
ServerCertRemotePath string
ServerKeyRemotePath string
PrivateKeyPath string
ClientCertPath string
}

View File

@ -0,0 +1 @@
package auth

10
libmachine/certs.go Normal file
View File

@ -0,0 +1,10 @@
package libmachine
type CertPathInfo struct {
CaCertPath string
CaKeyPath string
ClientCertPath string
ClientKeyPath string
ServerCertPath string
ServerKeyPath string
}

View File

@ -8,8 +8,6 @@ import (
"strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/utils"
)
@ -34,7 +32,7 @@ func (s Filestore) loadHost(name string) (*Host, error) {
return nil, err
}
h := validateHost(host)
h := ValidateHost(host)
return h, nil
}
@ -149,22 +147,3 @@ func (s Filestore) RemoveActive() error {
func (s Filestore) activePath() string {
return filepath.Join(utils.GetMachineDir(), ".active")
}
// validates host config and modifies if needed
// this is used for configuration updates
func validateHost(host *Host) *Host {
if host.EngineOptions == nil {
host.EngineOptions = &engine.EngineOptions{}
}
if host.SwarmOptions == nil {
host.SwarmOptions = &swarm.SwarmOptions{
Address: "",
Discovery: host.SwarmDiscovery,
Host: host.SwarmHost,
Master: host.SwarmMaster,
}
}
return host
}

View File

@ -1,26 +1,20 @@
package libmachine
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/docker/machine/drivers"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/provision"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/provider"
"github.com/docker/machine/ssh"
"github.com/docker/machine/state"
"github.com/docker/machine/utils"
@ -31,74 +25,58 @@ var (
validHostNamePattern = regexp.MustCompile(`^` + validHostNameChars + `+$`)
)
const (
swarmDockerImage = "swarm:latest"
swarmDiscoveryServiceEndpoint = "https://discovery-stage.hub.docker.com/v1"
)
type Host struct {
Name string `json:"-"`
DriverName string
Driver drivers.Driver
Name string `json:"-"`
DriverName string
Driver drivers.Driver
StorePath string
HostOptions *HostOptions
// deprecated options; these are left to assist in config migrations
SwarmHost string
SwarmMaster bool
SwarmDiscovery string
CaCertPath string
PrivateKeyPath string
ServerCertPath string
ServerKeyPath string
ClientCertPath string
StorePath string
EngineOptions *engine.EngineOptions
SwarmOptions *swarm.SwarmOptions
// deprecated options; these are left to assist in config migrations
SwarmHost string
SwarmMaster bool
SwarmDiscovery string
ClientKeyPath string
}
type HostOptions struct {
Driver string
Memory int
Disk int
DriverOptions drivers.DriverOptions
EngineOptions *engine.EngineOptions
SwarmOptions *swarm.SwarmOptions
AuthOptions *auth.AuthOptions
}
type DockerConfig struct {
EngineConfig string
EngineConfigPath string
type HostMetadata struct {
DriverName string
HostOptions HostOptions
StorePath string
CaCertPath string
PrivateKeyPath string
ServerCertPath string
ServerKeyPath string
ClientCertPath string
}
type hostConfig struct {
DriverName string
}
func waitForDocker(addr string) error {
for {
conn, err := net.DialTimeout("tcp", addr, time.Second*5)
if err != nil {
time.Sleep(time.Second * 5)
continue
}
conn.Close()
break
}
return nil
}
func NewHost(name, driverName, StorePath, caCert, privateKey string, engineOptions *engine.EngineOptions, swarmOptions *swarm.SwarmOptions) (*Host, error) {
driver, err := drivers.NewDriver(driverName, name, StorePath, caCert, privateKey)
func NewHost(name, driverName string, hostOptions *HostOptions) (*Host, error) {
authOptions := hostOptions.AuthOptions
storePath := filepath.Join(utils.GetMachineDir(), name)
driver, err := drivers.NewDriver(driverName, name, storePath, authOptions.CaCertPath, authOptions.PrivateKeyPath)
if err != nil {
return nil, err
}
return &Host{
Name: name,
DriverName: driverName,
Driver: driver,
CaCertPath: caCert,
PrivateKeyPath: privateKey,
EngineOptions: engineOptions,
SwarmOptions: swarmOptions,
StorePath: StorePath,
Name: name,
DriverName: driverName,
Driver: driver,
StorePath: storePath,
HostOptions: hostOptions,
}, nil
}
@ -121,373 +99,7 @@ func ValidateHostName(name string) (string, error) {
return name, nil
}
func (h *Host) GetDockerConfigDir() (string, error) {
// TODO: this will be refactored in https://github.com/docker/machine/issues/699
switch h.Driver.GetProviderType() {
case provider.Local:
return "/var/lib/boot2docker", nil
case provider.Remote:
return "/etc/docker", nil
case provider.None:
return "", nil
default:
return "", ErrUnknownProviderType
}
}
func (h *Host) ConfigureSwarm(discovery string, master bool, host string, addr string) error {
d := h.Driver
if d.DriverName() == "none" {
return nil
}
if addr == "" {
ip, err := d.GetIP()
if err != nil {
return err
}
// TODO: remove hardcoded port
addr = fmt.Sprintf("%s:2376", ip)
}
basePath, err := h.GetDockerConfigDir()
if err != nil {
return err
}
tlsCaCert := path.Join(basePath, "ca.pem")
tlsCert := path.Join(basePath, "server.pem")
tlsKey := path.Join(basePath, "server-key.pem")
masterArgs := fmt.Sprintf("--tlsverify --tlscacert=%s --tlscert=%s --tlskey=%s -H %s %s",
tlsCaCert, tlsCert, tlsKey, host, discovery)
nodeArgs := fmt.Sprintf("--addr %s %s", addr, discovery)
u, err := url.Parse(host)
if err != nil {
return err
}
parts := strings.Split(u.Host, ":")
port := parts[1]
if err := waitForDocker(addr); err != nil {
return err
}
cmd, err := h.GetSSHCommand(fmt.Sprintf("sudo docker pull %s", swarmDockerImage))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
dockerDir, err := h.GetDockerConfigDir()
if err != nil {
return err
}
// if master start master agent
if master {
log.Debug("launching swarm master")
log.Debugf("master args: %s", masterArgs)
cmd, err = h.GetSSHCommand(fmt.Sprintf("sudo docker run -d -p %s:%s --restart=always --name swarm-agent-master -v %s:%s %s manage %s",
port, port, dockerDir, dockerDir, swarmDockerImage, masterArgs))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
}
// start node agent
log.Debug("launching swarm node")
log.Debugf("node args: %s", nodeArgs)
cmd, err = h.GetSSHCommand(fmt.Sprintf("sudo docker run -d --restart=always --name swarm-agent -v %s:%s %s join %s",
dockerDir, dockerDir, swarmDockerImage, nodeArgs))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (h *Host) StartDocker() error {
log.Debug("Starting Docker...")
var (
cmd *exec.Cmd
err error
)
switch h.Driver.GetProviderType() {
case provider.Local:
cmd, err = h.GetSSHCommand("sudo /etc/init.d/docker start")
case provider.Remote:
cmd, err = h.GetSSHCommand("sudo service docker start")
default:
return ErrUnknownProviderType
}
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (h *Host) StopDocker() error {
log.Debug("Stopping Docker...")
var (
cmd *exec.Cmd
err error
)
switch h.Driver.GetProviderType() {
case provider.Local:
cmd, err = h.GetSSHCommand("if [ -e /var/run/docker.pid ] && [ -d /proc/$(cat /var/run/docker.pid) ]; then sudo /etc/init.d/docker stop ; exit 0; fi")
case provider.Remote:
cmd, err = h.GetSSHCommand("sudo service docker stop")
default:
return ErrUnknownProviderType
}
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (h *Host) ConfigureAuth() error {
d := h.Driver
if d.DriverName() == "none" {
return nil
}
// copy certs to client dir for docker client
machineDir := filepath.Join(utils.GetMachineDir(), h.Name)
if err := utils.CopyFile(h.CaCertPath, filepath.Join(machineDir, "ca.pem")); err != nil {
log.Fatalf("Error copying ca.pem to machine dir: %s", err)
}
clientCertPath := filepath.Join(utils.GetMachineCertDir(), "cert.pem")
if err := utils.CopyFile(clientCertPath, filepath.Join(machineDir, "cert.pem")); err != nil {
log.Fatalf("Error copying cert.pem to machine dir: %s", err)
}
clientKeyPath := filepath.Join(utils.GetMachineCertDir(), "key.pem")
if err := utils.CopyFile(clientKeyPath, filepath.Join(machineDir, "key.pem")); err != nil {
log.Fatalf("Error copying key.pem to machine dir: %s", err)
}
var (
ip = ""
ipErr error
maxRetries = 4
)
for i := 0; i < maxRetries; i++ {
ip, ipErr = h.Driver.GetIP()
if ip != "" {
break
}
log.Debugf("waiting for ip: %s", ipErr)
time.Sleep(5 * time.Second)
}
if ipErr != nil {
return ipErr
}
if ip == "" {
return fmt.Errorf("unable to get machine IP")
}
serverCertPath := filepath.Join(h.StorePath, "server.pem")
serverKeyPath := filepath.Join(h.StorePath, "server-key.pem")
org := h.Name
bits := 2048
log.Debugf("generating server cert: %s ca-key=%s private-key=%s org=%s",
serverCertPath,
h.CaCertPath,
h.PrivateKeyPath,
org,
)
if err := utils.GenerateCert([]string{ip}, serverCertPath, serverKeyPath, h.CaCertPath, h.PrivateKeyPath, org, bits); err != nil {
return fmt.Errorf("error generating server cert: %s", err)
}
if err := h.StopDocker(); err != nil {
return err
}
dockerDir, err := h.GetDockerConfigDir()
if err != nil {
return err
}
cmd, err := h.GetSSHCommand(fmt.Sprintf("sudo mkdir -p %s", dockerDir))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
// upload certs and configure TLS auth
caCert, err := ioutil.ReadFile(h.CaCertPath)
if err != nil {
return err
}
// due to windows clients, we cannot use filepath.Join as the paths
// will be mucked on the linux hosts
machineCaCertPath := path.Join(dockerDir, "ca.pem")
serverCert, err := ioutil.ReadFile(serverCertPath)
if err != nil {
return err
}
machineServerCertPath := path.Join(dockerDir, "server.pem")
serverKey, err := ioutil.ReadFile(serverKeyPath)
if err != nil {
return err
}
machineServerKeyPath := path.Join(dockerDir, "server-key.pem")
cmd, err = h.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee %s", string(caCert), machineCaCertPath))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
cmd, err = h.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee %s", string(serverKey), machineServerKeyPath))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
cmd, err = h.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee %s", string(serverCert), machineServerCertPath))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
dockerUrl, err := h.Driver.GetURL()
if err != nil {
return err
}
u, err := url.Parse(dockerUrl)
if err != nil {
return err
}
dockerPort := 2376
parts := strings.Split(u.Host, ":")
if len(parts) == 2 {
dPort, err := strconv.Atoi(parts[1])
if err != nil {
return err
}
dockerPort = dPort
}
cfg, err := h.generateDockerConfig(dockerPort, machineCaCertPath, machineServerKeyPath, machineServerCertPath)
if err != nil {
return err
}
cmd, err = h.GetSSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a %s", cfg.EngineConfig, cfg.EngineConfigPath))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
if err := h.StartDocker(); err != nil {
return err
}
return nil
}
func (h *Host) generateDockerConfig(dockerPort int, caCertPath string, serverKeyPath string, serverCertPath string) (*DockerConfig, error) {
d := h.Driver
var (
daemonOpts string
daemonOptsCfg string
daemonCfg string
swarmLabels = []string{}
)
swarmLabels = append(swarmLabels, fmt.Sprintf("--label=provider=%s", h.Driver.DriverName()))
defaultDaemonOpts := fmt.Sprintf(`--tlsverify --tlscacert=%s --tlskey=%s --tlscert=%s %s`,
caCertPath,
serverKeyPath,
serverCertPath,
strings.Join(swarmLabels, " "),
)
dockerDir, err := h.GetDockerConfigDir()
if err != nil {
return nil, err
}
switch d.DriverName() {
case "virtualbox", "vmwarefusion", "vmwarevsphere", "hyper-v":
daemonOpts = fmt.Sprintf("-H tcp://0.0.0.0:%d", dockerPort)
daemonOptsCfg = path.Join(dockerDir, "profile")
opts := fmt.Sprintf("%s %s", defaultDaemonOpts, daemonOpts)
daemonCfg = fmt.Sprintf(`EXTRA_ARGS='%s'
CACERT=%s
SERVERCERT=%s
SERVERKEY=%s
DOCKER_TLS=no`, opts, caCertPath, serverKeyPath, serverCertPath)
default:
daemonOpts = fmt.Sprintf("--host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:%d", dockerPort)
daemonOptsCfg = "/etc/default/docker"
opts := fmt.Sprintf("%s %s", defaultDaemonOpts, daemonOpts)
daemonCfg = fmt.Sprintf("export DOCKER_OPTS=\\\"%s\\\"", opts)
}
return &DockerConfig{
EngineConfig: daemonCfg,
EngineConfigPath: daemonOptsCfg,
}, nil
}
func (h *Host) Create(name string) error {
name, err := ValidateHostName(name)
if err != nil {
return err
}
// create the instance
if err := h.Driver.Create(); err != nil {
return err
@ -498,45 +110,20 @@ func (h *Host) Create(name string) error {
return err
}
// set hostname
if err := h.SetHostname(); err != nil {
return err
}
// TODO: Not really a fan of just checking "none" here.
if h.Driver.DriverName() != "none" {
if err := WaitForSSH(h); err != nil {
return err
}
// install docker
if err := h.Provision(); err != nil {
return err
}
provisioner, err := provision.DetectProvisioner(h.Driver)
if err != nil {
return err
}
return nil
}
func (h *Host) Provision() error {
// "local" providers use b2d; no provisioning necessary
switch h.Driver.DriverName() {
case "none", "virtualbox", "vmwarefusion", "vmwarevsphere":
return nil
}
if err := WaitForSSH(h); err != nil {
return err
}
// install docker - until cloudinit we use ubuntu everywhere so we
// just install it using the docker repos
cmd, err := h.GetSSHCommand("if [ ! -e /usr/bin/docker ]; then curl -sSL https://get.docker.com | sh -; fi")
if err != nil {
return err
}
// HACK: the script above will output debug to stderr; we save it and
// then check if the command returned an error; if so, we show the debug
var buf bytes.Buffer
cmd.Stderr = &buf
if err := cmd.Run(); err != nil {
return fmt.Errorf("error installing docker: %s\n%s\n", err, string(buf.Bytes()))
if err := provisioner.Provision(*h.HostOptions.SwarmOptions, *h.HostOptions.AuthOptions); err != nil {
return err
}
}
return nil
@ -561,48 +148,6 @@ func (h *Host) GetSSHCommand(args ...string) (*exec.Cmd, error) {
return cmd, nil
}
func (h *Host) SetHostname() error {
var (
cmd *exec.Cmd
err error
)
log.Debugf("setting hostname for provider type %s: %s",
h.Driver.GetProviderType(),
h.Name,
)
switch h.Driver.GetProviderType() {
case provider.None:
return nil
case provider.Local:
cmd, err = h.GetSSHCommand(fmt.Sprintf(
"sudo hostname %s && echo \"%s\" | sudo tee /var/lib/boot2docker/etc/hostname",
h.Name,
h.Name,
))
case provider.Remote:
cmd, err = h.GetSSHCommand(fmt.Sprintf(
"echo \"127.0.0.1 %s\" | sudo tee -a /etc/hosts && sudo hostname %s && echo \"%s\" | sudo tee /etc/hostname",
h.Name,
h.Name,
h.Name,
))
default:
return ErrUnknownProviderType
}
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (h *Host) MachineInState(desiredState state.State) func() bool {
return func() bool {
currentState, err := h.Driver.GetState()
@ -719,15 +264,20 @@ func (h *Host) LoadConfig() error {
}
// First pass: find the driver name and load the driver
var config hostConfig
if err := json.Unmarshal(data, &config); err != nil {
var hostMetadata HostMetadata
if err := json.Unmarshal(data, &hostMetadata); err != nil {
return err
}
driver, err := drivers.NewDriver(config.DriverName, h.Name, h.StorePath, h.CaCertPath, h.PrivateKeyPath)
meta := ValidateHostMetadata(&hostMetadata)
authOptions := meta.HostOptions.AuthOptions
driver, err := drivers.NewDriver(hostMetadata.DriverName, h.Name, h.StorePath, authOptions.CaCertPath, authOptions.PrivateKeyPath)
if err != nil {
return err
}
h.Driver = driver
// Second pass: unmarshal driver config into correct driver
@ -738,6 +288,19 @@ func (h *Host) LoadConfig() error {
return nil
}
func (h *Host) ConfigureAuth() error {
provisioner, err := provision.DetectProvisioner(h.Driver)
if err != nil {
return err
}
if err := provision.ConfigureAuth(provisioner, *h.HostOptions.AuthOptions); err != nil {
return err
}
return nil
}
func (h *Host) SaveConfig() error {
data, err := json.Marshal(h)
if err != nil {

View File

@ -4,11 +4,11 @@ import (
"fmt"
"io/ioutil"
"os"
"regexp"
"strings"
"testing"
_ "github.com/docker/machine/drivers/none"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/swarm"
)
@ -58,14 +58,20 @@ func getTestDriverFlags() *DriverOptionsMock {
}
func getDefaultTestHost() (*Host, error) {
engineOptions := &engine.EngineOptions{}
swarmOptions := &swarm.SwarmOptions{
Master: false,
Host: "",
Discovery: "",
Address: "",
hostOptions := &HostOptions{
EngineOptions: &engine.EngineOptions{},
SwarmOptions: &swarm.SwarmOptions{
Master: false,
Host: "",
Discovery: "",
Address: "",
},
AuthOptions: &auth.AuthOptions{
CaCertPath: hostTestCaCert,
PrivateKeyPath: hostTestPrivateKey,
},
}
host, err := NewHost(hostTestName, hostTestDriverName, hostTestStorePath, hostTestCaCert, hostTestPrivateKey, engineOptions, swarmOptions)
host, err := NewHost(hostTestName, hostTestDriverName, hostOptions)
if err != nil {
return nil, err
}
@ -90,6 +96,7 @@ func TestLoadHostExists(t *testing.T) {
if err != nil {
t.Fatal(err)
}
authOptions := host.HostOptions.AuthOptions
if host.Name != hostTestName {
t.Fatalf("expected name %s; received %s", hostTestName, host.Name)
}
@ -98,12 +105,12 @@ func TestLoadHostExists(t *testing.T) {
t.Fatalf("expected driver %s; received %s", hostTestDriverName, host.DriverName)
}
if host.CaCertPath != hostTestCaCert {
t.Fatalf("expected ca cert path %s; received %s", hostTestCaCert, host.CaCertPath)
if authOptions.CaCertPath != hostTestCaCert {
t.Fatalf("expected ca cert path %s; received %s", hostTestCaCert, authOptions.CaCertPath)
}
if host.PrivateKeyPath != hostTestPrivateKey {
t.Fatalf("expected key path %s; received %s", hostTestPrivateKey, host.PrivateKeyPath)
if authOptions.PrivateKeyPath != hostTestPrivateKey {
t.Fatalf("expected key path %s; received %s", hostTestPrivateKey, authOptions.PrivateKeyPath)
}
}
@ -141,138 +148,7 @@ func TestValidateHostnameInvalid(t *testing.T) {
}
}
func TestGenerateDockerConfigNonLocal(t *testing.T) {
host, err := getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
dockerPort := 1234
caCertPath := "/test/ca-cert"
serverKeyPath := "/test/server-key"
serverCertPath := "/test/server-cert"
engineConfigPath := "/etc/default/docker"
dockerCfg, err := host.generateDockerConfig(dockerPort, caCertPath, serverKeyPath, serverCertPath)
if err != nil {
t.Fatal(err)
}
if dockerCfg.EngineConfigPath != engineConfigPath {
t.Fatalf("expected engine path %s; received %s", engineConfigPath, dockerCfg.EngineConfigPath)
}
if strings.Index(dockerCfg.EngineConfig, fmt.Sprintf("--host=tcp://0.0.0.0:%d", dockerPort)) == -1 {
t.Fatalf("--host docker port invalid; expected %d", dockerPort)
}
if strings.Index(dockerCfg.EngineConfig, fmt.Sprintf("--tlscacert=%s", caCertPath)) == -1 {
t.Fatalf("--tlscacert option invalid; expected %s", caCertPath)
}
if strings.Index(dockerCfg.EngineConfig, fmt.Sprintf("--tlskey=%s", serverKeyPath)) == -1 {
t.Fatalf("--tlskey option invalid; expected %s", serverKeyPath)
}
if strings.Index(dockerCfg.EngineConfig, fmt.Sprintf("--tlscert=%s", serverCertPath)) == -1 {
t.Fatalf("--tlscert option invalid; expected %s", serverCertPath)
}
}
func TestMachinePort(t *testing.T) {
dockerPort := 2376
bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort)
store, err := getTestStore()
if err != nil {
t.Fatal(err)
}
host, err := getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
if err = store.Save(host); err != nil {
t.Fatal(err)
}
host, err = store.Get(hostTestName)
if err != nil {
t.Fatal(err)
}
cfg, err := host.generateDockerConfig(dockerPort, "", "", "")
if err != nil {
t.Fatal(err)
}
re := regexp.MustCompile("--host=tcp://.*:(.+)")
m := re.FindStringSubmatch(cfg.EngineConfig)
if len(m) == 0 {
t.Errorf("could not find port %d in engine config", dockerPort)
}
b := m[0]
u := strings.Split(b, "=")
url := u[1]
url = strings.Replace(url, "'", "", -1)
url = strings.Replace(url, "\\\"", "", -1)
if url != bindUrl {
t.Errorf("expected url %s; received %s", bindUrl, url)
}
if err := store.Remove(hostTestName, true); err != nil {
t.Fatal(err)
}
}
func TestMachineCustomPort(t *testing.T) {
dockerPort := 3376
bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort)
store, err := getTestStore()
if err != nil {
t.Fatal(err)
}
host, err := getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
if err = store.Save(host); err != nil {
t.Fatal(err)
}
host, err = store.Get(hostTestName)
if err != nil {
t.Fatal(err)
}
cfg, err := host.generateDockerConfig(dockerPort, "", "", "")
if err != nil {
t.Fatal(err)
}
re := regexp.MustCompile("--host=tcp://.*:(.+)")
m := re.FindStringSubmatch(cfg.EngineConfig)
if len(m) == 0 {
t.Errorf("could not find port %d in engine config", dockerPort)
}
b := m[0]
u := strings.Split(b, "=")
url := u[1]
url = strings.Replace(url, "'", "", -1)
url = strings.Replace(url, "\\\"", "", -1)
if url != bindUrl {
t.Errorf("expected url %s; received %s", bindUrl, url)
}
if err := store.Remove(hostTestName, true); err != nil {
t.Fatal(err)
}
}
func TestHostConfig(t *testing.T) {
func TestHostOptions(t *testing.T) {
store, err := getTestStore()
if err != nil {
t.Fatal(err)

View File

@ -5,7 +5,7 @@ import (
"os"
"path/filepath"
log "github.com/Sirupsen/logrus"
"github.com/docker/machine/drivers"
"github.com/docker/machine/utils"
)
@ -19,11 +19,7 @@ func New(store Store) (*Machine, error) {
}, nil
}
func (m *Machine) Create(name string, driverName string, options *HostOptions) (*Host, error) {
driverOptions := options.DriverOptions
engineOptions := options.EngineOptions
swarmOptions := options.SwarmOptions
func (m *Machine) Create(name string, driverName string, hostOptions *HostOptions, driverConfig drivers.DriverOptions) (*Host, error) {
exists, err := m.store.Exists(name)
if err != nil {
return nil, err
@ -34,22 +30,12 @@ func (m *Machine) Create(name string, driverName string, options *HostOptions) (
hostPath := filepath.Join(utils.GetMachineDir(), name)
caCert, err := m.store.GetCACertificatePath()
if err != nil {
return nil, err
}
privateKey, err := m.store.GetPrivateKeyPath()
if err != nil {
return nil, err
}
host, err := NewHost(name, driverName, hostPath, caCert, privateKey, engineOptions, swarmOptions)
host, err := NewHost(name, driverName, hostOptions)
if err != nil {
return host, err
}
if driverOptions != nil {
if err := host.Driver.SetConfigFromFlags(driverOptions); err != nil {
if driverConfig != nil {
if err := host.Driver.SetConfigFromFlags(driverConfig); err != nil {
return host, err
}
}
@ -70,22 +56,6 @@ func (m *Machine) Create(name string, driverName string, options *HostOptions) (
return host, err
}
if err := host.ConfigureAuth(); err != nil {
return host, err
}
if swarmOptions.Host != "" {
log.Info("Configuring Swarm...")
discovery := swarmOptions.Discovery
master := swarmOptions.Master
swarmHost := swarmOptions.Host
addr := swarmOptions.Address
if err := host.ConfigureSwarm(discovery, master, swarmHost, addr); err != nil {
log.Errorf("Error configuring Swarm: %s", err)
}
}
if err := m.store.SetActive(host); err != nil {
return nil, err
}

View File

@ -0,0 +1,139 @@
package provision
import (
"bytes"
"fmt"
"os/exec"
"path"
"github.com/docker/machine/drivers"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/provision/pkgaction"
"github.com/docker/machine/libmachine/swarm"
)
func init() {
Register("boot2docker", &RegisteredProvisioner{
New: NewBoot2DockerProvisioner,
})
}
func NewBoot2DockerProvisioner(d drivers.Driver) Provisioner {
return &Boot2DockerProvisioner{
Driver: d,
}
}
type Boot2DockerProvisioner struct {
OsReleaseInfo *OsRelease
Driver drivers.Driver
SwarmOptions swarm.SwarmOptions
}
func (provisioner *Boot2DockerProvisioner) Service(name string, action pkgaction.ServiceAction) error {
var (
cmd *exec.Cmd
err error
)
if name == "docker" && action == pkgaction.Stop {
cmd, err = provisioner.SSHCommand("if [ -e /var/run/docker.pid ] && [ -d /proc/$(cat /var/run/docker.pid) ]; then sudo /etc/init.d/docker stop ; exit 0; fi")
} else {
cmd, err = provisioner.SSHCommand(fmt.Sprintf("sudo /etc/init.d/%s %s", name, action.String()))
if err != nil {
return err
}
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (provisioner *Boot2DockerProvisioner) Package(name string, action pkgaction.PackageAction) error {
return nil
}
func (provisioner *Boot2DockerProvisioner) Hostname() (string, error) {
cmd, err := provisioner.SSHCommand(fmt.Sprintf("hostname"))
if err != nil {
return "", err
}
var so bytes.Buffer
cmd.Stdout = &so
if err := cmd.Run(); err != nil {
return "", err
}
return so.String(), nil
}
func (provisioner *Boot2DockerProvisioner) SetHostname(hostname string) error {
cmd, err := provisioner.SSHCommand(fmt.Sprintf(
"sudo hostname %s && echo %q | sudo tee /var/lib/boot2docker/etc/hostname",
hostname,
hostname,
))
if err != nil {
return err
}
return cmd.Run()
}
func (provisioner *Boot2DockerProvisioner) GetDockerOptionsDir() string {
return "/var/lib/boot2docker"
}
func (provisioner *Boot2DockerProvisioner) GenerateDockerOptions(dockerPort int, authOptions auth.AuthOptions) (*DockerOptions, error) {
defaultDaemonOpts := getDefaultDaemonOpts(provisioner.Driver.DriverName(), authOptions)
daemonOpts := fmt.Sprintf("-H tcp://0.0.0.0:%d", dockerPort)
daemonOptsDir := path.Join(provisioner.GetDockerOptionsDir(), "profile")
opts := fmt.Sprintf("%s %s", defaultDaemonOpts, daemonOpts)
daemonCfg := fmt.Sprintf(`EXTRA_ARGS='%s'
CACERT=%s
SERVERCERT=%s
SERVERKEY=%s
DOCKER_TLS=no`, opts, authOptions.CaCertRemotePath, authOptions.ServerCertRemotePath, authOptions.ServerKeyRemotePath)
return &DockerOptions{
EngineOptions: daemonCfg,
EngineOptionsPath: daemonOptsDir,
}, nil
}
func (provisioner *Boot2DockerProvisioner) CompatibleWithHost() bool {
return provisioner.OsReleaseInfo.Id == "boot2docker"
}
func (provisioner *Boot2DockerProvisioner) SetOsReleaseInfo(info *OsRelease) {
provisioner.OsReleaseInfo = info
}
func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions) error {
if err := provisioner.SetHostname(provisioner.Driver.GetMachineName()); err != nil {
return err
}
if err := installDockerGeneric(provisioner); err != nil {
return err
}
if err := ConfigureAuth(provisioner, authOptions); err != nil {
return err
}
if err := configureSwarm(provisioner, swarmOptions); err != nil {
return err
}
return nil
}
func (provisioner *Boot2DockerProvisioner) SSHCommand(args ...string) (*exec.Cmd, error) {
return drivers.GetSSHCommandFromDriver(provisioner.Driver, args...)
}
func (provisioner *Boot2DockerProvisioner) GetDriver() drivers.Driver {
return provisioner.Driver
}

View File

@ -0,0 +1,11 @@
package provision
import (
"errors"
)
var (
ErrDetectionFailed = errors.New("OS type not recognized")
ErrSSHCommandFailed = errors.New("SSH command failure")
ErrNotImplemented = errors.New("Runtime not implemented")
)

View File

@ -0,0 +1,83 @@
package provision
import (
"bufio"
"bytes"
"fmt"
"reflect"
"strings"
log "github.com/Sirupsen/logrus"
)
// The /etc/os-release file contains operating system identification data
// See http://www.freedesktop.org/software/systemd/man/os-release.html for more details
// Values in this struct must always be string
// or the reflection will not work properly.
type OsRelease struct {
AnsiColor string `osr:"ANSI_COLOR"`
Name string `osr:"NAME"`
Version string `osr:"VERSION"`
Id string `osr:"ID"`
IdLike string `osr:"ID_LIKE"`
PrettyName string `osr:"PRETTY_NAME"`
VersionId string `osr:"VERSION_ID"`
HomeUrl string `osr:"HOME_URL"`
SupportUrl string `osr:"SUPPORT_URL"`
BugReportUrl string `osr:"BUG_REPORT_URL"`
}
func stripQuotes(val string) string {
if val[0] == '"' {
return val[1 : len(val)-1]
}
return val
}
func (osr *OsRelease) setIfPossible(key, val string) error {
v := reflect.ValueOf(osr).Elem()
for i := 0; i < v.NumField(); i++ {
fieldValue := v.Field(i)
fieldType := v.Type().Field(i)
originalName := fieldType.Tag.Get("osr")
if key == originalName && fieldValue.Kind() == reflect.String {
fieldValue.SetString(val)
return nil
}
}
return fmt.Errorf("Couldn't set key %s, no corresponding struct field found", key)
}
func parseLine(osrLine string) (string, string, error) {
vals := strings.Split(osrLine, "=")
if len(vals) != 2 {
return "", "", fmt.Errorf("Expected %s to split by '=' char into two strings, instead got %d strings", osrLine, len(vals))
}
key := vals[0]
val := stripQuotes(vals[1])
return key, val, nil
}
func (osr *OsRelease) ParseOsRelease(osReleaseContents []byte) error {
r := bytes.NewReader(osReleaseContents)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
key, val, err := parseLine(scanner.Text())
if err != nil {
return err
}
if err := osr.setIfPossible(key, val); err != nil {
log.Debug(err)
}
}
return nil
}
func NewOsRelease(contents []byte) (*OsRelease, error) {
osr := &OsRelease{}
if err := osr.ParseOsRelease(contents); err != nil {
return nil, err
}
return osr, nil
}

View File

@ -0,0 +1,138 @@
package provision
import (
"reflect"
"testing"
)
func TestParseOsRelease(t *testing.T) {
// These example osr files stolen shamelessly from
// https://github.com/docker/docker/blob/master/pkg/parsers/operatingsystem/operatingsystem_test.go
// cheers @tiborvass
var (
ubuntuTrusty = []byte(`NAME="Ubuntu"
VERSION="14.04, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`)
gentoo = []byte(`NAME=Gentoo
ID=gentoo
PRETTY_NAME="Gentoo/Linux"
ANSI_COLOR="1;32"
HOME_URL="http://www.gentoo.org/"
SUPPORT_URL="http://www.gentoo.org/main/en/support.xml"
BUG_REPORT_URL="https://bugs.gentoo.org/"
`)
noPrettyName = []byte(`NAME="Ubuntu"
VERSION="14.04, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`)
)
osr, err := NewOsRelease(ubuntuTrusty)
if err != nil {
t.Fatal("Unexpected error parsing os release: %s", err)
}
expectedOsr := OsRelease{
AnsiColor: "",
Name: "Ubuntu",
Version: "14.04, Trusty Tahr",
Id: "ubuntu",
IdLike: "debian",
PrettyName: "Ubuntu 14.04 LTS",
VersionId: "14.04",
HomeUrl: "http://www.ubuntu.com/",
SupportUrl: "http://help.ubuntu.com/",
BugReportUrl: "http://bugs.launchpad.net/ubuntu/",
}
if !reflect.DeepEqual(*osr, expectedOsr) {
t.Fatal("Error with ubuntu osr parsing: structs do not match")
}
osr, err = NewOsRelease(gentoo)
if err != nil {
t.Fatal("Unexpected error parsing os release: %s", err)
}
expectedOsr = OsRelease{
AnsiColor: "1;32",
Name: "Gentoo",
Version: "",
Id: "gentoo",
IdLike: "",
PrettyName: "Gentoo/Linux",
VersionId: "",
HomeUrl: "http://www.gentoo.org/",
SupportUrl: "http://www.gentoo.org/main/en/support.xml",
BugReportUrl: "https://bugs.gentoo.org/",
}
if !reflect.DeepEqual(*osr, expectedOsr) {
t.Fatal("Error with gentoo osr parsing: structs do not match")
}
osr, err = NewOsRelease(noPrettyName)
if err != nil {
t.Fatal("Unexpected error parsing os release: %s", err)
}
expectedOsr = OsRelease{
AnsiColor: "",
Name: "Ubuntu",
Version: "14.04, Trusty Tahr",
Id: "ubuntu",
IdLike: "debian",
PrettyName: "",
VersionId: "14.04",
HomeUrl: "http://www.ubuntu.com/",
SupportUrl: "http://help.ubuntu.com/",
BugReportUrl: "http://bugs.launchpad.net/ubuntu/",
}
if !reflect.DeepEqual(*osr, expectedOsr) {
t.Fatal("Error with noPrettyName osr parsing: structs do not match")
}
}
func TestParseLine(t *testing.T) {
var (
withQuotes = "ID=\"ubuntu\""
withoutQuotes = "ID=gentoo"
wtf = "LOTS=OF=EQUALS"
)
key, val, err := parseLine(withQuotes)
if key != "ID" {
t.Fatalf("Expected ID, got %s", key)
}
if val != "ubuntu" {
t.Fatalf("Expected ubuntu, got %s", val)
}
if err != nil {
t.Fatalf("Got error on parseLine with quotes: %s", err)
}
key, val, err = parseLine(withoutQuotes)
if key != "ID" {
t.Fatalf("Expected ID, got %s", key)
}
if val != "gentoo" {
t.Fatalf("Expected gentoo, got %s", val)
}
if err != nil {
t.Fatalf("Got error on parseLine without quotes: %s", err)
}
key, val, err = parseLine(wtf)
if err == nil {
t.Fatal("Expected to get an error on parseLine, got nil")
}
}

View File

@ -0,0 +1,43 @@
package pkgaction
type ServiceAction int
const (
Restart ServiceAction = iota
Start
Stop
)
var serviceActions = []string{
"restart",
"start",
"stop",
}
func (s ServiceAction) String() string {
if int(s) >= 0 && int(s) < len(serviceActions) {
return serviceActions[s]
}
return ""
}
type PackageAction int
const (
Install PackageAction = iota
Remove
)
var packageActions = []string{
"install",
"remove",
}
func (s PackageAction) String() string {
if int(s) >= 0 && int(s) < len(packageActions) {
return packageActions[s]
}
return ""
}

View File

@ -0,0 +1,9 @@
package pkgaction
import "testing"
func TestActionValue(t *testing.T) {
if Install.String() != "install" {
t.Fatal("Expected %q but got %q", "install", Install.String())
}
}

View File

@ -0,0 +1,100 @@
package provision
import (
"bytes"
"fmt"
"os/exec"
"github.com/docker/machine/drivers"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/provision/pkgaction"
"github.com/docker/machine/libmachine/swarm"
)
var provisioners = make(map[string]*RegisteredProvisioner)
// Distribution specific actions
type Provisioner interface {
// Create the files for the daemon to consume configuration settings (return struct of content and path)
GenerateDockerOptions(dockerPort int, authOptions auth.AuthOptions) (*DockerOptions, error)
// Get the directory where the settings files for docker are to be found
GetDockerOptionsDir() string
// Run a package action e.g. install
Package(name string, action pkgaction.PackageAction) error
// Get Hostname
Hostname() (string, error)
// Set hostname
SetHostname(hostname string) error
// Figure out if this is the right provisioner to use based on /etc/os-release info
CompatibleWithHost() bool
// Do the actual provisioning piece:
// 1. Set the hostname on the instance.
// 2. Install Docker if it is not present.
// 3. Configure the daemon to accept connections over TLS.
// 4. Copy the needed certificates to the server and local config dir.
// 5. Configure / activate swarm if applicable.
Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions) error
// Perform action on a named service e.g. stop
Service(name string, action pkgaction.ServiceAction) error
// Get the driver which is contained in the provisioner.
GetDriver() drivers.Driver
// Short-hand for accessing an SSH command from the driver.
SSHCommand(args ...string) (*exec.Cmd, error)
// Set the OS Release info depending on how it's represented
// internally
SetOsReleaseInfo(info *OsRelease)
}
// Detection
type RegisteredProvisioner struct {
New func(d drivers.Driver) Provisioner
}
func Register(name string, p *RegisteredProvisioner) {
provisioners[name] = p
}
func DetectProvisioner(d drivers.Driver) (Provisioner, error) {
var (
osReleaseOut bytes.Buffer
)
catOsReleaseCmd, err := drivers.GetSSHCommandFromDriver(d, "cat /etc/os-release")
if err != nil {
return nil, fmt.Errorf("Error getting SSH command: %s", err)
}
// Normally I would just use Output() for this, but d.GetSSHCommand
// defaults to sending the output of the command to stdout in debug
// mode, so that will be broken if we don't set it ourselves.
catOsReleaseCmd.Stdout = &osReleaseOut
if err := catOsReleaseCmd.Run(); err != nil {
return nil, fmt.Errorf("Error running SSH command to get /etc/os-release: %s", err)
}
osReleaseInfo, err := NewOsRelease(osReleaseOut.Bytes())
if err != nil {
return nil, fmt.Errorf("Error parsing /etc/os-release file: %s", err)
}
for _, p := range provisioners {
provisioner := p.New(d)
provisioner.SetOsReleaseInfo(osReleaseInfo)
if provisioner.CompatibleWithHost() {
return provisioner, nil
}
}
return nil, ErrDetectionFailed
}

View File

@ -0,0 +1,162 @@
package provision
import (
"bytes"
"fmt"
"os/exec"
"github.com/docker/machine/drivers"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/provision/pkgaction"
"github.com/docker/machine/libmachine/swarm"
)
func init() {
Register("Ubuntu", &RegisteredProvisioner{
New: NewUbuntuProvisioner,
})
}
func NewUbuntuProvisioner(d drivers.Driver) Provisioner {
return &UbuntuProvisioner{
packages: []string{
"curl",
},
Driver: d,
}
}
type UbuntuProvisioner struct {
packages []string
OsReleaseInfo *OsRelease
Driver drivers.Driver
SwarmOptions swarm.SwarmOptions
}
func (provisioner *UbuntuProvisioner) Service(name string, action pkgaction.ServiceAction) error {
command := fmt.Sprintf("sudo service %s %s", name, action.String())
cmd, err := provisioner.SSHCommand(command)
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (provisioner *UbuntuProvisioner) Package(name string, action pkgaction.PackageAction) error {
var packageAction string
switch action {
case pkgaction.Install:
packageAction = "install"
case pkgaction.Remove:
packageAction = "remove"
}
command := fmt.Sprintf("DEBIAN_FRONTEND=noninteractive sudo -E apt-get %s -y %s", packageAction, name)
cmd, err := provisioner.SSHCommand(command)
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (provisioner *UbuntuProvisioner) Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions) error {
if err := provisioner.SetHostname(provisioner.Driver.GetMachineName()); err != nil {
return err
}
for _, pkg := range provisioner.packages {
if err := provisioner.Package(pkg, pkgaction.Install); err != nil {
return err
}
}
if err := installDockerGeneric(provisioner); err != nil {
return err
}
if err := ConfigureAuth(provisioner, authOptions); err != nil {
return err
}
if err := configureSwarm(provisioner, swarmOptions); err != nil {
return err
}
return nil
}
func (provisioner *UbuntuProvisioner) Hostname() (string, error) {
cmd, err := provisioner.SSHCommand("hostname")
if err != nil {
return "", err
}
var so bytes.Buffer
cmd.Stdout = &so
if err := cmd.Run(); err != nil {
return "", err
}
return so.String(), nil
}
func (provisioner *UbuntuProvisioner) SetHostname(hostname string) error {
cmd, err := provisioner.SSHCommand(fmt.Sprintf(
"sudo hostname %s && echo %q | sudo tee /etc/hostname && echo \"127.0.0.1 %s\" | sudo tee -a /etc/hosts",
hostname,
hostname,
hostname,
))
if err != nil {
return err
}
return cmd.Run()
}
func (provisioner *UbuntuProvisioner) GetDockerOptionsDir() string {
return "/etc/docker"
}
func (provisioner *UbuntuProvisioner) SSHCommand(args ...string) (*exec.Cmd, error) {
return drivers.GetSSHCommandFromDriver(provisioner.Driver, args...)
}
func (provisioner *UbuntuProvisioner) CompatibleWithHost() bool {
return provisioner.OsReleaseInfo.Id == "ubuntu"
}
func (provisioner *UbuntuProvisioner) SetOsReleaseInfo(info *OsRelease) {
provisioner.OsReleaseInfo = info
}
func (provisioner *UbuntuProvisioner) GenerateDockerOptions(dockerPort int, authOptions auth.AuthOptions) (*DockerOptions, error) {
defaultDaemonOpts := getDefaultDaemonOpts(provisioner.Driver.DriverName(), authOptions)
daemonOpts := fmt.Sprintf("--host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:%d", dockerPort)
daemonOptsDir := "/etc/default/docker"
opts := fmt.Sprintf("%s %s", defaultDaemonOpts, daemonOpts)
daemonCfg := fmt.Sprintf("export DOCKER_OPTS=\\\"%s\\\"", opts)
return &DockerOptions{
EngineOptions: daemonCfg,
EngineOptionsPath: daemonOptsDir,
}, nil
}
func (provisioner *UbuntuProvisioner) GetDriver() drivers.Driver {
return provisioner.Driver
}

View File

@ -0,0 +1,274 @@
package provision
import (
"bytes"
"fmt"
"io/ioutil"
"net/url"
"path"
"path/filepath"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/provision/pkgaction"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/utils"
)
type DockerOptions struct {
EngineOptions string
EngineOptionsPath string
}
func installDockerGeneric(p Provisioner) error {
// install docker - until cloudinit we use ubuntu everywhere so we
// just install it using the docker repos
cmd, err := p.SSHCommand("if ! type docker; then curl -sSL https://get.docker.com | sh -; fi")
if err != nil {
return err
}
// HACK: the script above will output debug to stderr; we save it and
// then check if the command returned an error; if so, we show the debug
var buf bytes.Buffer
cmd.Stderr = &buf
if err := cmd.Run(); err != nil {
return fmt.Errorf("error installing docker: %s\n%s\n", err, string(buf.Bytes()))
}
return nil
}
func ConfigureAuth(p Provisioner, authOptions auth.AuthOptions) error {
var (
err error
)
machineName := p.GetDriver().GetMachineName()
org := machineName
bits := 2048
ip, err := p.GetDriver().GetIP()
if err != nil {
return err
}
// copy certs to client dir for docker client
machineDir := filepath.Join(utils.GetMachineDir(), machineName)
if err := utils.CopyFile(authOptions.CaCertPath, filepath.Join(machineDir, "ca.pem")); err != nil {
log.Fatalf("Error copying ca.pem to machine dir: %s", err)
}
if err := utils.CopyFile(authOptions.ClientCertPath, filepath.Join(machineDir, "cert.pem")); err != nil {
log.Fatalf("Error copying cert.pem to machine dir: %s", err)
}
if err := utils.CopyFile(authOptions.ClientKeyPath, filepath.Join(machineDir, "key.pem")); err != nil {
log.Fatalf("Error copying key.pem to machine dir: %s", err)
}
log.Debugf("generating server cert: %s ca-key=%s private-key=%s org=%s",
authOptions.ServerCertPath,
authOptions.CaCertPath,
authOptions.PrivateKeyPath,
org,
)
// TODO: Switch to passing just authOptions to this func
// instead of all these individual fields
err = utils.GenerateCert(
[]string{ip},
authOptions.ServerCertPath,
authOptions.ServerKeyPath,
authOptions.CaCertPath,
authOptions.PrivateKeyPath,
org,
bits,
)
if err != nil {
return fmt.Errorf("error generating server cert: %s", err)
}
if err := p.Service("docker", pkgaction.Stop); err != nil {
return err
}
dockerDir := p.GetDockerOptionsDir()
cmd, err := p.SSHCommand(fmt.Sprintf("sudo mkdir -p %s", dockerDir))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
// upload certs and configure TLS auth
caCert, err := ioutil.ReadFile(authOptions.CaCertPath)
if err != nil {
return err
}
// due to windows clients, we cannot use filepath.Join as the paths
// will be mucked on the linux hosts
machineCaCertPath := path.Join(dockerDir, "ca.pem")
authOptions.CaCertRemotePath = machineCaCertPath
serverCert, err := ioutil.ReadFile(authOptions.ServerCertPath)
if err != nil {
return err
}
machineServerCertPath := path.Join(dockerDir, "server.pem")
authOptions.ServerCertRemotePath = machineServerCertPath
serverKey, err := ioutil.ReadFile(authOptions.ServerKeyPath)
if err != nil {
return err
}
machineServerKeyPath := path.Join(dockerDir, "server-key.pem")
authOptions.ServerKeyRemotePath = machineServerKeyPath
cmd, err = p.SSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee %s", string(caCert), machineCaCertPath))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
cmd, err = p.SSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee %s", string(serverKey), machineServerKeyPath))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
cmd, err = p.SSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee %s", string(serverCert), machineServerCertPath))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
dockerUrl, err := p.GetDriver().GetURL()
if err != nil {
return err
}
u, err := url.Parse(dockerUrl)
if err != nil {
return err
}
dockerPort := 2376
parts := strings.Split(u.Host, ":")
if len(parts) == 2 {
dPort, err := strconv.Atoi(parts[1])
if err != nil {
return err
}
dockerPort = dPort
}
dkrcfg, err := p.GenerateDockerOptions(dockerPort, authOptions)
if err != nil {
return err
}
cmd, err = p.SSHCommand(fmt.Sprintf("echo \"%s\" | sudo tee -a %s", dkrcfg.EngineOptions, dkrcfg.EngineOptionsPath))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
if err := p.Service("docker", pkgaction.Start); err != nil {
return err
}
return nil
}
func getDefaultDaemonOpts(driverName string, authOptions auth.AuthOptions) string {
return fmt.Sprintf(`--tlsverify --tlscacert=%s --tlskey=%s --tlscert=%s %s`,
authOptions.CaCertRemotePath,
authOptions.ServerKeyRemotePath,
authOptions.ServerCertRemotePath,
fmt.Sprintf("--label=provider=%s", driverName),
)
}
func configureSwarm(p Provisioner, swarmOptions swarm.SwarmOptions) error {
if !swarmOptions.IsSwarm {
return nil
}
basePath := p.GetDockerOptionsDir()
ip, err := p.GetDriver().GetIP()
if err != nil {
return err
}
tlsCaCert := path.Join(basePath, "ca.pem")
tlsCert := path.Join(basePath, "server.pem")
tlsKey := path.Join(basePath, "server-key.pem")
masterArgs := fmt.Sprintf("--tlsverify --tlscacert=%s --tlscert=%s --tlskey=%s -H %s %s",
tlsCaCert, tlsCert, tlsKey, swarmOptions.Host, swarmOptions.Discovery)
nodeArgs := fmt.Sprintf("--addr %s:2376 %s", ip, swarmOptions.Discovery)
u, err := url.Parse(swarmOptions.Host)
if err != nil {
return err
}
parts := strings.Split(u.Host, ":")
port := parts[1]
// TODO: Do not hardcode daemon port, ask the driver
if err := utils.WaitForDocker(ip, 2376); err != nil {
return err
}
cmd, err := p.SSHCommand(fmt.Sprintf("sudo docker pull %s", swarm.DockerImage))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
dockerDir := p.GetDockerOptionsDir()
// if master start master agent
if swarmOptions.Master {
log.Debug("launching swarm master")
log.Debugf("master args: %s", masterArgs)
cmd, err = p.SSHCommand(fmt.Sprintf("sudo docker run -d -p %s:%s --restart=always --name swarm-agent-master -v %s:%s %s manage %s",
port, port, dockerDir, dockerDir, swarm.DockerImage, masterArgs))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
}
// start node agent
log.Debug("launching swarm node")
log.Debugf("node args: %s", nodeArgs)
cmd, err = p.SSHCommand(fmt.Sprintf("sudo docker run -d --restart=always --name swarm-agent -v %s:%s %s join %s",
dockerDir, dockerDir, swarm.DockerImage, nodeArgs))
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,114 @@
package provision
import (
"fmt"
"regexp"
"strings"
"testing"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine/auth"
)
func TestGenerateDockerOptionsBoot2Docker(t *testing.T) {
p := &Boot2DockerProvisioner{
Driver: &fakedriver.FakeDriver{},
}
dockerPort := 1234
authOptions := auth.AuthOptions{
CaCertRemotePath: "/test/ca-cert",
ServerKeyRemotePath: "/test/server-key",
ServerCertRemotePath: "/test/server-cert",
}
engineConfigPath := "/var/lib/boot2docker/profile"
dockerCfg, err := p.GenerateDockerOptions(dockerPort, authOptions)
if err != nil {
t.Fatal(err)
}
if dockerCfg.EngineOptionsPath != engineConfigPath {
t.Fatalf("expected engine path %s; received %s", engineConfigPath, dockerCfg.EngineOptionsPath)
}
if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("-H tcp://0.0.0.0:%d", dockerPort)) == -1 {
t.Fatalf("-H docker port invalid; expected %d", dockerPort)
}
if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("CACERT=%s", authOptions.CaCertRemotePath)) == -1 {
t.Fatalf("CACERT option invalid; expected %s", authOptions.CaCertRemotePath)
}
if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERKEY=%s", authOptions.ServerKeyRemotePath)) == -1 {
t.Fatalf("SERVERKEY option invalid; expected %s", authOptions.ServerKeyRemotePath)
}
if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERCERT=%s", authOptions.ServerCertRemotePath)) == -1 {
t.Fatalf("SERVERCERT option invalid; expected %s", authOptions.ServerCertRemotePath)
}
}
func TestMachinePortBoot2Docker(t *testing.T) {
p := &Boot2DockerProvisioner{
Driver: &fakedriver.FakeDriver{},
}
dockerPort := 2376
bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort)
authOptions := auth.AuthOptions{
CaCertRemotePath: "/test/ca-cert",
ServerKeyRemotePath: "/test/server-key",
ServerCertRemotePath: "/test/server-cert",
}
cfg, err := p.GenerateDockerOptions(dockerPort, authOptions)
if err != nil {
t.Fatal(err)
}
re := regexp.MustCompile("-H tcp://.*:(.+)")
m := re.FindStringSubmatch(cfg.EngineOptions)
if len(m) == 0 {
t.Errorf("could not find port %d in engine config", dockerPort)
}
b := m[0]
u := strings.Split(b, " ")
url := u[1]
url = strings.Replace(url, "'", "", -1)
url = strings.Replace(url, "\\\"", "", -1)
if url != bindUrl {
t.Errorf("expected url %s; received %s", bindUrl, url)
}
}
func TestMachineCustomPortBoot2Docker(t *testing.T) {
p := &Boot2DockerProvisioner{
Driver: &fakedriver.FakeDriver{},
}
dockerPort := 3376
bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort)
authOptions := auth.AuthOptions{
CaCertRemotePath: "/test/ca-cert",
ServerKeyRemotePath: "/test/server-key",
ServerCertRemotePath: "/test/server-cert",
}
cfg, err := p.GenerateDockerOptions(dockerPort, authOptions)
if err != nil {
t.Fatal(err)
}
re := regexp.MustCompile("-H tcp://.*:(.+)")
m := re.FindStringSubmatch(cfg.EngineOptions)
if len(m) == 0 {
t.Errorf("could not find port %d in engine config", dockerPort)
}
b := m[0]
u := strings.Split(b, " ")
url := u[1]
url = strings.Replace(url, "'", "", -1)
url = strings.Replace(url, "\\\"", "", -1)
if url != bindUrl {
t.Errorf("expected url %s; received %s", bindUrl, url)
}
}

View File

@ -1,6 +1,12 @@
package swarm
const (
DockerImage = "swarm:latest"
DiscoveryServiceEndpoint = "https://discovery-stage.hub.docker.com/v1"
)
type SwarmOptions struct {
IsSwarm bool
Address string
Discovery string
Master bool

View File

@ -0,0 +1 @@
package swarm

115
libmachine/validate.go Normal file
View File

@ -0,0 +1,115 @@
package libmachine
import (
"path/filepath"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/swarm"
"github.com/docker/machine/utils"
)
// validates host config and modifies if needed
// this is used for configuration updates
func ValidateHost(host *Host) *Host {
certInfo := getCertInfoFromHost(host)
if host.HostOptions == nil {
host.HostOptions = &HostOptions{}
}
if host.HostOptions.EngineOptions == nil {
host.HostOptions.EngineOptions = &engine.EngineOptions{}
}
if host.HostOptions.SwarmOptions == nil {
host.HostOptions.SwarmOptions = &swarm.SwarmOptions{
Address: "",
Discovery: host.SwarmDiscovery,
Host: host.SwarmHost,
Master: host.SwarmMaster,
}
}
host.HostOptions.AuthOptions = &auth.AuthOptions{
StorePath: host.StorePath,
CaCertPath: certInfo.CaCertPath,
CaCertRemotePath: "",
ServerCertPath: certInfo.ServerCertPath,
ServerKeyPath: certInfo.ServerKeyPath,
ClientKeyPath: certInfo.ClientKeyPath,
ServerCertRemotePath: "",
ServerKeyRemotePath: "",
PrivateKeyPath: certInfo.CaKeyPath,
ClientCertPath: certInfo.ClientCertPath,
}
return host
}
// validates host metadata and modifies if needed
// this is used for configuration updates
func ValidateHostMetadata(m *HostMetadata) *HostMetadata {
if m.HostOptions.EngineOptions == nil {
m.HostOptions.EngineOptions = &engine.EngineOptions{}
}
if m.HostOptions.AuthOptions == nil {
m.HostOptions.AuthOptions = &auth.AuthOptions{
StorePath: m.StorePath,
CaCertPath: m.CaCertPath,
CaCertRemotePath: "",
ServerCertPath: m.ServerCertPath,
ServerKeyPath: m.ServerKeyPath,
ClientKeyPath: "",
ServerCertRemotePath: "",
ServerKeyRemotePath: "",
PrivateKeyPath: m.PrivateKeyPath,
ClientCertPath: m.ClientCertPath,
}
}
return m
}
func getCertInfoFromHost(h *Host) CertPathInfo {
// setup cert paths
caCertPath := h.CaCertPath
caKeyPath := h.PrivateKeyPath
clientCertPath := h.ClientCertPath
clientKeyPath := h.ClientKeyPath
serverCertPath := h.ServerCertPath
serverKeyPath := h.ServerKeyPath
if caCertPath == "" {
caCertPath = filepath.Join(utils.GetMachineCertDir(), "ca.pem")
}
if caKeyPath == "" {
caKeyPath = filepath.Join(utils.GetMachineCertDir(), "ca-key.pem")
}
if clientCertPath == "" {
clientCertPath = filepath.Join(utils.GetMachineCertDir(), "cert.pem")
}
if clientKeyPath == "" {
clientKeyPath = filepath.Join(utils.GetMachineCertDir(), "key.pem")
}
if serverCertPath == "" {
serverCertPath = filepath.Join(utils.GetMachineCertDir(), "server.pem")
}
if serverKeyPath == "" {
serverKeyPath = filepath.Join(utils.GetMachineCertDir(), "server-key.pem")
}
return CertPathInfo{
CaCertPath: caCertPath,
CaKeyPath: caKeyPath,
ClientCertPath: clientCertPath,
ClientKeyPath: clientKeyPath,
ServerCertPath: serverCertPath,
ServerKeyPath: serverKeyPath,
}
}

View File

@ -0,0 +1,34 @@
package libmachine
import (
"os"
"reflect"
"testing"
)
// Tests a function which "prefills" certificate information for a host
// due to a schema migration from "flat" to a "nested" structure.
func TestGetCertInfoFromHost(t *testing.T) {
os.Setenv("MACHINE_STORAGE_PATH", "/tmp/migration")
host := &Host{
CaCertPath: "",
PrivateKeyPath: "",
ClientCertPath: "",
ClientKeyPath: "",
ServerCertPath: "",
ServerKeyPath: "",
}
expectedCertInfo := CertPathInfo{
CaCertPath: "/tmp/migration/certs/ca.pem",
CaKeyPath: "/tmp/migration/certs/ca-key.pem",
ClientCertPath: "/tmp/migration/certs/cert.pem",
ClientKeyPath: "/tmp/migration/certs/key.pem",
ServerCertPath: "/tmp/migration/certs/server.pem",
ServerKeyPath: "/tmp/migration/certs/server-key.pem",
}
certInfo := getCertInfoFromHost(host)
if !reflect.DeepEqual(expectedCertInfo, certInfo) {
t.Log("\n\n\n", expectedCertInfo, "\n\n\n", certInfo)
t.Fatal("Expected these structs to be equal, they were different")
}
}

View File

@ -12,6 +12,19 @@ else
ARCH="386"
fi
MACHINE_BIN_NAME=docker-machine_$PLATFORM-$ARCH
BATS_LOG=${MACHINE_ROOT}/bats.log
touch ${BATS_LOG}
rm ${BATS_LOG}
teardown() {
echo "$BATS_TEST_NAME
----------
$output
----------
" >> ${BATS_LOG}
}
build_machine() {
pushd $MACHINE_ROOT >/dev/null

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io"
"net"
"os"
"path/filepath"
"runtime"
@ -95,6 +96,18 @@ func WaitFor(f func() bool) error {
return WaitForSpecific(f, 60, 3*time.Second)
}
func WaitForDocker(ip string, daemonPort int) error {
return WaitFor(func() bool {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", ip, daemonPort))
if err != nil {
fmt.Println("Got an error it was", err)
return false
}
conn.Close()
return true
})
}
func DumpVal(vals ...interface{}) {
for _, val := range vals {
prettyJSON, err := json.MarshalIndent(val, "", " ")