initial pass at internal api

Signed-off-by: Evan Hazlett <ejhazlett@gmail.com>
This commit is contained in:
Evan Hazlett 2015-03-11 03:16:46 -04:00
parent 7a336225e1
commit 27be8cf28e
13 changed files with 770 additions and 432 deletions

View File

@ -29,6 +29,7 @@ import (
_ "github.com/docker/machine/drivers/vmwarefusion"
_ "github.com/docker/machine/drivers/vmwarevcloudair"
_ "github.com/docker/machine/drivers/vmwarevsphere"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/state"
"github.com/docker/machine/utils"
)
@ -88,7 +89,22 @@ func confirmInput(msg string) bool {
}
return false
}
func newMcn(store libmachine.Store) (*libmachine.Machine, error) {
return libmachine.New(store)
}
func getMachineDir(rootPath string) string {
return filepath.Join(rootPath, "machines")
}
func getDefaultStore(rootPath, caCertPath, privateKeyPath string) (libmachine.Store, error) {
return libmachine.NewFilestore(
rootPath,
caCertPath,
privateKeyPath,
), nil
}
func setupCertificates(caCertPath, caKeyPath, clientCertPath, clientKeyPath string) error {
@ -310,10 +326,23 @@ var Commands = []cli.Command{
func cmdActive(c *cli.Context) {
name := c.Args().First()
store := NewStore(utils.GetMachineDir(), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key"))
defaultStore, err := getDefaultStore(
c.GlobalString("storage-path"),
c.GlobalString("tls-ca-cert"),
c.GlobalString("tls-ca-key"),
)
if err != nil {
log.Fatal(err)
}
mcn, err := newMcn(defaultStore)
if err != nil {
log.Fatal(err)
}
if name == "" {
host, err := store.GetActive()
host, err := mcn.GetActive()
if err != nil {
log.Fatalf("error getting active host: %v", err)
}
@ -321,12 +350,12 @@ func cmdActive(c *cli.Context) {
fmt.Println(host.Name)
}
} else if name != "" {
host, err := store.Load(name)
host, err := mcn.Get(name)
if err != nil {
log.Fatalf("error loading host: %v", err)
}
if err := store.SetActive(host); err != nil {
if err := mcn.SetActive(host); err != nil {
log.Fatalf("error setting active host: %v", err)
}
} else {
@ -348,15 +377,27 @@ func cmdCreate(c *cli.Context) {
log.Fatalf("Error generating certificates: %s", err)
}
store := NewStore(utils.GetMachineDir(), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key"))
defaultStore, err := getDefaultStore(
c.GlobalString("storage-path"),
c.GlobalString("tls-ca-cert"),
c.GlobalString("tls-ca-key"),
)
if err != nil {
log.Fatal(err)
}
host, err := store.Create(name, driver, c)
mcn, err := newMcn(defaultStore)
if err != nil {
log.Fatal(err)
}
host, err := mcn.Create(name, driver, 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.")
log.Fatal("Error creating machine")
}
if err := store.SetActive(host); err != nil {
if err := mcn.SetActive(host); err != nil {
log.Fatalf("error setting active host: %v", err)
}
@ -462,9 +503,22 @@ func cmdIp(c *cli.Context) {
func cmdLs(c *cli.Context) {
quiet := c.Bool("quiet")
store := NewStore(utils.GetMachineDir(), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key"))
hostList, err := store.List()
defaultStore, err := getDefaultStore(
c.GlobalString("storage-path"),
c.GlobalString("tls-ca-cert"),
c.GlobalString("tls-ca-key"),
)
if err != nil {
log.Fatal(err)
}
mcn, err := newMcn(defaultStore)
if err != nil {
log.Fatal(err)
}
hostList, err := mcn.List()
if err != nil {
log.Fatal(err)
}
@ -491,7 +545,7 @@ func cmdLs(c *cli.Context) {
swarmInfo[host.Name] = host.SwarmDiscovery
}
go getHostState(host, *store, hostListItems)
go getHostState(*host, defaultStore, hostListItems)
} else {
fmt.Fprintf(w, "%s\n", host.Name)
}
@ -548,9 +602,22 @@ func cmdRm(c *cli.Context) {
isError := false
store := NewStore(utils.GetMachineDir(), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key"))
defaultStore, err := getDefaultStore(
c.GlobalString("storage-path"),
c.GlobalString("tls-ca-cert"),
c.GlobalString("tls-ca-key"),
)
if err != nil {
log.Fatal(err)
}
mcn, err := newMcn(defaultStore)
if err != nil {
log.Fatal(err)
}
for _, host := range c.Args() {
if err := store.Remove(host, force); err != nil {
if err := mcn.Remove(host, force); err != nil {
log.Errorf("Error removing machine %s: %s", host, err)
isError = true
}
@ -642,10 +709,23 @@ func cmdSsh(c *cli.Context) {
sshCmd *exec.Cmd
)
name := c.Args().First()
store := NewStore(utils.GetMachineDir(), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key"))
defaultStore, err := getDefaultStore(
c.GlobalString("storage-path"),
c.GlobalString("tls-ca-cert"),
c.GlobalString("tls-ca-key"),
)
if err != nil {
log.Fatal(err)
}
mcn, err := newMcn(defaultStore)
if err != nil {
log.Fatal(err)
}
if name == "" {
host, err := store.GetActive()
host, err := mcn.GetActive()
if err != nil {
log.Fatalf("unable to get active host: %v", err)
}
@ -653,7 +733,7 @@ func cmdSsh(c *cli.Context) {
name = host.Name
}
host, err := store.Load(name)
host, err := mcn.Get(name)
if err != nil {
log.Fatal(err)
}
@ -677,17 +757,17 @@ func cmdSsh(c *cli.Context) {
// machineCommand maps the command name to the corresponding machine command.
// We run commands concurrently and communicate back an error if there was one.
func machineCommand(actionName string, machine *Host, errorChan chan<- error) {
func machineCommand(actionName string, host *libmachine.Host, errorChan chan<- error) {
commands := map[string](func() error){
"configureAuth": machine.ConfigureAuth,
"start": machine.Start,
"stop": machine.Stop,
"restart": machine.Restart,
"kill": machine.Kill,
"upgrade": machine.Upgrade,
"configureAuth": host.ConfigureAuth,
"start": host.Start,
"stop": host.Stop,
"restart": host.Restart,
"kill": host.Kill,
"upgrade": host.Upgrade,
}
log.Debugf("command=%s machine=%s", actionName, machine.Name)
log.Debugf("command=%s machine=%s", actionName, host.Name)
if err := commands[actionName](); err != nil {
errorChan <- err
@ -698,10 +778,10 @@ func machineCommand(actionName string, machine *Host, errorChan chan<- error) {
}
// runActionForeachMachine will run the command across multiple machines
func runActionForeachMachine(actionName string, machines []*Host) {
func runActionForeachMachine(actionName string, machines []*libmachine.Host) {
var (
numConcurrentActions = 0
serialMachines = []*Host{}
serialMachines = []*libmachine.Host{}
errorChan = make(chan error)
)
@ -751,12 +831,25 @@ func runActionWithContext(actionName string, c *cli.Context) error {
// No args specified, so use active.
if len(machines) == 0 {
store := NewStore(utils.GetMachineDir(), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key"))
activeHost, err := store.GetActive()
defaultStore, err := getDefaultStore(
c.GlobalString("storage-path"),
c.GlobalString("tls-ca-cert"),
c.GlobalString("tls-ca-key"),
)
if err != nil {
log.Fatal(err)
}
mcn, err := newMcn(defaultStore)
if err != nil {
log.Fatal(err)
}
activeHost, err := mcn.GetActive()
if err != nil {
log.Fatalf("Unable to get active host: %v", err)
}
machines = []*Host{activeHost}
machines = []*libmachine.Host{activeHost}
}
runActionForeachMachine(actionName, machines)
@ -813,8 +906,8 @@ func cmdNotFound(c *cli.Context, command string) {
)
}
func getHosts(c *cli.Context) ([]*Host, error) {
machines := []*Host{}
func getHosts(c *cli.Context) ([]*libmachine.Host, error) {
machines := []*libmachine.Host{}
for _, n := range c.Args() {
machine, err := loadMachine(n, c)
if err != nil {
@ -827,23 +920,48 @@ func getHosts(c *cli.Context) ([]*Host, error) {
return machines, nil
}
func loadMachine(name string, c *cli.Context) (*Host, error) {
store := NewStore(utils.GetMachineDir(), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key"))
func loadMachine(name string, c *cli.Context) (*libmachine.Host, error) {
defaultStore, err := getDefaultStore(
c.GlobalString("storage-path"),
c.GlobalString("tls-ca-cert"),
c.GlobalString("tls-ca-key"),
)
if err != nil {
log.Fatal(err)
}
machine, err := store.Load(name)
mcn, err := newMcn(defaultStore)
if err != nil {
log.Fatal(err)
}
host, err := mcn.Get(name)
if err != nil {
return nil, err
}
return machine, nil
return host, nil
}
func getHost(c *cli.Context) *Host {
func getHost(c *cli.Context) *libmachine.Host {
name := c.Args().First()
store := NewStore(utils.GetMachineDir(), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key"))
defaultStore, err := getDefaultStore(
c.GlobalString("storage-path"),
c.GlobalString("tls-ca-cert"),
c.GlobalString("tls-ca-key"),
)
if err != nil {
log.Fatal(err)
}
mcn, err := newMcn(defaultStore)
if err != nil {
log.Fatal(err)
}
if name == "" {
host, err := store.GetActive()
host, err := mcn.GetActive()
if err != nil {
log.Fatalf("unable to get active host: %v", err)
}
@ -854,14 +972,14 @@ func getHost(c *cli.Context) *Host {
return host
}
host, err := store.Load(name)
host, err := mcn.Get(name)
if err != nil {
log.Fatalf("unable to load host: %v", err)
}
return host
}
func getHostState(host Host, store Store, hostListItems chan<- hostListItem) {
func getHostState(host libmachine.Host, store libmachine.Store, hostListItems chan<- hostListItem) {
currentState, err := host.Driver.GetState()
if err != nil {
log.Errorf("error getting state for host %s: %s", host.Name, err)
@ -895,11 +1013,24 @@ func getHostState(host Host, store Store, hostListItems chan<- hostListItem) {
func getMachineConfig(c *cli.Context) (*machineConfig, error) {
name := c.Args().First()
store := NewStore(utils.GetMachineDir(), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key"))
var machine *Host
defaultStore, err := getDefaultStore(
c.GlobalString("storage-path"),
c.GlobalString("tls-ca-cert"),
c.GlobalString("tls-ca-key"),
)
if err != nil {
log.Fatal(err)
}
mcn, err := newMcn(defaultStore)
if err != nil {
log.Fatal(err)
}
var machine *libmachine.Host
if name == "" {
m, err := store.GetActive()
m, err := mcn.GetActive()
if err != nil {
log.Fatalf("error getting active host: %v", err)
}
@ -908,7 +1039,7 @@ func getMachineConfig(c *cli.Context) (*machineConfig, error) {
}
machine = m
} else {
m, err := store.Load(name)
m, err := mcn.Get(name)
if err != nil {
return nil, fmt.Errorf("Error loading machine config: %s", err)
}

View File

@ -13,10 +13,85 @@ import (
"github.com/codegangsta/cli"
drivers "github.com/docker/machine/drivers"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/provider"
"github.com/docker/machine/state"
)
const (
hostTestName = "test-host"
hostTestDriverName = "none"
hostTestCaCert = "test-cert"
hostTestPrivateKey = "test-key"
)
var (
hostTestStorePath string
)
func getTestStore() (libmachine.Store, error) {
tmpDir, err := ioutil.TempDir("", "machine-test-")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
hostTestStorePath = tmpDir
os.Setenv("MACHINE_STORAGE_PATH", tmpDir)
return libmachine.NewFilestore(tmpDir, hostTestCaCert, hostTestPrivateKey), nil
}
func cleanup() {
os.RemoveAll(hostTestStorePath)
}
func getTestDriverFlags() *DriverOptionsMock {
name := hostTestName
flags := &DriverOptionsMock{
Data: map[string]interface{}{
"name": name,
"url": "unix:///var/run/docker.sock",
"swarm": false,
"swarm-host": "",
"swarm-master": false,
"swarm-discovery": "",
},
}
return flags
}
func getDefaultTestHost() (*libmachine.Host, error) {
host, err := libmachine.NewHost(hostTestName, hostTestDriverName, hostTestStorePath, hostTestCaCert, hostTestPrivateKey, false, "", "")
if err != nil {
return nil, err
}
flags := getTestDriverFlags()
if err := host.Driver.SetConfigFromFlags(flags); err != nil {
return nil, err
}
return host, nil
}
type DriverOptionsMock struct {
Data map[string]interface{}
}
func (d DriverOptionsMock) String(key string) string {
return d.Data[key].(string)
}
func (d DriverOptionsMock) Int(key string) int {
return d.Data[key].(int)
}
func (d DriverOptionsMock) Bool(key string) bool {
return d.Data[key].(bool)
}
type FakeDriver struct {
MockState state.State
}
@ -123,61 +198,9 @@ func (d *FakeDriver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
return &exec.Cmd{}, nil
}
func TestGetHosts(t *testing.T) {
if err := clearHosts(); err != nil {
t.Fatal(err)
}
os.Setenv("MACHINE_STORAGE_PATH", TestStoreDir)
flags := getDefaultTestDriverFlags()
store := NewStore(TestMachineDir, "", "")
var err error
_, err = store.Create("test-a", "none", flags)
if err != nil {
t.Fatal(err)
}
_, err = store.Create("test-b", "none", flags)
if err != nil {
t.Fatal(err)
}
storeHosts, err := store.List()
if len(storeHosts) != 2 {
t.Fatalf("List returned %d items", len(storeHosts))
}
set := flag.NewFlagSet("start", 0)
set.Parse([]string{"test-a", "test-b"})
globalSet := flag.NewFlagSet("-d", 0)
globalSet.String("-d", "none", "driver")
globalSet.String("storage-path", store.Path, "storage path")
globalSet.String("tls-ca-cert", "", "")
globalSet.String("tls-ca-key", "", "")
c := cli.NewContext(nil, set, globalSet)
hosts, err := getHosts(c)
if err != nil {
t.Fatal(err)
}
if len(hosts) != 2 {
t.Fatal("Expected %d hosts, got %d hosts", 2, len(hosts))
}
os.Setenv("MACHINE_STORAGE_PATH", "")
}
func TestGetHostState(t *testing.T) {
storePath, err := ioutil.TempDir("", ".docker")
if err != nil {
t.Fatal("Error creating tmp dir:", err)
}
defer cleanup()
hostListItems := make(chan hostListItem)
store, err := getTestStore()
@ -185,14 +208,14 @@ func TestGetHostState(t *testing.T) {
t.Fatal(err)
}
hosts := []Host{
hosts := []libmachine.Host{
{
Name: "foo",
DriverName: "fakedriver",
Driver: &FakeDriver{
MockState: state.Running,
},
storePath: storePath,
StorePath: store.GetPath(),
},
{
Name: "bar",
@ -200,7 +223,7 @@ func TestGetHostState(t *testing.T) {
Driver: &FakeDriver{
MockState: state.Stopped,
},
storePath: storePath,
StorePath: store.GetPath(),
},
{
Name: "baz",
@ -208,7 +231,7 @@ func TestGetHostState(t *testing.T) {
Driver: &FakeDriver{
MockState: state.Running,
},
storePath: storePath,
StorePath: store.GetPath(),
},
}
expected := map[string]state.State{
@ -218,7 +241,7 @@ func TestGetHostState(t *testing.T) {
}
items := []hostListItem{}
for _, host := range hosts {
go getHostState(host, *store, hostListItems)
go getHostState(host, store, hostListItems)
}
for i := 0; i < len(hosts); i++ {
items = append(items, <-hostListItems)
@ -238,14 +261,14 @@ func TestRunActionForeachMachine(t *testing.T) {
// Assume a bunch of machines in randomly started or
// stopped states.
machines := []*Host{
machines := []*libmachine.Host{
{
Name: "foo",
DriverName: "fakedriver",
Driver: &FakeDriver{
MockState: state.Running,
},
storePath: storePath,
StorePath: storePath,
},
{
Name: "bar",
@ -253,7 +276,7 @@ func TestRunActionForeachMachine(t *testing.T) {
Driver: &FakeDriver{
MockState: state.Stopped,
},
storePath: storePath,
StorePath: storePath,
},
{
Name: "baz",
@ -265,7 +288,7 @@ func TestRunActionForeachMachine(t *testing.T) {
Driver: &FakeDriver{
MockState: state.Stopped,
},
storePath: storePath,
StorePath: storePath,
},
{
Name: "spam",
@ -273,7 +296,7 @@ func TestRunActionForeachMachine(t *testing.T) {
Driver: &FakeDriver{
MockState: state.Running,
},
storePath: storePath,
StorePath: storePath,
},
{
Name: "eggs",
@ -281,7 +304,7 @@ func TestRunActionForeachMachine(t *testing.T) {
Driver: &FakeDriver{
MockState: state.Stopped,
},
storePath: storePath,
StorePath: storePath,
},
{
Name: "ham",
@ -289,7 +312,7 @@ func TestRunActionForeachMachine(t *testing.T) {
Driver: &FakeDriver{
MockState: state.Running,
},
storePath: storePath,
StorePath: storePath,
},
}
@ -332,34 +355,29 @@ func TestRunActionForeachMachine(t *testing.T) {
}
func TestCmdConfig(t *testing.T) {
defer cleanup()
stdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
os.Setenv("MACHINE_STORAGE_PATH", TestStoreDir)
defer func() {
os.Setenv("MACHINE_STORAGE_PATH", "")
os.Stdout = stdout
}()
if err := clearHosts(); err != nil {
t.Fatal(err)
}
flags := getDefaultTestDriverFlags()
store := NewStore(TestMachineDir, "", "")
var err error
_, err = store.Create("test-a", "none", flags)
store, err := getTestStore()
if err != nil {
t.Fatal(err)
}
host, err := store.Load("test-a")
host, err := getDefaultTestHost()
if err != nil {
t.Fatalf("error loading host: %v", err)
t.Fatal(err)
}
if err = store.Save(host); err != nil {
t.Fatal(err)
}
if err := store.SetActive(host); err != nil {
@ -375,7 +393,11 @@ func TestCmdConfig(t *testing.T) {
}()
set := flag.NewFlagSet("config", 0)
c := cli.NewContext(nil, set, set)
globalSet := flag.NewFlagSet("test", 0)
globalSet.String("storage-path", store.GetPath(), "")
c := cli.NewContext(nil, set, globalSet)
cmdConfig(c)
w.Close()

11
libmachine/errors.go Normal file
View File

@ -0,0 +1,11 @@
package libmachine
import (
"errors"
)
var (
ErrHostDoesNotExist = errors.New("Host does not exist")
ErrInvalidHostname = errors.New("Invalid hostname specified")
ErrUnknownProviderType = errors.New("Unknown hypervisor type")
)

148
libmachine/filestore.go Normal file
View File

@ -0,0 +1,148 @@
package libmachine
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/machine/utils"
)
type Filestore struct {
path string
caCertPath string
privateKeyPath string
}
func NewFilestore(rootPath string, caCert string, privateKey string) *Filestore {
return &Filestore{path: rootPath, caCertPath: caCert, privateKeyPath: privateKey}
}
func (s Filestore) loadHost(name string) (*Host, error) {
hostPath := filepath.Join(utils.GetMachineDir(), name)
if _, err := os.Stat(hostPath); os.IsNotExist(err) {
return nil, ErrHostDoesNotExist
}
host := &Host{Name: name, StorePath: hostPath}
if err := host.LoadConfig(); err != nil {
return nil, err
}
return host, nil
}
func (s Filestore) GetPath() string {
return s.path
}
func (s Filestore) GetCACertificatePath() (string, error) {
return s.caCertPath, nil
}
func (s Filestore) GetPrivateKeyPath() (string, error) {
return s.privateKeyPath, nil
}
func (s Filestore) Save(host *Host) error {
data, err := json.Marshal(host)
if err != nil {
return err
}
hostPath := filepath.Join(utils.GetMachineDir(), host.Name)
if err := os.MkdirAll(hostPath, 0700); err != nil {
return err
}
if err := ioutil.WriteFile(filepath.Join(hostPath, "config.json"), data, 0600); err != nil {
return err
}
return nil
}
func (s Filestore) Remove(name string, force bool) error {
hostPath := filepath.Join(utils.GetMachineDir(), name)
return os.RemoveAll(hostPath)
}
func (s Filestore) List() ([]*Host, error) {
dir, err := ioutil.ReadDir(utils.GetMachineDir())
if err != nil && !os.IsNotExist(err) {
return nil, err
}
hosts := []*Host{}
for _, file := range dir {
// don't load hidden dirs; used for configs
if file.IsDir() && strings.Index(file.Name(), ".") != 0 {
host, err := s.Get(file.Name())
if err != nil {
log.Errorf("error loading host %q: %s", file.Name(), err)
continue
}
hosts = append(hosts, host)
}
}
return hosts, nil
}
func (s Filestore) Exists(name string) (bool, error) {
_, err := os.Stat(filepath.Join(utils.GetMachineDir(), name))
if os.IsNotExist(err) {
return false, nil
} else if err == nil {
return true, nil
}
return false, err
}
func (s Filestore) Get(name string) (*Host, error) {
return s.loadHost(name)
}
func (s Filestore) GetActive() (*Host, error) {
hostName, err := ioutil.ReadFile(s.activePath())
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, err
}
return s.Get(string(hostName))
}
func (s Filestore) IsActive(host *Host) (bool, error) {
active, err := s.GetActive()
if err != nil {
return false, err
}
if active == nil {
return false, nil
}
return active.Name == host.Name, nil
}
func (s Filestore) SetActive(host *Host) error {
if err := os.MkdirAll(filepath.Dir(s.activePath()), 0700); err != nil {
return err
}
return ioutil.WriteFile(s.activePath(), []byte(host.Name), 0600)
}
func (s Filestore) RemoveActive() error {
return os.Remove(s.activePath())
}
// activePath returns the path to the file that stores the name of the
// active host
func (s Filestore) activePath() string {
return filepath.Join(utils.GetMachineDir(), ".active")
}

View File

@ -1,4 +1,4 @@
package main
package libmachine
import (
"os"
@ -6,14 +6,7 @@ import (
"testing"
_ "github.com/docker/machine/drivers/none"
)
const (
TestStoreDir = ".store-test"
)
var (
TestMachineDir = filepath.Join(TestStoreDir, "machine", "machines")
"github.com/docker/machine/utils"
)
type DriverOptionsMock struct {
@ -32,62 +25,49 @@ func (d DriverOptionsMock) Bool(key string) bool {
return d.Data[key].(bool)
}
func clearHosts() error {
return os.RemoveAll(TestStoreDir)
}
func TestStoreSave(t *testing.T) {
defer cleanup()
func getDefaultTestDriverFlags() *DriverOptionsMock {
return &DriverOptionsMock{
Data: map[string]interface{}{
"name": "test",
"url": "unix:///var/run/docker.sock",
"swarm": false,
"swarm-host": "",
"swarm-master": false,
"swarm-discovery": "",
},
}
}
func TestStoreCreate(t *testing.T) {
if err := clearHosts(); err != nil {
t.Fatal(err)
}
flags := getDefaultTestDriverFlags()
store := NewStore(TestStoreDir, "", "")
host, err := store.Create("test", "none", flags)
store, err := getTestStore()
if err != nil {
t.Fatal(err)
}
if host.Name != "test" {
t.Fatal("Host name is incorrect")
host, err := getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
path := filepath.Join(TestStoreDir, "test")
if err := store.Save(host); err != nil {
t.Fatal(err)
}
path := filepath.Join(utils.GetMachineDir(), host.Name)
if _, err := os.Stat(path); os.IsNotExist(err) {
t.Fatalf("Host path doesn't exist: %s", path)
}
}
func TestStoreRemove(t *testing.T) {
if err := clearHosts(); err != nil {
t.Fatal(err)
}
defer cleanup()
flags := getDefaultTestDriverFlags()
store := NewStore(TestStoreDir, "", "")
_, err := store.Create("test", "none", flags)
store, err := getTestStore()
if err != nil {
t.Fatal(err)
}
path := filepath.Join(TestStoreDir, "test")
host, err := getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
if err := store.Save(host); err != nil {
t.Fatal(err)
}
path := filepath.Join(utils.GetMachineDir(), host.Name)
if _, err := os.Stat(path); os.IsNotExist(err) {
t.Fatalf("Host path doesn't exist: %s", path)
}
err = store.Remove("test", false)
err = store.Remove(host.Name, false)
if err != nil {
t.Fatal(err)
}
@ -97,88 +77,117 @@ func TestStoreRemove(t *testing.T) {
}
func TestStoreList(t *testing.T) {
if err := clearHosts(); err != nil {
t.Fatal(err)
}
defer cleanup()
flags := getDefaultTestDriverFlags()
store := NewStore(TestStoreDir, "", "")
_, err := store.Create("test", "none", flags)
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)
}
hosts, err := store.List()
if len(hosts) != 1 {
t.Fatalf("List returned %d items", len(hosts))
}
if hosts[0].Name != "test" {
if hosts[0].Name != host.Name {
t.Fatalf("hosts[0] name is incorrect, got: %s", hosts[0].Name)
}
}
func TestStoreExists(t *testing.T) {
if err := clearHosts(); err != nil {
defer cleanup()
store, err := getTestStore()
if err != nil {
t.Fatal(err)
}
flags := getDefaultTestDriverFlags()
host, err := getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
store := NewStore(TestStoreDir, "", "")
exists, err := store.Exists("test")
exists, err := store.Exists(host.Name)
if exists {
t.Fatal("Exists returned true when it should have been false")
}
_, err = store.Create("test", "none", flags)
if err != nil {
t.Fatal(err)
}
exists, err = store.Exists("test")
if err := store.Save(host); err != nil {
t.Fatal(err)
}
exists, err = store.Exists(host.Name)
if err != nil {
t.Fatal(err)
}
if !exists {
t.Fatal("Exists returned false when it should have been true")
}
if err := store.Remove(host.Name, true); err != nil {
t.Fatal(err)
}
exists, err = store.Exists(host.Name)
if err != nil {
t.Fatal(err)
}
if exists {
t.Fatal("Exists returned true when it should have been false")
}
}
func TestStoreLoad(t *testing.T) {
if err := clearHosts(); err != nil {
t.Fatal(err)
}
defer cleanup()
expectedURL := "unix:///foo/baz"
flags := getDefaultTestDriverFlags()
flags := getTestDriverFlags()
flags.Data["url"] = expectedURL
store := NewStore(TestStoreDir, "", "")
_, err := store.Create("test", "none", flags)
store, err := getTestStore()
if err != nil {
t.Fatal(err)
}
store = NewStore(TestStoreDir, "", "")
host, err := store.Load("test")
if host.Name != "test" {
host, err := getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
if err := host.Driver.SetConfigFromFlags(flags); err != nil {
t.Fatal(err)
}
if err := store.Save(host); err != nil {
t.Fatal(err)
}
host, err = store.Get(host.Name)
if host.Name != host.Name {
t.Fatal("Host name is incorrect")
}
actualURL, err := host.GetURL()
if err != nil {
t.Fatal(err)
}
if actualURL != expectedURL {
t.Fatalf("GetURL is not %q, got %q", expectedURL, expectedURL)
t.Fatalf("GetURL is not %q, got %q", expectedURL, actualURL)
}
}
func TestStoreGetSetActive(t *testing.T) {
if err := clearHosts(); err != nil {
t.Fatal(err)
}
defer cleanup()
flags := getDefaultTestDriverFlags()
//store := NewStore(TestStoreDir, "", "")
store, err := getTestStore()
if err != nil {
t.Fatal(err)
@ -194,12 +203,18 @@ func TestStoreGetSetActive(t *testing.T) {
t.Fatalf("GetActive: Active host should not exist")
}
// Set normal host
originalHost, err := store.Create("test", "none", flags)
host, err = getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
// Set normal host
if err := store.Save(host); err != nil {
t.Fatal(err)
}
originalHost := host
if err := store.SetActive(originalHost); err != nil {
t.Fatal(err)
}
@ -208,7 +223,7 @@ func TestStoreGetSetActive(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if host.Name != "test" {
if host.Name != host.Name {
t.Fatalf("Active host is not 'test', got %s", host.Name)
}
isActive, err := store.IsActive(host)

View File

@ -1,9 +1,8 @@
package main
package libmachine
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
@ -26,10 +25,8 @@ import (
)
var (
validHostNameChars = `[a-zA-Z0-9\-\.]`
validHostNamePattern = regexp.MustCompile(`^` + validHostNameChars + `+$`)
ErrInvalidHostname = errors.New("Invalid hostname specified. Hostnames must be comprised only of alphanumeric characters, \".\", or \"-\".")
ErrUnknownHypervisorType = errors.New("Unknown hypervisor type")
validHostNameChars = `[a-zA-Z0-9\-\.]`
validHostNamePattern = regexp.MustCompile(`^` + validHostNameChars + `+$`)
)
const (
@ -42,14 +39,14 @@ type Host struct {
DriverName string
Driver drivers.Driver
CaCertPath string
PrivateKeyPath string
ServerCertPath string
ServerKeyPath string
PrivateKeyPath string
ClientCertPath string
SwarmMaster bool
SwarmHost string
SwarmDiscovery string
storePath string
StorePath string
}
type DockerConfig struct {
@ -74,8 +71,8 @@ func waitForDocker(addr string) error {
return nil
}
func NewHost(name, driverName, storePath, caCert, privateKey string, swarmMaster bool, swarmHost string, swarmDiscovery string) (*Host, error) {
driver, err := drivers.NewDriver(driverName, name, storePath, caCert, privateKey)
func NewHost(name, driverName, StorePath, caCert, privateKey string, swarmMaster bool, swarmHost string, swarmDiscovery string) (*Host, error) {
driver, err := drivers.NewDriver(driverName, name, StorePath, caCert, privateKey)
if err != nil {
return nil, err
}
@ -88,16 +85,16 @@ func NewHost(name, driverName, storePath, caCert, privateKey string, swarmMaster
SwarmMaster: swarmMaster,
SwarmHost: swarmHost,
SwarmDiscovery: swarmDiscovery,
storePath: storePath,
StorePath: StorePath,
}, nil
}
func LoadHost(name string, storePath string) (*Host, error) {
if _, err := os.Stat(storePath); os.IsNotExist(err) {
func LoadHost(name string, StorePath string) (*Host, error) {
if _, err := os.Stat(StorePath); os.IsNotExist(err) {
return nil, fmt.Errorf("Host %q does not exist", name)
}
host := &Host{Name: name, storePath: storePath}
host := &Host{Name: name, StorePath: StorePath}
if err := host.LoadConfig(); err != nil {
return nil, err
}
@ -121,7 +118,7 @@ func (h *Host) GetDockerConfigDir() (string, error) {
case provider.None:
return "", nil
default:
return "", ErrUnknownHypervisorType
return "", ErrUnknownProviderType
}
}
@ -221,7 +218,7 @@ func (h *Host) StartDocker() error {
case provider.Remote:
cmd, err = h.GetSSHCommand("sudo service docker start")
default:
return ErrUnknownHypervisorType
return ErrUnknownProviderType
}
if err != nil {
@ -249,7 +246,7 @@ func (h *Host) StopDocker() error {
case provider.Remote:
cmd, err = h.GetSSHCommand("sudo service docker stop")
default:
return ErrUnknownHypervisorType
return ErrUnknownProviderType
}
if err != nil {
@ -308,8 +305,8 @@ func (h *Host) ConfigureAuth() error {
return fmt.Errorf("unable to get machine IP")
}
serverCertPath := filepath.Join(h.storePath, "server.pem")
serverKeyPath := filepath.Join(h.storePath, "server-key.pem")
serverCertPath := filepath.Join(h.StorePath, "server.pem")
serverKeyPath := filepath.Join(h.StorePath, "server-key.pem")
org := h.Name
bits := 2048
@ -579,7 +576,7 @@ func (h *Host) SetHostname() error {
h.Name,
))
default:
return ErrUnknownHypervisorType
return ErrUnknownProviderType
}
if err != nil {
@ -688,14 +685,14 @@ func (h *Host) Remove(force bool) error {
}
func (h *Host) removeStorePath() error {
file, err := os.Stat(h.storePath)
file, err := os.Stat(h.StorePath)
if err != nil {
return err
}
if !file.IsDir() {
return fmt.Errorf("%q is not a directory", h.storePath)
return fmt.Errorf("%q is not a directory", h.StorePath)
}
return os.RemoveAll(h.storePath)
return os.RemoveAll(h.StorePath)
}
func (h *Host) GetURL() (string, error) {
@ -703,7 +700,7 @@ func (h *Host) GetURL() (string, error) {
}
func (h *Host) LoadConfig() error {
data, err := ioutil.ReadFile(filepath.Join(h.storePath, "config.json"))
data, err := ioutil.ReadFile(filepath.Join(h.StorePath, "config.json"))
if err != nil {
return err
}
@ -714,7 +711,7 @@ func (h *Host) LoadConfig() error {
return err
}
driver, err := drivers.NewDriver(config.DriverName, h.Name, h.storePath, h.CaCertPath, h.PrivateKeyPath)
driver, err := drivers.NewDriver(config.DriverName, h.Name, h.StorePath, h.CaCertPath, h.PrivateKeyPath)
if err != nil {
return err
}
@ -733,7 +730,8 @@ func (h *Host) SaveConfig() error {
if err != nil {
return err
}
if err := ioutil.WriteFile(filepath.Join(h.storePath, "config.json"), data, 0600); err != nil {
if err := ioutil.WriteFile(filepath.Join(h.StorePath, "config.json"), data, 0600); err != nil {
return err
}
return nil

View File

@ -1,4 +1,4 @@
package main
package libmachine
import (
"fmt"
@ -14,20 +14,30 @@ import (
const (
hostTestName = "test-host"
hostTestDriverName = "none"
hostTestStorePath = "/test/path"
hostTestCaCert = "test-cert"
hostTestPrivateKey = "test-key"
)
func getTestStore() (*Store, error) {
var (
hostTestStorePath string
)
func getTestStore() (Store, error) {
tmpDir, err := ioutil.TempDir("", "machine-test-")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
hostTestStorePath = tmpDir
os.Setenv("MACHINE_STORAGE_PATH", tmpDir)
return NewStore(tmpDir, hostTestCaCert, hostTestPrivateKey), nil
return NewFilestore(tmpDir, hostTestCaCert, hostTestPrivateKey), nil
}
func cleanup() {
os.RemoveAll(hostTestStorePath)
}
func getTestDriverFlags() *DriverOptionsMock {
@ -162,14 +172,17 @@ func TestMachinePort(t *testing.T) {
if err != nil {
t.Fatal(err)
}
flags := getTestDriverFlags()
_, err = store.Create(hostTestName, hostTestDriverName, flags)
host, err := getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
host, err := store.Load(hostTestName)
if err = store.Save(host); err != nil {
t.Fatal(err)
}
host, err = store.Get(hostTestName)
if err != nil {
t.Fatal(err)
}
@ -205,14 +218,17 @@ func TestMachineCustomPort(t *testing.T) {
if err != nil {
t.Fatal(err)
}
flags := getTestDriverFlags()
_, err = store.Create(hostTestName, hostTestDriverName, flags)
host, err := getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
host, err := store.Load(hostTestName)
if err = store.Save(host); err != nil {
t.Fatal(err)
}
host, err = store.Get(hostTestName)
if err != nil {
t.Fatal(err)
}
@ -248,12 +264,15 @@ func TestHostConfig(t *testing.T) {
t.Fatal(err)
}
flags := getTestDriverFlags()
host, err := store.Create(hostTestName, hostTestDriverName, flags)
host, err := getDefaultTestHost()
if err != nil {
t.Fatal(err)
}
if err = store.Save(host); err != nil {
t.Fatal(err)
}
if err := host.SaveConfig(); err != nil {
t.Fatal(err)
}

143
libmachine/machine.go Normal file
View File

@ -0,0 +1,143 @@
package libmachine
import (
"fmt"
"os"
"path/filepath"
log "github.com/Sirupsen/logrus"
"github.com/docker/machine/drivers"
"github.com/docker/machine/utils"
)
type Machine struct {
store Store
}
func New(store Store) (*Machine, error) {
return &Machine{
store: store,
}, nil
}
func (m *Machine) Create(name string, driverName string, flags drivers.DriverOptions) (*Host, error) {
exists, err := m.store.Exists(name)
if err != nil {
return nil, err
}
if exists {
return nil, fmt.Errorf("Machine %s already exists", name)
}
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, flags.Bool("swarm-master"), flags.String("swarm-host"), flags.String("swarm-discovery"))
if err != nil {
return host, err
}
if flags != nil {
if err := host.Driver.SetConfigFromFlags(flags); err != nil {
return host, err
}
}
if err := host.Driver.PreCreateCheck(); err != nil {
return nil, err
}
if err := os.MkdirAll(hostPath, 0700); err != nil {
return nil, err
}
if err := host.SaveConfig(); err != nil {
return host, err
}
if err := host.Create(name); err != nil {
return host, err
}
if err := host.ConfigureAuth(); err != nil {
return host, err
}
if flags.Bool("swarm") {
log.Info("Configuring Swarm...")
discovery := flags.String("swarm-discovery")
master := flags.Bool("swarm-master")
swarmHost := flags.String("swarm-host")
addr := flags.String("swarm-addr")
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
}
return host, nil
}
func (m *Machine) Exists(name string) (bool, error) {
return m.store.Exists(name)
}
func (m *Machine) GetActive() (*Host, error) {
return m.store.GetActive()
}
func (m *Machine) IsActive(host *Host) (bool, error) {
return m.store.IsActive(host)
}
func (m *Machine) List() ([]*Host, error) {
return m.store.List()
}
func (m *Machine) Get(name string) (*Host, error) {
return m.store.Get(name)
}
func (m *Machine) Remove(name string, force bool) error {
active, err := m.store.GetActive()
if err != nil {
return err
}
if active != nil && active.Name == name {
if err := m.RemoveActive(); err != nil {
return err
}
}
host, err := m.store.Get(name)
if err != nil {
return err
}
if err := host.Remove(force); err != nil {
if !force {
return err
}
}
return m.store.Remove(name, force)
}
func (m *Machine) RemoveActive() error {
return m.store.RemoveActive()
}
func (m *Machine) SetActive(host *Host) error {
return m.store.SetActive(host)
}

View File

@ -0,0 +1 @@
package libmachine

28
libmachine/store.go Normal file
View File

@ -0,0 +1,28 @@
package libmachine
type Store interface {
// Exists returns whether a machine exists or not
Exists(name string) (bool, error)
// GetActive returns the active host
GetActive() (*Host, error)
// GetPath returns the path to the store
GetPath() string
// GetCACertPath returns the CA certificate
GetCACertificatePath() (string, error)
// GetPrivateKeyPath returns the private key
GetPrivateKeyPath() (string, error)
// IsActive returns whether the host is active or not
IsActive(host *Host) (bool, error)
// List returns a list of hosts
List() ([]*Host, error)
// Load loads a host by name
Get(name string) (*Host, error)
// Remove removes a machine from the store
Remove(name string, force bool) error
// RemoveActive removes the active machine from the store
RemoveActive() error
// Save persists a machine in the store
Save(host *Host) error
// SetActive sets the specified host as the active host
SetActive(host *Host) error
}

View File

@ -22,6 +22,10 @@ func main() {
app.Name = path.Base(os.Args[0])
app.Author = "Docker Machine Contributors"
app.Email = "https://github.com/docker/machine"
app.Before = func(c *cli.Context) error {
os.Setenv("MACHINE_STORAGE_PATH", c.GlobalString("storage-path"))
return nil
}
app.Commands = Commands
app.CommandNotFound = cmdNotFound
app.Usage = "Create and manage machines running Docker."
@ -35,7 +39,7 @@ func main() {
cli.StringFlag{
EnvVar: "MACHINE_STORAGE_PATH",
Name: "storage-path",
Value: utils.GetMachineRoot(),
Value: utils.GetBaseDir(),
Usage: "Configures storage path",
},
cli.StringFlag{

178
store.go
View File

@ -1,178 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/machine/drivers"
"github.com/docker/machine/utils"
)
// Store persists hosts on the filesystem
type Store struct {
Path string
CaCertPath string
PrivateKeyPath string
}
func NewStore(rootPath string, caCert string, privateKey string) *Store {
if rootPath == "" {
rootPath = utils.GetMachineDir()
}
return &Store{Path: rootPath, CaCertPath: caCert, PrivateKeyPath: privateKey}
}
func (s *Store) Create(name string, driverName string, flags drivers.DriverOptions) (*Host, error) {
exists, err := s.Exists(name)
if err != nil {
return nil, err
}
if exists {
return nil, fmt.Errorf("Machine %s already exists", name)
}
hostPath := filepath.Join(s.Path, name)
host, err := NewHost(name, driverName, hostPath, s.CaCertPath, s.PrivateKeyPath, flags.Bool("swarm-master"), flags.String("swarm-host"), flags.String("swarm-discovery"))
if err != nil {
return host, err
}
if flags != nil {
if err := host.Driver.SetConfigFromFlags(flags); err != nil {
return host, err
}
}
if err := host.Driver.PreCreateCheck(); err != nil {
return nil, err
}
if err := os.MkdirAll(hostPath, 0700); err != nil {
return nil, err
}
if err := host.SaveConfig(); err != nil {
return host, err
}
if err := host.Create(name); err != nil {
return host, err
}
if err := host.ConfigureAuth(); err != nil {
return host, err
}
if flags.Bool("swarm") {
log.Info("Configuring Swarm...")
discovery := flags.String("swarm-discovery")
master := flags.Bool("swarm-master")
swarmHost := flags.String("swarm-host")
addr := flags.String("swarm-addr")
if err := host.ConfigureSwarm(discovery, master, swarmHost, addr); err != nil {
log.Errorf("Error configuring Swarm: %s", err)
}
}
return host, nil
}
func (s *Store) Remove(name string, force bool) error {
active, err := s.GetActive()
if err != nil {
return err
}
if active != nil && active.Name == name {
if err := s.RemoveActive(); err != nil {
return err
}
}
host, err := s.Load(name)
if err != nil {
return err
}
return host.Remove(force)
}
func (s *Store) List() ([]Host, error) {
dir, err := ioutil.ReadDir(s.Path)
if err != nil && !os.IsNotExist(err) {
return nil, err
}
hosts := []Host{}
for _, file := range dir {
// don't load hidden dirs; used for configs
if file.IsDir() && strings.Index(file.Name(), ".") != 0 {
host, err := s.Load(file.Name())
if err != nil {
log.Errorf("error loading host %q: %s", file.Name(), err)
continue
}
hosts = append(hosts, *host)
}
}
return hosts, nil
}
func (s *Store) Exists(name string) (bool, error) {
_, err := os.Stat(filepath.Join(s.Path, name))
if os.IsNotExist(err) {
return false, nil
} else if err == nil {
return true, nil
}
return false, err
}
func (s *Store) Load(name string) (*Host, error) {
hostPath := filepath.Join(s.Path, name)
return LoadHost(name, hostPath)
}
func (s *Store) GetActive() (*Host, error) {
hostName, err := ioutil.ReadFile(s.activePath())
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, err
}
return s.Load(string(hostName))
}
func (s *Store) IsActive(host *Host) (bool, error) {
active, err := s.GetActive()
if err != nil {
return false, err
}
if active == nil {
return false, nil
}
return active.Name == host.Name, nil
}
func (s *Store) SetActive(host *Host) error {
if err := os.MkdirAll(filepath.Dir(s.activePath()), 0700); err != nil {
return err
}
return ioutil.WriteFile(s.activePath(), []byte(host.Name), 0600)
}
func (s *Store) RemoveActive() error {
return os.Remove(s.activePath())
}
// activePath returns the path to the file that stores the name of the
// active host
func (s *Store) activePath() string {
return filepath.Join(s.Path, ".active")
}

View File

@ -22,7 +22,7 @@ func GetHomeDir() string {
func GetBaseDir() string {
baseDir := os.Getenv("MACHINE_STORAGE_PATH")
if baseDir == "" {
baseDir = filepath.Join(GetHomeDir(), ".docker")
baseDir = filepath.Join(GetHomeDir(), ".docker", "machine")
}
return baseDir
}
@ -31,20 +31,16 @@ func GetDockerDir() string {
return filepath.Join(GetHomeDir(), ".docker")
}
func GetMachineRoot() string {
return filepath.Join(GetBaseDir(), "machine")
}
func GetMachineDir() string {
return filepath.Join(GetMachineRoot(), "machines")
return filepath.Join(GetBaseDir(), "machines")
}
func GetMachineCertDir() string {
return filepath.Join(GetMachineRoot(), "certs")
return filepath.Join(GetBaseDir(), "certs")
}
func GetMachineCacheDir() string {
return filepath.Join(GetMachineRoot(), "cache")
return filepath.Join(GetBaseDir(), "cache")
}
func GetUsername() string {