From 19ce7b79bd8f8f2c6bc46beae3a71cda45fbfd73 Mon Sep 17 00:00:00 2001
From: Nathan LeClaire <nathan.leclaire@gmail.com>
Date: Fri, 13 Nov 2015 13:26:31 -0800
Subject: [PATCH] Revise CommandLine interface to contain libmachine client and
 store

Signed-off-by: Nathan LeClaire <nathan.leclaire@gmail.com>
---
 cmd/machine.go                               |  12 --
 commands/active.go                           |  31 +---
 commands/commands.go                         | 179 +++++--------------
 commands/config.go                           |   5 +-
 commands/create.go                           |  32 ++--
 commands/env.go                              |  13 +-
 commands/inspect.go                          |   6 +-
 commands/ip.go                               |   6 +-
 commands/kill.go                             |   6 +-
 commands/ls.go                               |  27 ++-
 commands/ls_test.go                          |   3 +-
 commands/regeneratecerts.go                  |   5 +-
 commands/restart.go                          |   5 +-
 commands/rm.go                               |   8 +-
 commands/scp.go                              |   8 +-
 commands/ssh.go                              |   6 +-
 commands/start.go                            |   5 +-
 commands/status.go                           |   5 +-
 commands/stop.go                             |   6 +-
 commands/upgrade.go                          |   6 +-
 commands/url.go                              |   6 +-
 commands/version.go                          |   4 +-
 libmachine/drivers/plugin/register_driver.go |   4 +-
 libmachine/examples/vbox_create.go           |  24 ++-
 libmachine/libmachine.go                     |  76 ++++++--
 libmachine/persist/filestore.go              |  98 +++-------
 libmachine/persist/filestore_test.go         |   8 +-
 libmachine/persist/plugin_store.go           |  85 +++++++++
 libmachine/persist/store.go                  |  37 +++-
 test/integration/cli/create-rm.bats          |   2 +-
 30 files changed, 357 insertions(+), 361 deletions(-)
 create mode 100644 libmachine/persist/plugin_store.go

diff --git a/cmd/machine.go b/cmd/machine.go
index c4c24d5b95..5166127592 100644
--- a/cmd/machine.go
+++ b/cmd/machine.go
@@ -10,8 +10,6 @@ import (
 	"github.com/docker/machine/commands"
 	"github.com/docker/machine/commands/mcndirs"
 	"github.com/docker/machine/libmachine/log"
-	"github.com/docker/machine/libmachine/mcnutils"
-	"github.com/docker/machine/libmachine/ssh"
 	"github.com/docker/machine/version"
 )
 
@@ -74,16 +72,6 @@ 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 {
-		// TODO: Need better handling of config, everything is too
-		// complected together right now.
-		if c.GlobalBool("native-ssh") {
-			ssh.SetDefaultClient(ssh.Native)
-		}
-		mcnutils.GithubAPIToken = c.GlobalString("github-api-token")
-		mcndirs.BaseDir = c.GlobalString("storage-path")
-		return nil
-	}
 
 	app.Commands = commands.Commands
 	app.CommandNotFound = cmdNotFound
diff --git a/commands/active.go b/commands/active.go
index 587617c387..f5c74cc566 100644
--- a/commands/active.go
+++ b/commands/active.go
@@ -4,46 +4,33 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/docker/machine/libmachine/host"
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/persist"
 )
 
 var (
 	errTooManyArguments = errors.New("Error: Too many arguments given")
+	errNoActiveHost     = errors.New("No active host found")
 )
 
-func cmdActive(c CommandLine) error {
+func cmdActive(c CommandLine, api libmachine.API) error {
 	if len(c.Args()) > 0 {
 		return errTooManyArguments
 	}
 
-	store := getStore(c)
-
-	host, err := getActiveHost(store)
+	hosts, err := persist.LoadAllHosts(api)
 	if err != nil {
 		return fmt.Errorf("Error getting active host: %s", err)
 	}
 
-	if host != nil {
-		fmt.Println(host.Name)
-	}
+	items := getHostListItems(hosts)
 
-	return nil
-}
-
-func getActiveHost(store persist.Store) (*host.Host, error) {
-	hosts, err := listHosts(store)
-	if err != nil {
-		return nil, err
-	}
-
-	hostListItems := getHostListItems(hosts)
-
-	for _, item := range hostListItems {
+	for _, item := range items {
 		if item.Active {
-			return loadHost(store, item.Name)
+			fmt.Println(item.Name)
+			return nil
 		}
 	}
 
-	return nil, errors.New("Active host not found")
+	return errNoActiveHost
 }
diff --git a/commands/commands.go b/commands/commands.go
index 2f6ff36a70..32a3a6534b 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -9,14 +9,13 @@ import (
 
 	"github.com/codegangsta/cli"
 	"github.com/docker/machine/commands/mcndirs"
-	"github.com/docker/machine/drivers/errdriver"
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/cert"
-	"github.com/docker/machine/libmachine/drivers"
-	"github.com/docker/machine/libmachine/drivers/plugin/localbinary"
-	"github.com/docker/machine/libmachine/drivers/rpc"
 	"github.com/docker/machine/libmachine/host"
 	"github.com/docker/machine/libmachine/log"
+	"github.com/docker/machine/libmachine/mcnutils"
 	"github.com/docker/machine/libmachine/persist"
+	"github.com/docker/machine/libmachine/ssh"
 )
 
 var (
@@ -64,26 +63,49 @@ func (c *contextCommandLine) Application() *cli.App {
 	return c.App
 }
 
-func newPluginDriver(driverName string, rawContent []byte) (drivers.Driver, error) {
-	d, err := rpcdriver.NewRPCClientDriver(rawContent, driverName)
+func runAction(actionName string, c CommandLine, api libmachine.API) error {
+	hosts, err := persist.LoadHosts(api, c.Args())
 	if err != nil {
-		// Not being able to find a driver binary is a "known error"
-		if _, ok := err.(localbinary.ErrPluginBinaryNotFound); ok {
-			return errdriver.NewDriver(driverName), nil
+		return err
+	}
+
+	if len(hosts) == 0 {
+		return ErrNoMachineSpecified
+	}
+
+	if errs := runActionForeachMachine(actionName, hosts); len(errs) > 0 {
+		return consolidateErrs(errs)
+	}
+
+	for _, h := range hosts {
+		if err := api.Save(h); err != nil {
+			return fmt.Errorf("Error saving host to store: %s", err)
 		}
-		return nil, err
 	}
 
-	if driverName == "virtualbox" {
-		return drivers.NewSerialDriver(d), nil
-	}
-
-	return d, nil
+	return nil
 }
 
-func fatalOnError(command func(commandLine CommandLine) error) func(context *cli.Context) {
+func fatalOnError(command func(commandLine CommandLine, api libmachine.API) error) func(context *cli.Context) {
 	return func(context *cli.Context) {
-		if err := command(&contextCommandLine{context}); err != nil {
+		api := libmachine.NewClient(mcndirs.GetBaseDir())
+
+		if context.GlobalBool("native-ssh") {
+			api.SSHClientType = ssh.Native
+		}
+		api.GithubAPIToken = context.GlobalString("github-api-token")
+		api.Filestore.Path = context.GlobalString("storage-path")
+
+		// TODO (nathanleclaire): These should ultimately be accessed
+		// through the libmachine client by the rest of the code and
+		// not through their respective modules.  For now, however,
+		// they are also being set the way that they originally were
+		// set to preserve backwards compatibility.
+		mcndirs.BaseDir = api.Filestore.Path
+		mcnutils.GithubAPIToken = api.GithubAPIToken
+		ssh.SetDefaultClient(api.SSHClientType)
+
+		if err := command(&contextCommandLine{context}, api); err != nil {
 			log.Fatal(err)
 		}
 	}
@@ -102,88 +124,6 @@ func confirmInput(msg string) (bool, error) {
 	return confirmed, nil
 }
 
-func getStore(c CommandLine) persist.Store {
-	certInfo := getCertPathInfoFromContext(c)
-	return &persist.Filestore{
-		Path:             c.GlobalString("storage-path"),
-		CaCertPath:       certInfo.CaCertPath,
-		CaPrivateKeyPath: certInfo.CaPrivateKeyPath,
-	}
-}
-
-func listHosts(store persist.Store) ([]*host.Host, error) {
-	cliHosts := []*host.Host{}
-
-	hosts, err := store.List()
-	if err != nil {
-		return nil, fmt.Errorf("Error attempting to list hosts from store: %s", err)
-	}
-
-	for _, h := range hosts {
-		d, err := newPluginDriver(h.DriverName, h.RawDriver)
-		if err != nil {
-			return nil, fmt.Errorf("Error attempting to invoke binary for plugin '%s': %s", h.DriverName, err)
-		}
-
-		h.Driver = d
-
-		cliHosts = append(cliHosts, h)
-	}
-
-	return cliHosts, nil
-}
-
-func loadHost(store persist.Store, hostName string) (*host.Host, error) {
-	h, err := store.Load(hostName)
-	if err != nil {
-		return nil, fmt.Errorf("Loading host from store failed: %s", err)
-	}
-
-	d, err := newPluginDriver(h.DriverName, h.RawDriver)
-	if err != nil {
-		return nil, fmt.Errorf("Error attempting to invoke binary for plugin: %s", err)
-	}
-
-	h.Driver = d
-
-	return h, nil
-}
-
-func saveHost(store persist.Store, h *host.Host) error {
-	if err := store.Save(h); err != nil {
-		return fmt.Errorf("Error attempting to save host to store: %s", err)
-	}
-
-	return nil
-}
-
-func getFirstArgHost(c CommandLine) (*host.Host, error) {
-	store := getStore(c)
-	hostName := c.Args().First()
-
-	h, err := loadHost(store, hostName)
-	if err != nil {
-		return nil, fmt.Errorf("Error trying to get host %q: %s", hostName, err)
-	}
-
-	return h, nil
-}
-
-func getHostsFromContext(c CommandLine) ([]*host.Host, error) {
-	store := getStore(c)
-	hosts := []*host.Host{}
-
-	for _, hostName := range c.Args() {
-		h, err := loadHost(store, hostName)
-		if err != nil {
-			return nil, fmt.Errorf("Could not load host %q: %s", hostName, err)
-		}
-		hosts = append(hosts, h)
-	}
-
-	return hosts, nil
-}
-
 var Commands = []cli.Command{
 	{
 		Name:   "active",
@@ -427,52 +367,27 @@ func consolidateErrs(errs []error) error {
 	return errors.New(strings.TrimSpace(finalErr))
 }
 
-func runActionWithContext(actionName string, c CommandLine) error {
-	store := getStore(c)
-
-	hosts, err := getHostsFromContext(c)
-	if err != nil {
-		return err
-	}
-
-	if len(hosts) == 0 {
-		return ErrNoMachineSpecified
-	}
-
-	if errs := runActionForeachMachine(actionName, hosts); len(errs) > 0 {
-		return consolidateErrs(errs)
-	}
-
-	for _, h := range hosts {
-		if err := saveHost(store, h); err != nil {
-			return fmt.Errorf("Error saving host to store: %s", err)
-		}
-	}
-
-	return nil
-}
-
-// Returns the cert paths.
-// 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 getCertPathInfoFromContext(c CommandLine) cert.PathInfo {
+// Returns the cert paths.  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 getCertPathInfoFromCommandLine(c CommandLine) cert.PathInfo {
 	caCertPath := c.GlobalString("tls-ca-cert")
+	caKeyPath := c.GlobalString("tls-ca-key")
+	clientCertPath := c.GlobalString("tls-client-cert")
+	clientKeyPath := c.GlobalString("tls-client-key")
+
 	if caCertPath == "" {
 		caCertPath = filepath.Join(mcndirs.GetMachineCertDir(), "ca.pem")
 	}
 
-	caKeyPath := c.GlobalString("tls-ca-key")
 	if caKeyPath == "" {
 		caKeyPath = filepath.Join(mcndirs.GetMachineCertDir(), "ca-key.pem")
 	}
 
-	clientCertPath := c.GlobalString("tls-client-cert")
 	if clientCertPath == "" {
 		clientCertPath = filepath.Join(mcndirs.GetMachineCertDir(), "cert.pem")
 	}
 
-	clientKeyPath := c.GlobalString("tls-client-key")
 	if clientKeyPath == "" {
 		clientKeyPath = filepath.Join(mcndirs.GetMachineCertDir(), "key.pem")
 	}
diff --git a/commands/config.go b/commands/config.go
index 429ffbb56d..0cecb18d4e 100644
--- a/commands/config.go
+++ b/commands/config.go
@@ -6,6 +6,7 @@ import (
 	"os"
 	"strings"
 
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/auth"
 	"github.com/docker/machine/libmachine/cert"
 	"github.com/docker/machine/libmachine/host"
@@ -26,7 +27,7 @@ Be advised that this will trigger a Docker daemon restart which will stop runnin
 `, e.hostURL, e.wrappedErr)
 }
 
-func cmdConfig(c CommandLine) error {
+func cmdConfig(c CommandLine, api libmachine.API) error {
 	// Ensure that log messages always go to stderr when this command is
 	// being run (it is intended to be run in a subshell)
 	log.SetOutWriter(os.Stderr)
@@ -35,7 +36,7 @@ func cmdConfig(c CommandLine) error {
 		return ErrExpectedOneMachine
 	}
 
-	host, err := getFirstArgHost(c)
+	host, err := api.Load(c.Args().First())
 	if err != nil {
 		return err
 	}
diff --git a/commands/create.go b/commands/create.go
index bbe4bd71c2..ab397cc443 100644
--- a/commands/create.go
+++ b/commands/create.go
@@ -24,7 +24,6 @@ import (
 	"github.com/docker/machine/libmachine/log"
 	"github.com/docker/machine/libmachine/mcnerror"
 	"github.com/docker/machine/libmachine/mcnflag"
-	"github.com/docker/machine/libmachine/persist"
 	"github.com/docker/machine/libmachine/swarm"
 )
 
@@ -123,28 +122,19 @@ var (
 	}
 )
 
-func cmdCreateInner(c CommandLine) error {
+func cmdCreateInner(c CommandLine, api libmachine.API) error {
 	if len(c.Args()) > 1 {
 		return fmt.Errorf("Invalid command line. Found extra arguments %v", c.Args()[1:])
 	}
 
 	name := c.Args().First()
-	driverName := c.String("driver")
-	certInfo := getCertPathInfoFromContext(c)
-
-	storePath := c.GlobalString("storage-path")
-
-	store := &persist.Filestore{
-		Path:             storePath,
-		CaCertPath:       certInfo.CaCertPath,
-		CaPrivateKeyPath: certInfo.CaPrivateKeyPath,
-	}
-
 	if name == "" {
 		c.ShowHelp()
 		return errNoMachineName
 	}
 
+	driverName := c.String("driver")
+
 	validName := host.ValidateHostName(name)
 	if !validName {
 		return fmt.Errorf("Error creating machine: %s", mcnerror.ErrInvalidHostname)
@@ -163,16 +153,18 @@ func cmdCreateInner(c CommandLine) error {
 		return fmt.Errorf("Error attempting to marshal bare driver data: %s", err)
 	}
 
-	driver, err := newPluginDriver(driverName, bareDriverData)
+	driver, err := api.NewPluginDriver(driverName, bareDriverData)
 	if err != nil {
 		return fmt.Errorf("Error loading driver %q: %s", driverName, err)
 	}
 
-	h, err := store.NewHost(driver)
+	h, err := api.NewHost(driver)
 	if err != nil {
 		return fmt.Errorf("Error getting new host: %s", err)
 	}
 
+	certInfo := getCertPathInfoFromCommandLine(c)
+
 	h.HostOptions = &host.Options{
 		AuthOptions: &auth.Options{
 			CertDir:          mcndirs.GetMachineCertDir(),
@@ -207,7 +199,7 @@ func cmdCreateInner(c CommandLine) error {
 		},
 	}
 
-	exists, err := store.Exists(h.Name)
+	exists, err := api.Exists(h.Name)
 	if err != nil {
 		return fmt.Errorf("Error checking if host exists: %s", err)
 	}
@@ -227,11 +219,11 @@ func cmdCreateInner(c CommandLine) error {
 		return fmt.Errorf("Error setting machine configuration from flags provided: %s", err)
 	}
 
-	if err := libmachine.Create(store, h); err != nil {
+	if err := api.Create(h); err != nil {
 		return fmt.Errorf("Error creating machine: %s", err)
 	}
 
-	if err := saveHost(store, h); err != nil {
+	if err := api.Save(h); err != nil {
 		return fmt.Errorf("Error attempting to save store: %s", err)
 	}
 
@@ -276,7 +268,7 @@ func flagHackLookup(flagName string) string {
 	return ""
 }
 
-func cmdCreateOuter(c CommandLine) error {
+func cmdCreateOuter(c CommandLine, api libmachine.API) error {
 	const (
 		flagLookupMachineName = "flag-lookup"
 	)
@@ -296,7 +288,7 @@ func cmdCreateOuter(c CommandLine) error {
 		return fmt.Errorf("Error attempting to marshal bare driver data: %s", err)
 	}
 
-	driver, err := newPluginDriver(driverName, bareDriverData)
+	driver, err := api.NewPluginDriver(driverName, bareDriverData)
 	if err != nil {
 		return fmt.Errorf("Error loading driver %q: %s", driverName, err)
 	}
diff --git a/commands/env.go b/commands/env.go
index f81c0dcad8..7c085c325d 100644
--- a/commands/env.go
+++ b/commands/env.go
@@ -10,6 +10,7 @@ import (
 	"text/template"
 
 	"github.com/docker/machine/commands/mcndirs"
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/log"
 )
 
@@ -35,23 +36,23 @@ type ShellConfig struct {
 	NoProxyValue    string
 }
 
-func cmdEnv(c CommandLine) error {
+func cmdEnv(c CommandLine, api libmachine.API) error {
 	// Ensure that log messages always go to stderr when this command is
 	// being run (it is intended to be run in a subshell)
 	log.SetOutWriter(os.Stderr)
 
 	if c.Bool("unset") {
-		return unset(c)
+		return unset(c, api)
 	}
-	return set(c)
+	return set(c, api)
 }
 
-func set(c CommandLine) error {
+func set(c CommandLine, api libmachine.API) error {
 	if len(c.Args()) != 1 {
 		return errImproperEnvArgs
 	}
 
-	host, err := getFirstArgHost(c)
+	host, err := api.Load(c.Args().First())
 	if err != nil {
 		return err
 	}
@@ -118,7 +119,7 @@ func set(c CommandLine) error {
 	return executeTemplateStdout(shellCfg)
 }
 
-func unset(c CommandLine) error {
+func unset(c CommandLine, api libmachine.API) error {
 	if len(c.Args()) != 0 {
 		return errImproperUnsetEnvArgs
 	}
diff --git a/commands/inspect.go b/commands/inspect.go
index e3d130ca05..9799839db7 100644
--- a/commands/inspect.go
+++ b/commands/inspect.go
@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"os"
 	"text/template"
+
+	"github.com/docker/machine/libmachine"
 )
 
 var funcMap = template.FuncMap{
@@ -18,13 +20,13 @@ var funcMap = template.FuncMap{
 	},
 }
 
-func cmdInspect(c CommandLine) error {
+func cmdInspect(c CommandLine, api libmachine.API) error {
 	if len(c.Args()) == 0 {
 		c.ShowHelp()
 		return ErrExpectedOneMachine
 	}
 
-	host, err := getFirstArgHost(c)
+	host, err := api.Load(c.Args().First())
 	if err != nil {
 		return err
 	}
diff --git a/commands/ip.go b/commands/ip.go
index ad1647e256..13e45f6896 100644
--- a/commands/ip.go
+++ b/commands/ip.go
@@ -1,5 +1,7 @@
 package commands
 
-func cmdIP(c CommandLine) error {
-	return runActionWithContext("ip", c)
+import "github.com/docker/machine/libmachine"
+
+func cmdIP(c CommandLine, api libmachine.API) error {
+	return runAction("ip", c, api)
 }
diff --git a/commands/kill.go b/commands/kill.go
index 2340338fbc..f8c2aac3d1 100644
--- a/commands/kill.go
+++ b/commands/kill.go
@@ -1,5 +1,7 @@
 package commands
 
-func cmdKill(c CommandLine) error {
-	return runActionWithContext("kill", c)
+import "github.com/docker/machine/libmachine"
+
+func cmdKill(c CommandLine, api libmachine.API) error {
+	return runAction("kill", c, api)
 }
diff --git a/commands/ls.go b/commands/ls.go
index 822657adc2..78c652bc44 100644
--- a/commands/ls.go
+++ b/commands/ls.go
@@ -10,9 +10,11 @@ import (
 	"text/tabwriter"
 	"time"
 
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/drivers"
 	"github.com/docker/machine/libmachine/host"
 	"github.com/docker/machine/libmachine/log"
+	"github.com/docker/machine/libmachine/persist"
 	"github.com/docker/machine/libmachine/state"
 	"github.com/docker/machine/libmachine/swarm"
 	"github.com/skarademir/naturalsort"
@@ -39,15 +41,14 @@ type HostListItem struct {
 	SwarmOptions *swarm.Options
 }
 
-func cmdLs(c CommandLine) error {
+func cmdLs(c CommandLine, api libmachine.API) error {
 	quiet := c.Bool("quiet")
 	filters, err := parseFilters(c.StringSlice("filter"))
 	if err != nil {
 		return err
 	}
 
-	store := getStore(c)
-	hostList, err := listHosts(store)
+	hostList, err := persist.LoadAllHosts(api)
 	if err != nil {
 		return err
 	}
@@ -261,15 +262,9 @@ func attemptGetHostState(h *host.Host, stateQueryChan chan<- HostListItem) {
 	close(stateCh)
 	close(urlCh)
 
-	active, err := isActive(currentState, url)
-	if err != nil {
-		log.Errorf("error determining if host is active for host %s: %s",
-			h.Name, err)
-	}
-
 	stateQueryChan <- HostListItem{
 		Name:         h.Name,
-		Active:       active,
+		Active:       isActive(currentState, url),
 		DriverName:   h.Driver.DriverName(),
 		State:        currentState,
 		URL:          url,
@@ -332,14 +327,14 @@ func sortHostListItemsByName(items []HostListItem) {
 
 // IsActive provides a single function for determining if a host is active
 // based on both the url and if the host is stopped.
-func isActive(currentState state.State, url string) (bool, error) {
-	if currentState != state.Running {
-		return false, nil
-	}
+func isActive(currentState state.State, url string) bool {
+	dockerHost := os.Getenv("DOCKER_HOST")
 
 	// TODO: hard-coding the swarm port is a travesty...
-	dockerHost := os.Getenv("DOCKER_HOST")
 	deSwarmedHost := strings.Replace(dockerHost, ":3376", ":2376", 1)
+	if dockerHost == url || deSwarmedHost == url {
+		return currentState == state.Running
+	}
 
-	return dockerHost == url || deSwarmedHost == url, nil
+	return false
 }
diff --git a/commands/ls_test.go b/commands/ls_test.go
index eba3ac24d8..de88d514a7 100644
--- a/commands/ls_test.go
+++ b/commands/ls_test.go
@@ -467,9 +467,8 @@ func TestIsActive(t *testing.T) {
 			os.Setenv("DOCKER_HOST", c.dockerHost)
 		}
 
-		actual, err := isActive(c.state, "tcp://1.2.3.4:2376")
+		actual := isActive(c.state, "tcp://1.2.3.4:2376")
 
 		assert.Equal(t, c.expected, actual, "IsActive(%s, \"%s\") should return %v, but didn't", c.state, c.dockerHost, c.expected)
-		assert.NoError(t, err)
 	}
 }
diff --git a/commands/regeneratecerts.go b/commands/regeneratecerts.go
index ce7574a32e..f801f729ca 100644
--- a/commands/regeneratecerts.go
+++ b/commands/regeneratecerts.go
@@ -1,10 +1,11 @@
 package commands
 
 import (
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/log"
 )
 
-func cmdRegenerateCerts(c CommandLine) error {
+func cmdRegenerateCerts(c CommandLine, api libmachine.API) error {
 	if !c.Bool("force") {
 		ok, err := confirmInput("Regenerate TLS machine certs?  Warning: this is irreversible.")
 		if err != nil {
@@ -18,5 +19,5 @@ func cmdRegenerateCerts(c CommandLine) error {
 
 	log.Infof("Regenerating TLS certificates")
 
-	return runActionWithContext("configureAuth", c)
+	return runAction("configureAuth", c, api)
 }
diff --git a/commands/restart.go b/commands/restart.go
index 18432ee03c..1b197f9e47 100644
--- a/commands/restart.go
+++ b/commands/restart.go
@@ -1,11 +1,12 @@
 package commands
 
 import (
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/log"
 )
 
-func cmdRestart(c CommandLine) error {
-	if err := runActionWithContext("restart", c); err != nil {
+func cmdRestart(c CommandLine, api libmachine.API) error {
+	if err := runAction("restart", c, api); err != nil {
 		return err
 	}
 
diff --git a/commands/rm.go b/commands/rm.go
index 4ebd994126..726ec0aaa8 100644
--- a/commands/rm.go
+++ b/commands/rm.go
@@ -4,20 +4,20 @@ import (
 	"errors"
 	"fmt"
 
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/log"
 )
 
-func cmdRm(c CommandLine) error {
+func cmdRm(c CommandLine, api libmachine.API) error {
 	if len(c.Args()) == 0 {
 		c.ShowHelp()
 		return errors.New("You must specify a machine name")
 	}
 
 	force := c.Bool("force")
-	store := getStore(c)
 
 	for _, hostName := range c.Args() {
-		h, err := loadHost(store, hostName)
+		h, err := api.Load(hostName)
 		if err != nil {
 			return fmt.Errorf("Error removing host %q: %s", hostName, err)
 		}
@@ -29,7 +29,7 @@ func cmdRm(c CommandLine) error {
 			}
 		}
 
-		if err := store.Remove(hostName); err != nil {
+		if err := api.Remove(hostName); err != nil {
 			log.Errorf("Error removing machine %q from store: %s", hostName, err)
 		} else {
 			log.Infof("Successfully removed %s", hostName)
diff --git a/commands/scp.go b/commands/scp.go
index bb927477fb..bf33cce5b3 100644
--- a/commands/scp.go
+++ b/commands/scp.go
@@ -7,6 +7,7 @@ import (
 	"os/exec"
 	"strings"
 
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/log"
 	"github.com/docker/machine/libmachine/persist"
 )
@@ -44,7 +45,7 @@ type storeHostInfoLoader struct {
 }
 
 func (s *storeHostInfoLoader) load(name string) (HostInfo, error) {
-	host, err := loadHost(s.store, name)
+	host, err := s.store.Load(name)
 	if err != nil {
 		return nil, fmt.Errorf("Error loading host: %s", err)
 	}
@@ -52,7 +53,7 @@ func (s *storeHostInfoLoader) load(name string) (HostInfo, error) {
 	return host.Driver, nil
 }
 
-func cmdScp(c CommandLine) error {
+func cmdScp(c CommandLine, api libmachine.API) error {
 	args := c.Args()
 	if len(args) != 2 {
 		c.ShowHelp()
@@ -62,8 +63,7 @@ func cmdScp(c CommandLine) error {
 	src := args[0]
 	dest := args[1]
 
-	store := getStore(c)
-	hostInfoLoader := &storeHostInfoLoader{store}
+	hostInfoLoader := &storeHostInfoLoader{api}
 
 	cmd, err := getScpCmd(src, dest, c.Bool("recursive"), hostInfoLoader)
 	if err != nil {
diff --git a/commands/ssh.go b/commands/ssh.go
index 30874d6043..c4ce4d367e 100644
--- a/commands/ssh.go
+++ b/commands/ssh.go
@@ -3,10 +3,11 @@ package commands
 import (
 	"fmt"
 
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/state"
 )
 
-func cmdSSH(c CommandLine) error {
+func cmdSSH(c CommandLine, api libmachine.API) error {
 	// Check for help flag -- Needed due to SkipFlagParsing
 	for _, arg := range c.Args() {
 		if arg == "-help" || arg == "--help" || arg == "-h" {
@@ -20,8 +21,7 @@ func cmdSSH(c CommandLine) error {
 		return ErrExpectedOneMachine
 	}
 
-	store := getStore(c)
-	host, err := loadHost(store, name)
+	host, err := api.Load(name)
 	if err != nil {
 		return err
 	}
diff --git a/commands/start.go b/commands/start.go
index 348b0f8ce3..09322f1d90 100644
--- a/commands/start.go
+++ b/commands/start.go
@@ -1,11 +1,12 @@
 package commands
 
 import (
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/log"
 )
 
-func cmdStart(c CommandLine) error {
-	if err := runActionWithContext("start", c); err != nil {
+func cmdStart(c CommandLine, api libmachine.API) error {
+	if err := runAction("start", c, api); err != nil {
 		return err
 	}
 
diff --git a/commands/status.go b/commands/status.go
index d660ac5382..d5c30b060a 100644
--- a/commands/status.go
+++ b/commands/status.go
@@ -1,15 +1,16 @@
 package commands
 
 import (
+	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/log"
 )
 
-func cmdStatus(c CommandLine) error {
+func cmdStatus(c CommandLine, api libmachine.API) error {
 	if len(c.Args()) != 1 {
 		return ErrExpectedOneMachine
 	}
 
-	host, err := getFirstArgHost(c)
+	host, err := api.Load(c.Args().First())
 	if err != nil {
 		return err
 	}
diff --git a/commands/stop.go b/commands/stop.go
index 2a926945df..bd0cd38658 100644
--- a/commands/stop.go
+++ b/commands/stop.go
@@ -1,5 +1,7 @@
 package commands
 
-func cmdStop(c CommandLine) error {
-	return runActionWithContext("stop", c)
+import "github.com/docker/machine/libmachine"
+
+func cmdStop(c CommandLine, api libmachine.API) error {
+	return runAction("stop", c, api)
 }
diff --git a/commands/upgrade.go b/commands/upgrade.go
index 31e8469c03..84282f1651 100644
--- a/commands/upgrade.go
+++ b/commands/upgrade.go
@@ -1,5 +1,7 @@
 package commands
 
-func cmdUpgrade(c CommandLine) error {
-	return runActionWithContext("upgrade", c)
+import "github.com/docker/machine/libmachine"
+
+func cmdUpgrade(c CommandLine, api libmachine.API) error {
+	return runAction("upgrade", c, api)
 }
diff --git a/commands/url.go b/commands/url.go
index 3ec861f3cc..7847c25d35 100644
--- a/commands/url.go
+++ b/commands/url.go
@@ -2,14 +2,16 @@ package commands
 
 import (
 	"fmt"
+
+	"github.com/docker/machine/libmachine"
 )
 
-func cmdURL(c CommandLine) error {
+func cmdURL(c CommandLine, api libmachine.API) error {
 	if len(c.Args()) != 1 {
 		return ErrExpectedOneMachine
 	}
 
-	host, err := getFirstArgHost(c)
+	host, err := api.Load(c.Args().First())
 	if err != nil {
 		return err
 	}
diff --git a/commands/version.go b/commands/version.go
index dbc4a9d7e5..835b511eeb 100644
--- a/commands/version.go
+++ b/commands/version.go
@@ -1,6 +1,8 @@
 package commands
 
-func cmdVersion(c CommandLine) error {
+import "github.com/docker/machine/libmachine"
+
+func cmdVersion(c CommandLine, api libmachine.API) error {
 	c.ShowVersion()
 	return nil
 }
diff --git a/libmachine/drivers/plugin/register_driver.go b/libmachine/drivers/plugin/register_driver.go
index f3348481c6..2ce660b3c7 100644
--- a/libmachine/drivers/plugin/register_driver.go
+++ b/libmachine/drivers/plugin/register_driver.go
@@ -8,10 +8,10 @@ import (
 	"os"
 	"time"
 
-	"github.com/docker/machine/libmachine"
 	"github.com/docker/machine/libmachine/drivers"
 	"github.com/docker/machine/libmachine/drivers/plugin/localbinary"
 	"github.com/docker/machine/libmachine/drivers/rpc"
+	"github.com/docker/machine/libmachine/log"
 	"github.com/docker/machine/libmachine/version"
 )
 
@@ -29,7 +29,7 @@ Please use this plugin through the main 'docker-machine' binary.
 		os.Exit(1)
 	}
 
-	libmachine.SetDebug(true)
+	log.IsDebug = true
 
 	rpcd := rpcdriver.NewRPCServerDriver(d)
 	rpc.Register(rpcd)
diff --git a/libmachine/examples/vbox_create.go b/libmachine/examples/vbox_create.go
index 2da9d7aa3c..a2c4e878df 100644
--- a/libmachine/examples/vbox_create.go
+++ b/libmachine/examples/vbox_create.go
@@ -2,6 +2,7 @@ package main
 
 // Sample Virtualbox create independent of Machine CLI.
 import (
+	"encoding/json"
 	"fmt"
 	"os"
 
@@ -11,16 +12,11 @@ import (
 )
 
 func main() {
-	libmachine.SetDebug(true)
-
+	log.IsDebug = true
 	log.SetOutWriter(os.Stdout)
 	log.SetErrWriter(os.Stderr)
 
-	// returns the familiar store at $HOME/.docker/machine
-	store := libmachine.GetDefaultStore()
-
-	// over-ride this for now (don't want to muck with my default store)
-	store.Path = "/tmp/automatic"
+	client := libmachine.NewClient("/tmp/automatic")
 
 	hostName := "myfunhost"
 
@@ -29,14 +25,24 @@ func main() {
 	driver.CPU = 2
 	driver.Memory = 2048
 
-	h, err := store.NewHost(driver)
+	data, err := json.Marshal(driver)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	pluginDriver, err := client.NewPluginDriver("virtualbox", data)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	h, err := client.NewHost(pluginDriver)
 	if err != nil {
 		log.Fatal(err)
 	}
 
 	h.HostOptions.EngineOptions.StorageDriver = "overlay"
 
-	if err := libmachine.Create(store, h); err != nil {
+	if err := client.Create(h); err != nil {
 		log.Fatal(err)
 	}
 
diff --git a/libmachine/libmachine.go b/libmachine/libmachine.go
index bf33f28882..63fe37da7a 100644
--- a/libmachine/libmachine.go
+++ b/libmachine/libmachine.go
@@ -4,29 +4,81 @@ import (
 	"fmt"
 	"path/filepath"
 
+	"github.com/docker/machine/libmachine/auth"
 	"github.com/docker/machine/libmachine/cert"
 	"github.com/docker/machine/libmachine/drivers"
+	"github.com/docker/machine/libmachine/engine"
 	"github.com/docker/machine/libmachine/host"
 	"github.com/docker/machine/libmachine/log"
 	"github.com/docker/machine/libmachine/mcnutils"
 	"github.com/docker/machine/libmachine/persist"
 	"github.com/docker/machine/libmachine/provision"
+	"github.com/docker/machine/libmachine/ssh"
 	"github.com/docker/machine/libmachine/state"
+	"github.com/docker/machine/libmachine/swarm"
+	"github.com/docker/machine/libmachine/version"
 )
 
-func GetDefaultStore() *persist.Filestore {
-	homeDir := mcnutils.GetHomeDir()
-	certsDir := filepath.Join(homeDir, ".docker", "machine", "certs")
-	return &persist.Filestore{
-		Path:             homeDir,
-		CaCertPath:       certsDir,
-		CaPrivateKeyPath: certsDir,
+type API interface {
+	persist.Store
+	NewPluginDriver(string, []byte) (drivers.Driver, error)
+	NewHost(drivers.Driver) (*host.Host, error)
+	Create(h *host.Host) error
+}
+
+type Client struct {
+	*persist.PluginStore
+	IsDebug        bool
+	SSHClientType  ssh.ClientType
+	GithubAPIToken string
+}
+
+func NewClient(storePath string) *Client {
+	certsDir := filepath.Join(storePath, ".docker", "machine", "certs")
+	return &Client{
+		IsDebug:       false,
+		SSHClientType: ssh.External,
+		PluginStore:   persist.NewPluginStore(storePath, certsDir, certsDir),
 	}
 }
 
+func (api *Client) NewHost(driver drivers.Driver) (*host.Host, error) {
+	certDir := filepath.Join(api.Path, "certs")
+
+	hostOptions := &host.Options{
+		AuthOptions: &auth.Options{
+			CertDir:          certDir,
+			CaCertPath:       filepath.Join(certDir, "ca.pem"),
+			CaPrivateKeyPath: filepath.Join(certDir, "ca-key.pem"),
+			ClientCertPath:   filepath.Join(certDir, "cert.pem"),
+			ClientKeyPath:    filepath.Join(certDir, "key.pem"),
+			ServerCertPath:   filepath.Join(api.GetMachinesDir(), "server.pem"),
+			ServerKeyPath:    filepath.Join(api.GetMachinesDir(), "server-key.pem"),
+		},
+		EngineOptions: &engine.Options{
+			InstallURL:    "https://get.docker.com",
+			StorageDriver: "aufs",
+			TLSVerify:     true,
+		},
+		SwarmOptions: &swarm.Options{
+			Host:     "tcp://0.0.0.0:3376",
+			Image:    "swarm:latest",
+			Strategy: "spread",
+		},
+	}
+
+	return &host.Host{
+		ConfigVersion: version.ConfigVersion,
+		Name:          driver.GetMachineName(),
+		Driver:        driver,
+		DriverName:    driver.DriverName(),
+		HostOptions:   hostOptions,
+	}, nil
+}
+
 // Create is the wrapper method which covers all of the boilerplate around
 // actually creating, provisioning, and persisting an instance in the store.
-func Create(store persist.Store, h *host.Host) error {
+func (api *Client) Create(h *host.Host) error {
 	if err := cert.BootstrapCertificates(h.HostOptions.AuthOptions); err != nil {
 		return fmt.Errorf("Error generating certificates: %s", err)
 	}
@@ -37,7 +89,7 @@ func Create(store persist.Store, h *host.Host) error {
 		return fmt.Errorf("Error with pre-create check: %s", err)
 	}
 
-	if err := store.Save(h); err != nil {
+	if err := api.Save(h); err != nil {
 		return fmt.Errorf("Error saving host to store before attempting creation: %s", err)
 	}
 
@@ -47,7 +99,7 @@ func Create(store persist.Store, h *host.Host) error {
 		return fmt.Errorf("Error in driver during machine creation: %s", err)
 	}
 
-	if err := store.Save(h); err != nil {
+	if err := api.Save(h); err != nil {
 		return fmt.Errorf("Error saving host to store after attempting creation: %s", err)
 	}
 
@@ -79,7 +131,3 @@ func Create(store persist.Store, h *host.Host) error {
 
 	return nil
 }
-
-func SetDebug(val bool) {
-	log.IsDebug = val
-}
diff --git a/libmachine/persist/filestore.go b/libmachine/persist/filestore.go
index 408ff39db4..739e9b5fc8 100644
--- a/libmachine/persist/filestore.go
+++ b/libmachine/persist/filestore.go
@@ -8,15 +8,8 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/docker/machine/libmachine/auth"
-	"github.com/docker/machine/libmachine/drivers"
-	"github.com/docker/machine/libmachine/drivers/rpc"
-	"github.com/docker/machine/libmachine/engine"
 	"github.com/docker/machine/libmachine/host"
-	"github.com/docker/machine/libmachine/log"
 	"github.com/docker/machine/libmachine/mcnerror"
-	"github.com/docker/machine/libmachine/swarm"
-	"github.com/docker/machine/libmachine/version"
 )
 
 type Filestore struct {
@@ -25,7 +18,15 @@ type Filestore struct {
 	CaPrivateKeyPath string
 }
 
-func (s Filestore) getMachinesDir() string {
+func NewFilestore(path, caCertPath, caPrivateKeyPath string) *Filestore {
+	return &Filestore{
+		Path:             path,
+		CaCertPath:       caCertPath,
+		CaPrivateKeyPath: caPrivateKeyPath,
+	}
+}
+
+func (s Filestore) GetMachinesDir() string {
 	return filepath.Join(s.Path, "machines")
 }
 
@@ -34,31 +35,12 @@ func (s Filestore) saveToFile(data []byte, file string) error {
 }
 
 func (s Filestore) Save(host *host.Host) error {
-	if serialDriver, ok := host.Driver.(*drivers.SerialDriver); ok {
-		// Unwrap Driver
-		host.Driver = serialDriver.Driver
-
-		// Re-wrap Driver when done
-		defer func() {
-			host.Driver = serialDriver
-		}()
-	}
-
-	// TODO: Does this belong here?
-	if rpcClientDriver, ok := host.Driver.(*rpcdriver.RPCClientDriver); ok {
-		data, err := rpcClientDriver.GetConfigRaw()
-		if err != nil {
-			return fmt.Errorf("Error getting raw config for driver: %s", err)
-		}
-		host.RawDriver = data
-	}
-
 	data, err := json.MarshalIndent(host, "", "    ")
 	if err != nil {
 		return err
 	}
 
-	hostPath := filepath.Join(s.getMachinesDir(), host.Name)
+	hostPath := filepath.Join(s.GetMachinesDir(), host.Name)
 
 	// Ensure that the directory we want to save to exists.
 	if err := os.MkdirAll(hostPath, 0700); err != nil {
@@ -69,34 +51,29 @@ func (s Filestore) Save(host *host.Host) error {
 }
 
 func (s Filestore) Remove(name string) error {
-	hostPath := filepath.Join(s.getMachinesDir(), name)
+	hostPath := filepath.Join(s.GetMachinesDir(), name)
 	return os.RemoveAll(hostPath)
 }
 
-func (s Filestore) List() ([]*host.Host, error) {
-	dir, err := ioutil.ReadDir(s.getMachinesDir())
+func (s Filestore) List() ([]string, error) {
+	dir, err := ioutil.ReadDir(s.GetMachinesDir())
 	if err != nil && !os.IsNotExist(err) {
 		return nil, err
 	}
 
-	hosts := []*host.Host{}
+	hostNames := []string{}
 
 	for _, file := range dir {
 		if file.IsDir() && !strings.HasPrefix(file.Name(), ".") {
-			host, err := s.Load(file.Name())
-			if err != nil {
-				log.Errorf("error loading host %q: %s", file.Name(), err)
-				continue
-			}
-			hosts = append(hosts, host)
+			hostNames = append(hostNames, file.Name())
 		}
 	}
 
-	return hosts, nil
+	return hostNames, nil
 }
 
 func (s Filestore) Exists(name string) (bool, error) {
-	_, err := os.Stat(filepath.Join(s.getMachinesDir(), name))
+	_, err := os.Stat(filepath.Join(s.GetMachinesDir(), name))
 
 	if os.IsNotExist(err) {
 		return false, nil
@@ -108,7 +85,7 @@ func (s Filestore) Exists(name string) (bool, error) {
 }
 
 func (s Filestore) loadConfig(h *host.Host) error {
-	data, err := ioutil.ReadFile(filepath.Join(s.getMachinesDir(), h.Name, "config.json"))
+	data, err := ioutil.ReadFile(filepath.Join(s.GetMachinesDir(), h.Name, "config.json"))
 	if err != nil {
 		return err
 	}
@@ -128,7 +105,7 @@ func (s Filestore) loadConfig(h *host.Host) error {
 
 	// If we end up performing a migration, we should save afterwards so we don't have to do it again on subsequent invocations.
 	if migrationPerformed {
-		if err := s.saveToFile(data, filepath.Join(s.getMachinesDir(), h.Name, "config.json.bak")); err != nil {
+		if err := s.saveToFile(data, filepath.Join(s.GetMachinesDir(), h.Name, "config.json.bak")); err != nil {
 			return fmt.Errorf("Error attempting to save backup after migration: %s", err)
 		}
 
@@ -138,11 +115,10 @@ func (s Filestore) loadConfig(h *host.Host) error {
 	}
 
 	return nil
-
 }
 
 func (s Filestore) Load(name string) (*host.Host, error) {
-	hostPath := filepath.Join(s.getMachinesDir(), name)
+	hostPath := filepath.Join(s.GetMachinesDir(), name)
 
 	if _, err := os.Stat(hostPath); os.IsNotExist(err) {
 		return nil, mcnerror.ErrHostDoesNotExist{
@@ -160,37 +136,3 @@ func (s Filestore) Load(name string) (*host.Host, error) {
 
 	return host, nil
 }
-
-func (s Filestore) NewHost(driver drivers.Driver) (*host.Host, error) {
-	certDir := filepath.Join(s.Path, "certs")
-
-	hostOptions := &host.Options{
-		AuthOptions: &auth.Options{
-			CertDir:          certDir,
-			CaCertPath:       filepath.Join(certDir, "ca.pem"),
-			CaPrivateKeyPath: filepath.Join(certDir, "ca-key.pem"),
-			ClientCertPath:   filepath.Join(certDir, "cert.pem"),
-			ClientKeyPath:    filepath.Join(certDir, "key.pem"),
-			ServerCertPath:   filepath.Join(s.getMachinesDir(), "server.pem"),
-			ServerKeyPath:    filepath.Join(s.getMachinesDir(), "server-key.pem"),
-		},
-		EngineOptions: &engine.Options{
-			InstallURL:    "https://get.docker.com",
-			StorageDriver: "aufs",
-			TLSVerify:     true,
-		},
-		SwarmOptions: &swarm.Options{
-			Host:     "tcp://0.0.0.0:3376",
-			Image:    "swarm:latest",
-			Strategy: "spread",
-		},
-	}
-
-	return &host.Host{
-		ConfigVersion: version.ConfigVersion,
-		Name:          driver.GetMachineName(),
-		Driver:        driver,
-		DriverName:    driver.DriverName(),
-		HostOptions:   hostOptions,
-	}, nil
-}
diff --git a/libmachine/persist/filestore_test.go b/libmachine/persist/filestore_test.go
index 69e1ee95b5..1320670299 100644
--- a/libmachine/persist/filestore_test.go
+++ b/libmachine/persist/filestore_test.go
@@ -47,7 +47,7 @@ func TestStoreSave(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	path := filepath.Join(store.getMachinesDir(), h.Name)
+	path := filepath.Join(store.GetMachinesDir(), h.Name)
 	if _, err := os.Stat(path); os.IsNotExist(err) {
 		t.Fatalf("Host path doesn't exist: %s", path)
 	}
@@ -67,7 +67,7 @@ func TestStoreRemove(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	path := filepath.Join(store.getMachinesDir(), h.Name)
+	path := filepath.Join(store.GetMachinesDir(), h.Name)
 	if _, err := os.Stat(path); os.IsNotExist(err) {
 		t.Fatalf("Host path doesn't exist: %s", path)
 	}
@@ -101,8 +101,8 @@ func TestStoreList(t *testing.T) {
 		t.Fatalf("List returned %d items, expected 1", len(hosts))
 	}
 
-	if hosts[0].Name != h.Name {
-		t.Fatalf("hosts[0] name is incorrect, got: %s", hosts[0].Name)
+	if hosts[0] != h.Name {
+		t.Fatalf("hosts[0] name is incorrect, got: %s", hosts[0])
 	}
 }
 
diff --git a/libmachine/persist/plugin_store.go b/libmachine/persist/plugin_store.go
new file mode 100644
index 0000000000..e7c5366479
--- /dev/null
+++ b/libmachine/persist/plugin_store.go
@@ -0,0 +1,85 @@
+package persist
+
+import (
+	"fmt"
+
+	"github.com/docker/machine/drivers/errdriver"
+	"github.com/docker/machine/libmachine/drivers"
+	"github.com/docker/machine/libmachine/drivers/plugin/localbinary"
+	"github.com/docker/machine/libmachine/drivers/rpc"
+	"github.com/docker/machine/libmachine/host"
+)
+
+type PluginDriverFactory interface {
+	NewPluginDriver(string, []byte) (drivers.Driver, error)
+}
+
+type RPCPluginDriverFactory struct{}
+
+type PluginStore struct {
+	*Filestore
+	PluginDriverFactory
+}
+
+func (factory RPCPluginDriverFactory) NewPluginDriver(driverName string, rawContent []byte) (drivers.Driver, error) {
+	d, err := rpcdriver.NewRPCClientDriver(rawContent, driverName)
+	if err != nil {
+		// Not being able to find a driver binary is a "known error"
+		if _, ok := err.(localbinary.ErrPluginBinaryNotFound); ok {
+			return errdriver.NewDriver(driverName), nil
+		}
+		return nil, err
+	}
+
+	if driverName == "virtualbox" {
+		return drivers.NewSerialDriver(d), nil
+	}
+
+	return d, nil
+}
+
+func NewPluginStore(path, caCertPath, caPrivateKeyPath string) *PluginStore {
+	return &PluginStore{
+		Filestore:           NewFilestore(path, caCertPath, caPrivateKeyPath),
+		PluginDriverFactory: RPCPluginDriverFactory{},
+	}
+}
+
+func (ps PluginStore) Save(host *host.Host) error {
+	if serialDriver, ok := host.Driver.(*drivers.SerialDriver); ok {
+		// Unwrap Driver
+		host.Driver = serialDriver.Driver
+
+		// Re-wrap Driver when done
+		defer func() {
+			host.Driver = serialDriver
+		}()
+	}
+
+	// TODO: Does this belong here?
+	if rpcClientDriver, ok := host.Driver.(*rpcdriver.RPCClientDriver); ok {
+		data, err := rpcClientDriver.GetConfigRaw()
+		if err != nil {
+			return fmt.Errorf("Error getting raw config for driver: %s", err)
+		}
+		host.RawDriver = data
+	}
+
+	return ps.Filestore.Save(host)
+}
+
+func (ps PluginStore) Load(name string) (*host.Host, error) {
+	h, err := ps.Filestore.Load(name)
+	if err != nil {
+		return nil, err
+	}
+
+	d, err := ps.NewPluginDriver(h.DriverName, h.RawDriver)
+	if err != nil {
+		return nil, err
+	}
+
+	h.Driver = d
+
+	return h, nil
+}
diff --git a/libmachine/persist/store.go b/libmachine/persist/store.go
index eb59b70ce3..226b62b04c 100644
--- a/libmachine/persist/store.go
+++ b/libmachine/persist/store.go
@@ -1,21 +1,15 @@
 package persist
 
-import (
-	"github.com/docker/machine/libmachine/drivers"
-	"github.com/docker/machine/libmachine/host"
-)
+import "github.com/docker/machine/libmachine/host"
 
 type Store interface {
 	// Exists returns whether a machine exists or not
 	Exists(name string) (bool, error)
 
-	// NewHost will initialize a new host machine
-	NewHost(driver drivers.Driver) (*host.Host, error)
-
 	// List returns a list of all hosts in the store
-	List() ([]*host.Host, error)
+	List() ([]string, error)
 
-	// Get loads a host by name
+	// Load loads a host by name
 	Load(name string) (*host.Host, error)
 
 	// Remove removes a machine from the store
@@ -24,3 +18,28 @@ type Store interface {
 	// Save persists a machine in the store
 	Save(host *host.Host) error
 }
+
+func LoadHosts(s Store, hostNames []string) ([]*host.Host, error) {
+	loadedHosts := []*host.Host{}
+
+	for _, hostName := range hostNames {
+		h, err := s.Load(hostName)
+		if err != nil {
+			// TODO: (nathanleclaire) Should these be bundled up
+			// into one error instead of exiting?
+			return nil, err
+		}
+		loadedHosts = append(loadedHosts, h)
+	}
+
+	return loadedHosts, nil
+}
+
+func LoadAllHosts(s Store) ([]*host.Host, error) {
+	hostNames, err := s.List()
+	if err != nil {
+		return nil, err
+	}
+
+	return LoadHosts(s, hostNames)
+}
diff --git a/test/integration/cli/create-rm.bats b/test/integration/cli/create-rm.bats
index 73f18184ae..49a9e38d19 100644
--- a/test/integration/cli/create-rm.bats
+++ b/test/integration/cli/create-rm.bats
@@ -73,7 +73,7 @@ load ${BASE_TEST_DIR}/helpers.bash
 @test "none: rm non existent machine fails 'machine rm ∞'" {
   run machine rm ∞
   [ "$status" -eq 1 ]
-  [[ ${lines[0]} == "Error removing host \"∞\": Loading host from store failed: Host does not exist: \"∞\"" ]]
+  [[ ${lines[0]} == "Error removing host \"∞\": Host does not exist: \"∞\"" ]]
 }
 
 @test "none: rm is successful 'machine rm 0'" {