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'" {