Revise CommandLine interface to contain libmachine client and store

Signed-off-by: Nathan LeClaire <nathan.leclaire@gmail.com>
This commit is contained in:
Nathan LeClaire 2015-11-13 13:26:31 -08:00
parent 3affe5ec25
commit 19ce7b79bd
30 changed files with 357 additions and 361 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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])
}
}

View File

@ -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
}

View File

@ -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)
}

View File

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