diff --git a/commands/active.go b/commands/active.go new file mode 100644 index 0000000000..6840806a67 --- /dev/null +++ b/commands/active.go @@ -0,0 +1,49 @@ +package commands + +import ( + "fmt" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" +) + +func cmdActive(c *cli.Context) { + name := c.Args().First() + + certInfo := getCertPathInfo(c) + defaultStore, err := getDefaultStore( + c.GlobalString("storage-path"), + certInfo.CaCertPath, + certInfo.CaKeyPath, + ) + if err != nil { + log.Fatal(err) + } + + mcn, err := newMcn(defaultStore) + if err != nil { + log.Fatal(err) + } + + if name == "" { + host, err := mcn.GetActive() + if err != nil { + log.Fatalf("error getting active host: %v", err) + } + if host != nil { + fmt.Println(host.Name) + } + } else if name != "" { + host, err := mcn.Get(name) + if err != nil { + log.Fatalf("error loading host: %v", err) + } + + if err := mcn.SetActive(host); err != nil { + log.Fatalf("error setting active host: %v", err) + } + } else { + cli.ShowCommandHelp(c, "active") + } +} diff --git a/commands/active_test.go b/commands/active_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/active_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands.go b/commands/commands.go similarity index 55% rename from commands.go rename to commands/commands.go index 2145adb674..3e7455a113 100644 --- a/commands.go +++ b/commands/commands.go @@ -1,15 +1,11 @@ -package main +package commands import ( - "encoding/json" "fmt" - "net/url" "os" - "os/exec" "path/filepath" "sort" "strings" - "text/tabwriter" log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" @@ -29,9 +25,9 @@ import ( _ "github.com/docker/machine/drivers/vmwarefusion" _ "github.com/docker/machine/drivers/vmwarevcloudair" _ "github.com/docker/machine/drivers/vmwarevsphere" + "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/auth" - "github.com/docker/machine/libmachine/engine" "github.com/docker/machine/libmachine/swarm" "github.com/docker/machine/state" "github.com/docker/machine/utils" @@ -329,536 +325,6 @@ var Commands = []cli.Command{ }, } -func cmdActive(c *cli.Context) { - name := c.Args().First() - - certInfo := getCertPathInfo(c) - defaultStore, err := getDefaultStore( - c.GlobalString("storage-path"), - certInfo.CaCertPath, - certInfo.CaKeyPath, - ) - if err != nil { - log.Fatal(err) - } - - mcn, err := newMcn(defaultStore) - if err != nil { - log.Fatal(err) - } - - if name == "" { - host, err := mcn.GetActive() - if err != nil { - log.Fatalf("error getting active host: %v", err) - } - if host != nil { - fmt.Println(host.Name) - } - } else if name != "" { - host, err := mcn.Get(name) - if err != nil { - log.Fatalf("error loading host: %v", err) - } - - if err := mcn.SetActive(host); err != nil { - log.Fatalf("error setting active host: %v", err) - } - } else { - cli.ShowCommandHelp(c, "active") - } -} - -// If the user has specified a driver, they should not see the flags for all -// of the drivers in `docker-machine create`. This method replaces the 100+ -// create flags with only the ones applicable to the driver specified -func trimDriverFlags(driver string, cmds []cli.Command) ([]cli.Command, error) { - filteredCmds := cmds - driverFlags, err := drivers.GetCreateFlagsForDriver(driver) - if err != nil { - return nil, err - } - - for i, cmd := range cmds { - if cmd.HasName("create") { - filteredCmds[i].Flags = append(driverFlags, sharedCreateFlags...) - } - } - - return filteredCmds, nil -} - -func cmdCreate(c *cli.Context) { - var ( - err error - ) - driver := c.String("driver") - name := c.Args().First() - - // TODO: Not really a fan of "none" as the default driver... - if driver != "none" { - c.App.Commands, err = trimDriverFlags(driver, c.App.Commands) - if err != nil { - log.Fatal(err) - } - } - - if name == "" { - cli.ShowCommandHelp(c, "create") - log.Fatal("You must specify a machine name") - } - - certInfo := getCertPathInfo(c) - - if err := setupCertificates( - certInfo.CaCertPath, - certInfo.CaKeyPath, - certInfo.ClientCertPath, - certInfo.ClientKeyPath); err != nil { - log.Fatalf("Error generating certificates: %s", err) - } - - defaultStore, err := getDefaultStore( - c.GlobalString("storage-path"), - certInfo.CaCertPath, - certInfo.CaKeyPath, - ) - if err != nil { - log.Fatal(err) - } - - mcn, err := newMcn(defaultStore) - if err != nil { - log.Fatal(err) - } - - hostOptions := &libmachine.HostOptions{ - AuthOptions: &auth.AuthOptions{ - CaCertPath: certInfo.CaCertPath, - PrivateKeyPath: certInfo.CaKeyPath, - ClientCertPath: certInfo.ClientCertPath, - ClientKeyPath: certInfo.ClientKeyPath, - ServerCertPath: filepath.Join(utils.GetMachineDir(), name, "server.pem"), - ServerKeyPath: filepath.Join(utils.GetMachineDir(), name, "server-key.pem"), - }, - EngineOptions: &engine.EngineOptions{}, - SwarmOptions: &swarm.SwarmOptions{ - IsSwarm: c.Bool("swarm"), - Master: c.Bool("swarm-master"), - Discovery: c.String("swarm-discovery"), - Address: c.String("swarm-addr"), - Host: c.String("swarm-host"), - }, - } - - host, err := mcn.Create(name, driver, hostOptions, c) - if err != nil { - log.Errorf("Error creating machine: %s", err) - log.Warn("You will want to check the provider to make sure the machine and associated resources were properly removed.") - log.Fatal("Error creating machine") - } - if err := mcn.SetActive(host); err != nil { - log.Fatalf("error setting active host: %v", err) - } - - info := "" - userShell := filepath.Base(os.Getenv("SHELL")) - - switch userShell { - case "fish": - info = fmt.Sprintf("%s env %s | source", c.App.Name, name) - default: - info = fmt.Sprintf(`eval "$(%s env %s)"`, c.App.Name, name) - } - - log.Infof("%q has been created and is now the active machine.", name) - - if info != "" { - log.Infof("To point your Docker client at it, run this in your shell: %s", info) - } -} - -func cmdConfig(c *cli.Context) { - cfg, err := getMachineConfig(c) - if err != nil { - log.Fatal(err) - } - - dockerHost, err := getHost(c).Driver.GetURL() - if err != nil { - log.Fatal(err) - } - - if c.Bool("swarm") { - if !cfg.SwarmOptions.Master { - log.Fatalf("%s is not a swarm master", cfg.machineName) - } - u, err := url.Parse(cfg.SwarmOptions.Host) - if err != nil { - log.Fatal(err) - } - parts := strings.Split(u.Host, ":") - swarmPort := parts[1] - - // get IP of machine to replace in case swarm host is 0.0.0.0 - mUrl, err := url.Parse(dockerHost) - if err != nil { - log.Fatal(err) - } - mParts := strings.Split(mUrl.Host, ":") - machineIp := mParts[0] - - dockerHost = fmt.Sprintf("tcp://%s:%s", machineIp, swarmPort) - } - - log.Debug(dockerHost) - - u, err := url.Parse(cfg.machineUrl) - if err != nil { - log.Fatal(err) - } - - if u.Scheme != "unix" { - // validate cert and regenerate if needed - valid, err := utils.ValidateCertificate( - u.Host, - cfg.caCertPath, - cfg.serverCertPath, - cfg.serverKeyPath, - ) - if err != nil { - log.Fatal(err) - } - - if !valid { - log.Debugf("invalid certs detected; regenerating for %s", u.Host) - - if err := runActionWithContext("configureAuth", c); err != nil { - log.Fatal(err) - } - } - } - - fmt.Printf("--tlsverify --tlscacert=%q --tlscert=%q --tlskey=%q -H=%s", - cfg.caCertPath, cfg.clientCertPath, cfg.clientKeyPath, dockerHost) -} - -func cmdInspect(c *cli.Context) { - prettyJSON, err := json.MarshalIndent(getHost(c), "", " ") - if err != nil { - log.Fatal(err) - } - - fmt.Println(string(prettyJSON)) -} - -func cmdIp(c *cli.Context) { - ip, err := getHost(c).Driver.GetIP() - if err != nil { - log.Fatal(err) - } - - fmt.Println(ip) -} - -func cmdLs(c *cli.Context) { - quiet := c.Bool("quiet") - - certInfo := getCertPathInfo(c) - defaultStore, err := getDefaultStore( - c.GlobalString("storage-path"), - certInfo.CaCertPath, - certInfo.CaKeyPath, - ) - if err != nil { - log.Fatal(err) - } - - mcn, err := newMcn(defaultStore) - if err != nil { - log.Fatal(err) - } - - hostList, err := mcn.List() - if err != nil { - log.Fatal(err) - } - - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) - - if !quiet { - fmt.Fprintln(w, "NAME\tACTIVE\tDRIVER\tSTATE\tURL\tSWARM") - } - - items := []hostListItem{} - hostListItems := make(chan hostListItem) - - swarmMasters := make(map[string]string) - swarmInfo := make(map[string]string) - - for _, host := range hostList { - swarmOptions := host.HostOptions.SwarmOptions - if !quiet { - if swarmOptions.Master { - swarmMasters[swarmOptions.Discovery] = host.Name - } - - if swarmOptions.Discovery != "" { - swarmInfo[host.Name] = swarmOptions.Discovery - } - - go getHostState(*host, defaultStore, hostListItems) - } else { - fmt.Fprintf(w, "%s\n", host.Name) - } - } - - if !quiet { - for i := 0; i < len(hostList); i++ { - items = append(items, <-hostListItems) - } - } - - close(hostListItems) - - sortHostListItemsByName(items) - - for _, item := range items { - activeString := "" - if item.Active { - activeString = "*" - } - - swarmInfo := "" - - if item.SwarmOptions.Discovery != "" { - swarmInfo = swarmMasters[item.SwarmOptions.Discovery] - if item.SwarmOptions.Master { - swarmInfo = fmt.Sprintf("%s (master)", swarmInfo) - } - } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", - item.Name, activeString, item.DriverName, item.State, item.URL, swarmInfo) - } - - w.Flush() -} - -func cmdRegenerateCerts(c *cli.Context) { - force := c.Bool("force") - if force || confirmInput("Regenerate TLS machine certs? Warning: this is irreversible.") { - log.Infof("Regenerating TLS certificates") - if err := runActionWithContext("configureAuth", c); err != nil { - log.Fatal(err) - } - } -} - -func cmdRm(c *cli.Context) { - if len(c.Args()) == 0 { - cli.ShowCommandHelp(c, "rm") - log.Fatal("You must specify a machine name") - } - - force := c.Bool("force") - - isError := false - - certInfo := getCertPathInfo(c) - defaultStore, err := getDefaultStore( - c.GlobalString("storage-path"), - certInfo.CaCertPath, - certInfo.CaKeyPath, - ) - if err != nil { - log.Fatal(err) - } - - mcn, err := newMcn(defaultStore) - if err != nil { - log.Fatal(err) - } - - for _, host := range c.Args() { - if err := mcn.Remove(host, force); err != nil { - log.Errorf("Error removing machine %s: %s", host, err) - isError = true - } - } - if isError { - log.Fatal("There was an error removing a machine. To force remove it, pass the -f option. Warning: this might leave it running on the provider.") - } - log.Print("The machine was successfully removed.") -} - -func cmdEnv(c *cli.Context) { - userShell := filepath.Base(os.Getenv("SHELL")) - if c.Bool("unset") { - switch userShell { - case "fish": - fmt.Printf("set -e DOCKER_TLS_VERIFY;\nset -e DOCKER_CERT_PATH;\nset -e DOCKER_HOST;\n") - default: - fmt.Println("unset DOCKER_TLS_VERIFY DOCKER_CERT_PATH DOCKER_HOST") - } - return - } - - cfg, err := getMachineConfig(c) - if err != nil { - log.Fatal(err) - } - - if cfg.machineUrl == "" { - log.Fatalf("%s is not running. Please start this with docker-machine start %s", cfg.machineName, cfg.machineName) - } - - dockerHost := cfg.machineUrl - if c.Bool("swarm") { - if !cfg.SwarmOptions.Master { - log.Fatalf("%s is not a swarm master", cfg.machineName) - } - u, err := url.Parse(cfg.SwarmOptions.Host) - if err != nil { - log.Fatal(err) - } - parts := strings.Split(u.Host, ":") - swarmPort := parts[1] - - // get IP of machine to replace in case swarm host is 0.0.0.0 - mUrl, err := url.Parse(cfg.machineUrl) - if err != nil { - log.Fatal(err) - } - mParts := strings.Split(mUrl.Host, ":") - machineIp := mParts[0] - - dockerHost = fmt.Sprintf("tcp://%s:%s", machineIp, swarmPort) - } - - u, err := url.Parse(cfg.machineUrl) - if err != nil { - log.Fatal(err) - } - - if u.Scheme != "unix" { - // validate cert and regenerate if needed - valid, err := utils.ValidateCertificate( - u.Host, - cfg.caCertPath, - cfg.serverCertPath, - cfg.serverKeyPath, - ) - if err != nil { - log.Fatal(err) - } - - if !valid { - log.Debugf("invalid certs detected; regenerating for %s", u.Host) - - if err := runActionWithContext("configureAuth", c); err != nil { - log.Fatal(err) - } - } - } - - usageHint := generateUsageHint(c.Args().First(), userShell) - - switch userShell { - case "fish": - fmt.Printf("set -x DOCKER_TLS_VERIFY 1;\nset -x DOCKER_CERT_PATH %q;\nset -x DOCKER_HOST %s;\n\n%s\n", - cfg.machineDir, dockerHost, usageHint) - default: - fmt.Printf("export DOCKER_TLS_VERIFY=1\nexport DOCKER_CERT_PATH=%q\nexport DOCKER_HOST=%s\n\n%s\n", - cfg.machineDir, dockerHost, usageHint) - } -} - -func generateUsageHint(machineName string, userShell string) string { - cmd := "" - switch userShell { - case "fish": - if machineName != "" { - cmd = fmt.Sprintf("eval (docker-machine env %s)", machineName) - } else { - cmd = "eval (docker-machine env)" - } - default: - if machineName != "" { - cmd = fmt.Sprintf("eval \"$(docker-machine env %s)\"", machineName) - } else { - cmd = "eval \"$(docker-machine env)\"" - } - } - - return fmt.Sprintf("# Run this command to configure your shell: %s\n", cmd) -} - -func cmdSsh(c *cli.Context) { - var ( - err error - sshCmd *exec.Cmd - ) - name := c.Args().First() - - certInfo := getCertPathInfo(c) - defaultStore, err := getDefaultStore( - c.GlobalString("storage-path"), - certInfo.CaCertPath, - certInfo.CaKeyPath, - ) - if err != nil { - log.Fatal(err) - } - - mcn, err := newMcn(defaultStore) - if err != nil { - log.Fatal(err) - } - - if name == "" { - host, err := mcn.GetActive() - if err != nil { - log.Fatalf("unable to get active host: %v", err) - } - - if host == nil { - log.Fatalf("There is no active host. Please set it with %s active .", c.App.Name) - } - - name = host.Name - } - - host, err := mcn.Get(name) - if err != nil { - log.Fatal(err) - } - - _, err = host.GetURL() - if err != nil { - if err == drivers.ErrHostIsNotRunning { - log.Fatalf("%s is not running. Please start this with docker-machine start %s", host.Name, host.Name) - } else { - log.Fatalf("Unexpected error getting machine url: %s", err) - } - } - - if len(c.Args()) <= 1 { - sshCmd, err = host.GetSSHCommand() - } else { - sshCmd, err = host.GetSSHCommand(c.Args()[1:]...) - } - if err != nil { - log.Fatal(err) - } - - sshCmd.Stdin = os.Stdin - sshCmd.Stdout = os.Stdout - sshCmd.Stderr = os.Stderr - if err := sshCmd.Run(); err != nil { - log.Fatal(err) - } -} - // machineCommand maps the command name to the corresponding machine command. // We run commands concurrently and communicate back an error if there was one. func machineCommand(actionName string, host *libmachine.Host, errorChan chan<- error) { @@ -965,55 +431,6 @@ func runActionWithContext(actionName string, c *cli.Context) error { return nil } -func cmdStart(c *cli.Context) { - if err := runActionWithContext("start", c); err != nil { - log.Fatal(err) - } -} - -func cmdStop(c *cli.Context) { - if err := runActionWithContext("stop", c); err != nil { - log.Fatal(err) - } -} - -func cmdRestart(c *cli.Context) { - if err := runActionWithContext("restart", c); err != nil { - log.Fatal(err) - } -} - -func cmdKill(c *cli.Context) { - if err := runActionWithContext("kill", c); err != nil { - log.Fatal(err) - } -} - -func cmdUpgrade(c *cli.Context) { - if err := runActionWithContext("upgrade", c); err != nil { - log.Fatal(err) - } -} - -func cmdUrl(c *cli.Context) { - url, err := getHost(c).GetURL() - if err != nil { - log.Fatal(err) - } - - fmt.Println(url) -} - -func cmdNotFound(c *cli.Context, command string) { - log.Fatalf( - "%s: '%s' is not a %s command. See '%s --help'.", - c.App.Name, - command, - c.App.Name, - c.App.Name, - ) -} - func getHosts(c *cli.Context) ([]*libmachine.Host, error) { machines := []*libmachine.Host{} for _, n := range c.Args() { diff --git a/commands_test.go b/commands/commands_test.go similarity index 51% rename from commands_test.go rename to commands/commands_test.go index d20334be6b..7506c07948 100644 --- a/commands_test.go +++ b/commands/commands_test.go @@ -1,18 +1,13 @@ -package main +package commands import ( - "bytes" - "flag" "fmt" - "io" "io/ioutil" "os" - "path/filepath" - "strings" "testing" - "github.com/codegangsta/cli" "github.com/docker/machine/drivers/fakedriver" + _ "github.com/docker/machine/drivers/none" "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/auth" "github.com/docker/machine/libmachine/engine" @@ -310,288 +305,3 @@ func TestRunActionForeachMachine(t *testing.T) { } } } - -func TestCmdConfig(t *testing.T) { - defer cleanup() - - stdout := os.Stdout - r, w, _ := os.Pipe() - - os.Stdout = w - - defer func() { - os.Stdout = stdout - }() - - store, err := getTestStore() - if err != nil { - t.Fatal(err) - } - - mcn, err := libmachine.New(store) - if err != nil { - t.Fatal(err) - } - - flags := getTestDriverFlags() - hostOptions := &libmachine.HostOptions{ - EngineOptions: &engine.EngineOptions{}, - SwarmOptions: &swarm.SwarmOptions{ - Master: false, - Discovery: "", - Address: "", - Host: "", - }, - AuthOptions: &auth.AuthOptions{}, - } - - host, err := mcn.Create("test-a", "none", hostOptions, flags) - if err != nil { - t.Fatal(err) - } - - if err := store.SetActive(host); err != nil { - t.Fatalf("error setting active host: %v", err) - } - - outStr := make(chan string) - - go func() { - var testOutput bytes.Buffer - io.Copy(&testOutput, r) - outStr <- testOutput.String() - }() - - set := flag.NewFlagSet("config", 0) - globalSet := flag.NewFlagSet("test", 0) - globalSet.String("storage-path", store.GetPath(), "") - - c := cli.NewContext(nil, set, globalSet) - - cmdConfig(c) - - w.Close() - - out := <-outStr - - if !strings.Contains(out, "--tlsverify") { - t.Fatalf("Expect --tlsverify") - } - - testMachineDir := filepath.Join(store.GetPath(), "machines", host.Name) - - tlscacert := fmt.Sprintf("--tlscacert=\"%s/ca.pem\"", testMachineDir) - if !strings.Contains(out, tlscacert) { - t.Fatalf("Expected to find %s in %s", tlscacert, out) - } - - tlscert := fmt.Sprintf("--tlscert=\"%s/cert.pem\"", testMachineDir) - if !strings.Contains(out, tlscert) { - t.Fatalf("Expected to find %s in %s", tlscert, out) - } - - tlskey := fmt.Sprintf("--tlskey=\"%s/key.pem\"", testMachineDir) - if !strings.Contains(out, tlskey) { - t.Fatalf("Expected to find %s in %s", tlskey, out) - } - - if !strings.Contains(out, "-H=unix:///var/run/docker.sock") { - t.Fatalf("Expect docker host URL") - } -} - -func TestCmdEnvBash(t *testing.T) { - stdout := os.Stdout - shell := os.Getenv("SHELL") - r, w, _ := os.Pipe() - - os.Stdout = w - os.Setenv("MACHINE_STORAGE_PATH", TestStoreDir) - os.Setenv("SHELL", "/bin/bash") - - defer func() { - os.Setenv("MACHINE_STORAGE_PATH", "") - os.Setenv("SHELL", shell) - os.Stdout = stdout - }() - - if err := clearHosts(); err != nil { - t.Fatal(err) - } - - flags := getTestDriverFlags() - - store, sErr := getTestStore() - if sErr != nil { - t.Fatal(sErr) - } - - mcn, err := libmachine.New(store) - if err != nil { - t.Fatal(err) - } - - hostOptions := &libmachine.HostOptions{ - EngineOptions: &engine.EngineOptions{}, - SwarmOptions: &swarm.SwarmOptions{ - Master: false, - Discovery: "", - Address: "", - Host: "", - }, - AuthOptions: &auth.AuthOptions{}, - } - - host, err := mcn.Create("test-a", "none", hostOptions, flags) - if err != nil { - t.Fatal(err) - } - - host, err = mcn.Get("test-a") - if err != nil { - t.Fatalf("error loading host: %v", err) - } - - if err := mcn.SetActive(host); err != nil { - t.Fatalf("error setting active host: %v", err) - } - - outStr := make(chan string) - - go func() { - var testOutput bytes.Buffer - io.Copy(&testOutput, r) - outStr <- testOutput.String() - }() - - set := flag.NewFlagSet("config", 0) - c := cli.NewContext(nil, set, set) - cmdEnv(c) - - w.Close() - - out := <-outStr - - // parse the output into a map of envvar:value for easier testing below - envvars := make(map[string]string) - for _, e := range strings.Split(strings.TrimSpace(out), "\n") { - if !strings.HasPrefix(e, "export ") { - continue - } - kv := strings.SplitN(e, "=", 2) - key, value := kv[0], kv[1] - envvars[strings.Replace(key, "export ", "", 1)] = value - } - - testMachineDir := filepath.Join(store.GetPath(), "machines", host.Name) - - expected := map[string]string{ - "DOCKER_TLS_VERIFY": "1", - "DOCKER_CERT_PATH": fmt.Sprintf("\"%s\"", testMachineDir), - "DOCKER_HOST": "unix:///var/run/docker.sock", - } - - for k, v := range envvars { - if v != expected[k] { - t.Fatalf("Expected %s == <%s>, but was <%s>", k, expected[k], v) - } - } -} - -func TestCmdEnvFish(t *testing.T) { - stdout := os.Stdout - shell := os.Getenv("SHELL") - r, w, _ := os.Pipe() - - os.Stdout = w - os.Setenv("MACHINE_STORAGE_PATH", TestStoreDir) - os.Setenv("SHELL", "/bin/fish") - - defer func() { - os.Setenv("MACHINE_STORAGE_PATH", "") - os.Setenv("SHELL", shell) - os.Stdout = stdout - }() - - if err := clearHosts(); err != nil { - t.Fatal(err) - } - - flags := getTestDriverFlags() - - store, err := getTestStore() - if err != nil { - t.Fatal(err) - } - - mcn, err := libmachine.New(store) - if err != nil { - t.Fatal(err) - } - - hostOptions := &libmachine.HostOptions{ - EngineOptions: &engine.EngineOptions{}, - SwarmOptions: &swarm.SwarmOptions{ - Master: false, - Discovery: "", - Address: "", - Host: "", - }, - AuthOptions: &auth.AuthOptions{}, - } - - host, err := mcn.Create("test-a", "none", hostOptions, flags) - if err != nil { - t.Fatal(err) - } - - host, err = mcn.Get("test-a") - if err != nil { - t.Fatalf("error loading host: %v", err) - } - - if err := mcn.SetActive(host); err != nil { - t.Fatalf("error setting active host: %v", err) - } - - outStr := make(chan string) - - go func() { - var testOutput bytes.Buffer - io.Copy(&testOutput, r) - outStr <- testOutput.String() - }() - - set := flag.NewFlagSet("config", 0) - c := cli.NewContext(nil, set, set) - cmdEnv(c) - - w.Close() - - out := <-outStr - - // parse the output into a map of envvar:value for easier testing below - envvars := make(map[string]string) - for _, e := range strings.Split(strings.TrimSuffix(out, ";\n"), ";\n") { - if !strings.HasPrefix(e, "set -x ") { - continue - } - kv := strings.SplitN(strings.Replace(e, "set -x ", "", 1), " ", 2) - key, value := kv[0], kv[1] - envvars[key] = value - } - - testMachineDir := filepath.Join(store.GetPath(), "machines", host.Name) - - expected := map[string]string{ - "DOCKER_TLS_VERIFY": "1", - "DOCKER_CERT_PATH": fmt.Sprintf("\"%s\"", testMachineDir), - "DOCKER_HOST": "unix:///var/run/docker.sock", - } - - for k, v := range envvars { - if v != expected[k] { - t.Fatalf("Expected %s == <%s>, but was <%s>", k, expected[k], v) - } - } -} diff --git a/commands/config.go b/commands/config.go new file mode 100644 index 0000000000..fdfa9fe0e6 --- /dev/null +++ b/commands/config.go @@ -0,0 +1,77 @@ +package commands + +import ( + "fmt" + "net/url" + "strings" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" + "github.com/docker/machine/utils" +) + +func cmdConfig(c *cli.Context) { + cfg, err := getMachineConfig(c) + if err != nil { + log.Fatal(err) + } + + dockerHost, err := getHost(c).Driver.GetURL() + if err != nil { + log.Fatal(err) + } + + if c.Bool("swarm") { + if !cfg.SwarmOptions.Master { + log.Fatalf("%s is not a swarm master", cfg.machineName) + } + u, err := url.Parse(cfg.SwarmOptions.Host) + if err != nil { + log.Fatal(err) + } + parts := strings.Split(u.Host, ":") + swarmPort := parts[1] + + // get IP of machine to replace in case swarm host is 0.0.0.0 + mUrl, err := url.Parse(dockerHost) + if err != nil { + log.Fatal(err) + } + mParts := strings.Split(mUrl.Host, ":") + machineIp := mParts[0] + + dockerHost = fmt.Sprintf("tcp://%s:%s", machineIp, swarmPort) + } + + log.Debug(dockerHost) + + u, err := url.Parse(cfg.machineUrl) + if err != nil { + log.Fatal(err) + } + + if u.Scheme != "unix" { + // validate cert and regenerate if needed + valid, err := utils.ValidateCertificate( + u.Host, + cfg.caCertPath, + cfg.serverCertPath, + cfg.serverKeyPath, + ) + if err != nil { + log.Fatal(err) + } + + if !valid { + log.Debugf("invalid certs detected; regenerating for %s", u.Host) + + if err := runActionWithContext("configureAuth", c); err != nil { + log.Fatal(err) + } + } + } + + fmt.Printf("--tlsverify --tlscacert=%q --tlscert=%q --tlskey=%q -H=%s", + cfg.caCertPath, cfg.clientCertPath, cfg.clientKeyPath, dockerHost) +} diff --git a/commands/config_test.go b/commands/config_test.go new file mode 100644 index 0000000000..e2f2cf884f --- /dev/null +++ b/commands/config_test.go @@ -0,0 +1,107 @@ +package commands + +import ( + "bytes" + "flag" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/codegangsta/cli" + "github.com/docker/machine/libmachine" + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/swarm" +) + +func TestCmdConfig(t *testing.T) { + defer cleanup() + + stdout := os.Stdout + r, w, _ := os.Pipe() + + os.Stdout = w + + defer func() { + os.Stdout = stdout + }() + + store, err := getTestStore() + if err != nil { + t.Fatal(err) + } + + mcn, err := libmachine.New(store) + if err != nil { + t.Fatal(err) + } + + flags := getTestDriverFlags() + hostOptions := &libmachine.HostOptions{ + EngineOptions: &engine.EngineOptions{}, + SwarmOptions: &swarm.SwarmOptions{ + Master: false, + Discovery: "", + Address: "", + Host: "", + }, + AuthOptions: &auth.AuthOptions{}, + } + + host, err := mcn.Create("test-a", "none", hostOptions, flags) + if err != nil { + t.Fatal(err) + } + + if err := store.SetActive(host); err != nil { + t.Fatalf("error setting active host: %v", err) + } + + outStr := make(chan string) + + go func() { + var testOutput bytes.Buffer + io.Copy(&testOutput, r) + outStr <- testOutput.String() + }() + + set := flag.NewFlagSet("config", 0) + globalSet := flag.NewFlagSet("test", 0) + globalSet.String("storage-path", store.GetPath(), "") + + c := cli.NewContext(nil, set, globalSet) + + cmdConfig(c) + + w.Close() + + out := <-outStr + + if !strings.Contains(out, "--tlsverify") { + t.Fatalf("Expect --tlsverify") + } + + testMachineDir := filepath.Join(store.GetPath(), "machines", host.Name) + + tlscacert := fmt.Sprintf("--tlscacert=\"%s/ca.pem\"", testMachineDir) + if !strings.Contains(out, tlscacert) { + t.Fatalf("Expected to find %s in %s", tlscacert, out) + } + + tlscert := fmt.Sprintf("--tlscert=\"%s/cert.pem\"", testMachineDir) + if !strings.Contains(out, tlscert) { + t.Fatalf("Expected to find %s in %s", tlscert, out) + } + + tlskey := fmt.Sprintf("--tlskey=\"%s/key.pem\"", testMachineDir) + if !strings.Contains(out, tlskey) { + t.Fatalf("Expected to find %s in %s", tlskey, out) + } + + if !strings.Contains(out, "-H=unix:///var/run/docker.sock") { + t.Fatalf("Expect docker host URL") + } +} diff --git a/commands/create.go b/commands/create.go new file mode 100644 index 0000000000..5d00d8e705 --- /dev/null +++ b/commands/create.go @@ -0,0 +1,126 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" + "github.com/docker/machine/drivers" + "github.com/docker/machine/libmachine" + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/swarm" + "github.com/docker/machine/utils" +) + +func cmdCreate(c *cli.Context) { + var ( + err error + ) + driver := c.String("driver") + name := c.Args().First() + + // TODO: Not really a fan of "none" as the default driver... + if driver != "none" { + c.App.Commands, err = trimDriverFlags(driver, c.App.Commands) + if err != nil { + log.Fatal(err) + } + } + + if name == "" { + cli.ShowCommandHelp(c, "create") + log.Fatal("You must specify a machine name") + } + + certInfo := getCertPathInfo(c) + + if err := setupCertificates( + certInfo.CaCertPath, + certInfo.CaKeyPath, + certInfo.ClientCertPath, + certInfo.ClientKeyPath); err != nil { + log.Fatalf("Error generating certificates: %s", err) + } + + defaultStore, err := getDefaultStore( + c.GlobalString("storage-path"), + certInfo.CaCertPath, + certInfo.CaKeyPath, + ) + if err != nil { + log.Fatal(err) + } + + mcn, err := newMcn(defaultStore) + if err != nil { + log.Fatal(err) + } + + hostOptions := &libmachine.HostOptions{ + AuthOptions: &auth.AuthOptions{ + CaCertPath: certInfo.CaCertPath, + PrivateKeyPath: certInfo.CaKeyPath, + ClientCertPath: certInfo.ClientCertPath, + ClientKeyPath: certInfo.ClientKeyPath, + ServerCertPath: filepath.Join(utils.GetMachineDir(), name, "server.pem"), + ServerKeyPath: filepath.Join(utils.GetMachineDir(), name, "server-key.pem"), + }, + EngineOptions: &engine.EngineOptions{}, + SwarmOptions: &swarm.SwarmOptions{ + IsSwarm: c.Bool("swarm"), + Master: c.Bool("swarm-master"), + Discovery: c.String("swarm-discovery"), + Address: c.String("swarm-addr"), + Host: c.String("swarm-host"), + }, + } + + host, err := mcn.Create(name, driver, hostOptions, c) + if err != nil { + log.Errorf("Error creating machine: %s", err) + log.Warn("You will want to check the provider to make sure the machine and associated resources were properly removed.") + log.Fatal("Error creating machine") + } + if err := mcn.SetActive(host); err != nil { + log.Fatalf("error setting active host: %v", err) + } + + info := "" + userShell := filepath.Base(os.Getenv("SHELL")) + + switch userShell { + case "fish": + info = fmt.Sprintf("%s env %s | source", c.App.Name, name) + default: + info = fmt.Sprintf(`eval "$(%s env %s)"`, c.App.Name, name) + } + + log.Infof("%q has been created and is now the active machine.", name) + + if info != "" { + log.Infof("To point your Docker client at it, run this in your shell: %s", info) + } +} + +// If the user has specified a driver, they should not see the flags for all +// of the drivers in `docker-machine create`. This method replaces the 100+ +// create flags with only the ones applicable to the driver specified +func trimDriverFlags(driver string, cmds []cli.Command) ([]cli.Command, error) { + filteredCmds := cmds + driverFlags, err := drivers.GetCreateFlagsForDriver(driver) + if err != nil { + return nil, err + } + + for i, cmd := range cmds { + if cmd.HasName("create") { + filteredCmds[i].Flags = append(driverFlags, sharedCreateFlags...) + } + } + + return filteredCmds, nil +} diff --git a/commands/create_test.go b/commands/create_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/create_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/env.go b/commands/env.go new file mode 100644 index 0000000000..2e688be2db --- /dev/null +++ b/commands/env.go @@ -0,0 +1,116 @@ +package commands + +import ( + "fmt" + "net/url" + "os" + "path/filepath" + "strings" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" + "github.com/docker/machine/utils" +) + +func cmdEnv(c *cli.Context) { + userShell := filepath.Base(os.Getenv("SHELL")) + if c.Bool("unset") { + switch userShell { + case "fish": + fmt.Printf("set -e DOCKER_TLS_VERIFY;\nset -e DOCKER_CERT_PATH;\nset -e DOCKER_HOST;\n") + default: + fmt.Println("unset DOCKER_TLS_VERIFY DOCKER_CERT_PATH DOCKER_HOST") + } + return + } + + cfg, err := getMachineConfig(c) + if err != nil { + log.Fatal(err) + } + + if cfg.machineUrl == "" { + log.Fatalf("%s is not running. Please start this with docker-machine start %s", cfg.machineName, cfg.machineName) + } + + dockerHost := cfg.machineUrl + if c.Bool("swarm") { + if !cfg.SwarmOptions.Master { + log.Fatalf("%s is not a swarm master", cfg.machineName) + } + u, err := url.Parse(cfg.SwarmOptions.Host) + if err != nil { + log.Fatal(err) + } + parts := strings.Split(u.Host, ":") + swarmPort := parts[1] + + // get IP of machine to replace in case swarm host is 0.0.0.0 + mUrl, err := url.Parse(cfg.machineUrl) + if err != nil { + log.Fatal(err) + } + mParts := strings.Split(mUrl.Host, ":") + machineIp := mParts[0] + + dockerHost = fmt.Sprintf("tcp://%s:%s", machineIp, swarmPort) + } + + u, err := url.Parse(cfg.machineUrl) + if err != nil { + log.Fatal(err) + } + + if u.Scheme != "unix" { + // validate cert and regenerate if needed + valid, err := utils.ValidateCertificate( + u.Host, + cfg.caCertPath, + cfg.serverCertPath, + cfg.serverKeyPath, + ) + if err != nil { + log.Fatal(err) + } + + if !valid { + log.Debugf("invalid certs detected; regenerating for %s", u.Host) + + if err := runActionWithContext("configureAuth", c); err != nil { + log.Fatal(err) + } + } + } + + usageHint := generateUsageHint(c.Args().First(), userShell) + + switch userShell { + case "fish": + fmt.Printf("set -x DOCKER_TLS_VERIFY 1;\nset -x DOCKER_CERT_PATH %q;\nset -x DOCKER_HOST %s;\n\n%s\n", + cfg.machineDir, dockerHost, usageHint) + default: + fmt.Printf("export DOCKER_TLS_VERIFY=1\nexport DOCKER_CERT_PATH=%q\nexport DOCKER_HOST=%s\n\n%s\n", + cfg.machineDir, dockerHost, usageHint) + } +} + +func generateUsageHint(machineName string, userShell string) string { + cmd := "" + switch userShell { + case "fish": + if machineName != "" { + cmd = fmt.Sprintf("eval (docker-machine env %s)", machineName) + } else { + cmd = "eval (docker-machine env)" + } + default: + if machineName != "" { + cmd = fmt.Sprintf("eval \"$(docker-machine env %s)\"", machineName) + } else { + cmd = "eval \"$(docker-machine env)\"" + } + } + + return fmt.Sprintf("# Run this command to configure your shell: %s\n", cmd) +} diff --git a/commands/env_test.go b/commands/env_test.go new file mode 100644 index 0000000000..e65bd80d0d --- /dev/null +++ b/commands/env_test.go @@ -0,0 +1,214 @@ +package commands + +import ( + "bytes" + "flag" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/codegangsta/cli" + "github.com/docker/machine/libmachine" + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/swarm" +) + +func TestCmdEnvBash(t *testing.T) { + stdout := os.Stdout + shell := os.Getenv("SHELL") + r, w, _ := os.Pipe() + + os.Stdout = w + os.Setenv("MACHINE_STORAGE_PATH", TestStoreDir) + os.Setenv("SHELL", "/bin/bash") + + defer func() { + os.Setenv("MACHINE_STORAGE_PATH", "") + os.Setenv("SHELL", shell) + os.Stdout = stdout + }() + + if err := clearHosts(); err != nil { + t.Fatal(err) + } + + flags := getTestDriverFlags() + + store, sErr := getTestStore() + if sErr != nil { + t.Fatal(sErr) + } + + mcn, err := libmachine.New(store) + if err != nil { + t.Fatal(err) + } + + hostOptions := &libmachine.HostOptions{ + EngineOptions: &engine.EngineOptions{}, + SwarmOptions: &swarm.SwarmOptions{ + Master: false, + Discovery: "", + Address: "", + Host: "", + }, + AuthOptions: &auth.AuthOptions{}, + } + + host, err := mcn.Create("test-a", "none", hostOptions, flags) + if err != nil { + t.Fatal(err) + } + + host, err = mcn.Get("test-a") + if err != nil { + t.Fatalf("error loading host: %v", err) + } + + if err := mcn.SetActive(host); err != nil { + t.Fatalf("error setting active host: %v", err) + } + + outStr := make(chan string) + + go func() { + var testOutput bytes.Buffer + io.Copy(&testOutput, r) + outStr <- testOutput.String() + }() + + set := flag.NewFlagSet("config", 0) + c := cli.NewContext(nil, set, set) + cmdEnv(c) + + w.Close() + + out := <-outStr + + // parse the output into a map of envvar:value for easier testing below + envvars := make(map[string]string) + for _, e := range strings.Split(strings.TrimSpace(out), "\n") { + if !strings.HasPrefix(e, "export ") { + continue + } + kv := strings.SplitN(e, "=", 2) + key, value := kv[0], kv[1] + envvars[strings.Replace(key, "export ", "", 1)] = value + } + + testMachineDir := filepath.Join(store.GetPath(), "machines", host.Name) + + expected := map[string]string{ + "DOCKER_TLS_VERIFY": "1", + "DOCKER_CERT_PATH": fmt.Sprintf("\"%s\"", testMachineDir), + "DOCKER_HOST": "unix:///var/run/docker.sock", + } + + for k, v := range envvars { + if v != expected[k] { + t.Fatalf("Expected %s == <%s>, but was <%s>", k, expected[k], v) + } + } +} + +func TestCmdEnvFish(t *testing.T) { + stdout := os.Stdout + shell := os.Getenv("SHELL") + r, w, _ := os.Pipe() + + os.Stdout = w + os.Setenv("MACHINE_STORAGE_PATH", TestStoreDir) + os.Setenv("SHELL", "/bin/fish") + + defer func() { + os.Setenv("MACHINE_STORAGE_PATH", "") + os.Setenv("SHELL", shell) + os.Stdout = stdout + }() + + if err := clearHosts(); err != nil { + t.Fatal(err) + } + + flags := getTestDriverFlags() + + store, err := getTestStore() + if err != nil { + t.Fatal(err) + } + + mcn, err := libmachine.New(store) + if err != nil { + t.Fatal(err) + } + + hostOptions := &libmachine.HostOptions{ + EngineOptions: &engine.EngineOptions{}, + SwarmOptions: &swarm.SwarmOptions{ + Master: false, + Discovery: "", + Address: "", + Host: "", + }, + AuthOptions: &auth.AuthOptions{}, + } + + host, err := mcn.Create("test-a", "none", hostOptions, flags) + if err != nil { + t.Fatal(err) + } + + host, err = mcn.Get("test-a") + if err != nil { + t.Fatalf("error loading host: %v", err) + } + + if err := mcn.SetActive(host); err != nil { + t.Fatalf("error setting active host: %v", err) + } + + outStr := make(chan string) + + go func() { + var testOutput bytes.Buffer + io.Copy(&testOutput, r) + outStr <- testOutput.String() + }() + + set := flag.NewFlagSet("config", 0) + c := cli.NewContext(nil, set, set) + cmdEnv(c) + + w.Close() + + out := <-outStr + + // parse the output into a map of envvar:value for easier testing below + envvars := make(map[string]string) + for _, e := range strings.Split(strings.TrimSuffix(out, ";\n"), ";\n") { + if !strings.HasPrefix(e, "set -x ") { + continue + } + kv := strings.SplitN(strings.Replace(e, "set -x ", "", 1), " ", 2) + key, value := kv[0], kv[1] + envvars[key] = value + } + + testMachineDir := filepath.Join(store.GetPath(), "machines", host.Name) + + expected := map[string]string{ + "DOCKER_TLS_VERIFY": "1", + "DOCKER_CERT_PATH": fmt.Sprintf("\"%s\"", testMachineDir), + "DOCKER_HOST": "unix:///var/run/docker.sock", + } + + for k, v := range envvars { + if v != expected[k] { + t.Fatalf("Expected %s == <%s>, but was <%s>", k, expected[k], v) + } + } +} diff --git a/commands/inspect.go b/commands/inspect.go new file mode 100644 index 0000000000..60d1e5ac8b --- /dev/null +++ b/commands/inspect.go @@ -0,0 +1,19 @@ +package commands + +import ( + "encoding/json" + "fmt" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" +) + +func cmdInspect(c *cli.Context) { + prettyJSON, err := json.MarshalIndent(getHost(c), "", " ") + if err != nil { + log.Fatal(err) + } + + fmt.Println(string(prettyJSON)) +} diff --git a/commands/inspect_test.go b/commands/inspect_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/inspect_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/ip.go b/commands/ip.go new file mode 100644 index 0000000000..c280a61051 --- /dev/null +++ b/commands/ip.go @@ -0,0 +1,18 @@ +package commands + +import ( + "fmt" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" +) + +func cmdIp(c *cli.Context) { + ip, err := getHost(c).Driver.GetIP() + if err != nil { + log.Fatal(err) + } + + fmt.Println(ip) +} diff --git a/commands/ip_test.go b/commands/ip_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/ip_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/kill.go b/commands/kill.go new file mode 100644 index 0000000000..8bed899f10 --- /dev/null +++ b/commands/kill.go @@ -0,0 +1,13 @@ +package commands + +import ( + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" +) + +func cmdKill(c *cli.Context) { + if err := runActionWithContext("kill", c); err != nil { + log.Fatal(err) + } +} diff --git a/commands/kill_test.go b/commands/kill_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/kill_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/ls.go b/commands/ls.go new file mode 100644 index 0000000000..6bf5a1927f --- /dev/null +++ b/commands/ls.go @@ -0,0 +1,94 @@ +package commands + +import ( + "fmt" + "os" + "text/tabwriter" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" +) + +func cmdLs(c *cli.Context) { + quiet := c.Bool("quiet") + + certInfo := getCertPathInfo(c) + defaultStore, err := getDefaultStore( + c.GlobalString("storage-path"), + certInfo.CaCertPath, + certInfo.CaKeyPath, + ) + if err != nil { + log.Fatal(err) + } + + mcn, err := newMcn(defaultStore) + if err != nil { + log.Fatal(err) + } + + hostList, err := mcn.List() + if err != nil { + log.Fatal(err) + } + + w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) + + if !quiet { + fmt.Fprintln(w, "NAME\tACTIVE\tDRIVER\tSTATE\tURL\tSWARM") + } + + items := []hostListItem{} + hostListItems := make(chan hostListItem) + + swarmMasters := make(map[string]string) + swarmInfo := make(map[string]string) + + for _, host := range hostList { + swarmOptions := host.HostOptions.SwarmOptions + if !quiet { + if swarmOptions.Master { + swarmMasters[swarmOptions.Discovery] = host.Name + } + + if swarmOptions.Discovery != "" { + swarmInfo[host.Name] = swarmOptions.Discovery + } + + go getHostState(*host, defaultStore, hostListItems) + } else { + fmt.Fprintf(w, "%s\n", host.Name) + } + } + + if !quiet { + for i := 0; i < len(hostList); i++ { + items = append(items, <-hostListItems) + } + } + + close(hostListItems) + + sortHostListItemsByName(items) + + for _, item := range items { + activeString := "" + if item.Active { + activeString = "*" + } + + swarmInfo := "" + + if item.SwarmOptions.Discovery != "" { + swarmInfo = swarmMasters[item.SwarmOptions.Discovery] + if item.SwarmOptions.Master { + swarmInfo = fmt.Sprintf("%s (master)", swarmInfo) + } + } + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", + item.Name, activeString, item.DriverName, item.State, item.URL, swarmInfo) + } + + w.Flush() +} diff --git a/commands/ls_test.go b/commands/ls_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/ls_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/regeneratecerts.go b/commands/regeneratecerts.go new file mode 100644 index 0000000000..0a4b35f2d2 --- /dev/null +++ b/commands/regeneratecerts.go @@ -0,0 +1,16 @@ +package commands + +import ( + log "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" +) + +func cmdRegenerateCerts(c *cli.Context) { + force := c.Bool("force") + if force || confirmInput("Regenerate TLS machine certs? Warning: this is irreversible.") { + log.Infof("Regenerating TLS certificates") + if err := runActionWithContext("configureAuth", c); err != nil { + log.Fatal(err) + } + } +} diff --git a/commands/regeneratecerts_test.go b/commands/regeneratecerts_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/regeneratecerts_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/restart.go b/commands/restart.go new file mode 100644 index 0000000000..99b8365315 --- /dev/null +++ b/commands/restart.go @@ -0,0 +1,13 @@ +package commands + +import ( + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" +) + +func cmdRestart(c *cli.Context) { + if err := runActionWithContext("restart", c); err != nil { + log.Fatal(err) + } +} diff --git a/commands/restart_test.go b/commands/restart_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/restart_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/rm.go b/commands/rm.go new file mode 100644 index 0000000000..75ec1c133d --- /dev/null +++ b/commands/rm.go @@ -0,0 +1,43 @@ +package commands + +import ( + log "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" +) + +func cmdRm(c *cli.Context) { + if len(c.Args()) == 0 { + cli.ShowCommandHelp(c, "rm") + log.Fatal("You must specify a machine name") + } + + force := c.Bool("force") + + isError := false + + certInfo := getCertPathInfo(c) + defaultStore, err := getDefaultStore( + c.GlobalString("storage-path"), + certInfo.CaCertPath, + certInfo.CaKeyPath, + ) + if err != nil { + log.Fatal(err) + } + + mcn, err := newMcn(defaultStore) + if err != nil { + log.Fatal(err) + } + + for _, host := range c.Args() { + if err := mcn.Remove(host, force); err != nil { + log.Errorf("Error removing machine %s: %s", host, err) + isError = true + } + } + if isError { + log.Fatal("There was an error removing a machine. To force remove it, pass the -f option. Warning: this might leave it running on the provider.") + } + log.Print("The machine was successfully removed.") +} diff --git a/commands/rm_test.go b/commands/rm_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/rm_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/ssh.go b/commands/ssh.go new file mode 100644 index 0000000000..e2de4c2ca5 --- /dev/null +++ b/commands/ssh.go @@ -0,0 +1,77 @@ +package commands + +import ( + "os" + "os/exec" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" + "github.com/docker/machine/drivers" +) + +func cmdSsh(c *cli.Context) { + var ( + err error + sshCmd *exec.Cmd + ) + name := c.Args().First() + + certInfo := getCertPathInfo(c) + defaultStore, err := getDefaultStore( + c.GlobalString("storage-path"), + certInfo.CaCertPath, + certInfo.CaKeyPath, + ) + if err != nil { + log.Fatal(err) + } + + mcn, err := newMcn(defaultStore) + if err != nil { + log.Fatal(err) + } + + if name == "" { + host, err := mcn.GetActive() + if err != nil { + log.Fatalf("unable to get active host: %v", err) + } + + if host == nil { + log.Fatalf("There is no active host. Please set it with %s active .", c.App.Name) + } + + name = host.Name + } + + host, err := mcn.Get(name) + if err != nil { + log.Fatal(err) + } + + _, err = host.GetURL() + if err != nil { + if err == drivers.ErrHostIsNotRunning { + log.Fatalf("%s is not running. Please start this with docker-machine start %s", host.Name, host.Name) + } else { + log.Fatalf("Unexpected error getting machine url: %s", err) + } + } + + if len(c.Args()) <= 1 { + sshCmd, err = host.GetSSHCommand() + } else { + sshCmd, err = host.GetSSHCommand(c.Args()[1:]...) + } + if err != nil { + log.Fatal(err) + } + + sshCmd.Stdin = os.Stdin + sshCmd.Stdout = os.Stdout + sshCmd.Stderr = os.Stderr + if err := sshCmd.Run(); err != nil { + log.Fatal(err) + } +} diff --git a/commands/ssh_test.go b/commands/ssh_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/ssh_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/start.go b/commands/start.go new file mode 100644 index 0000000000..d82ed69c00 --- /dev/null +++ b/commands/start.go @@ -0,0 +1,13 @@ +package commands + +import ( + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" +) + +func cmdStart(c *cli.Context) { + if err := runActionWithContext("start", c); err != nil { + log.Fatal(err) + } +} diff --git a/commands/start_test.go b/commands/start_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/start_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/stop.go b/commands/stop.go new file mode 100644 index 0000000000..f5d51751f4 --- /dev/null +++ b/commands/stop.go @@ -0,0 +1,13 @@ +package commands + +import ( + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" +) + +func cmdStop(c *cli.Context) { + if err := runActionWithContext("stop", c); err != nil { + log.Fatal(err) + } +} diff --git a/commands/stop_test.go b/commands/stop_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/stop_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/upgrade.go b/commands/upgrade.go new file mode 100644 index 0000000000..5326aefee9 --- /dev/null +++ b/commands/upgrade.go @@ -0,0 +1,13 @@ +package commands + +import ( + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" +) + +func cmdUpgrade(c *cli.Context) { + if err := runActionWithContext("upgrade", c); err != nil { + log.Fatal(err) + } +} diff --git a/commands/upgrade_test.go b/commands/upgrade_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/upgrade_test.go @@ -0,0 +1 @@ +package commands diff --git a/commands/url.go b/commands/url.go new file mode 100644 index 0000000000..60f6377af5 --- /dev/null +++ b/commands/url.go @@ -0,0 +1,18 @@ +package commands + +import ( + "fmt" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" +) + +func cmdUrl(c *cli.Context) { + url, err := getHost(c).GetURL() + if err != nil { + log.Fatal(err) + } + + fmt.Println(url) +} diff --git a/commands/url_test.go b/commands/url_test.go new file mode 100644 index 0000000000..cdff10da75 --- /dev/null +++ b/commands/url_test.go @@ -0,0 +1 @@ +package commands diff --git a/main.go b/main.go index 0b9eb758d5..d9e1223dbd 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,8 @@ import ( log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" + + "github.com/docker/machine/commands" "github.com/docker/machine/utils" "github.com/docker/machine/version" ) @@ -26,7 +28,7 @@ func main() { os.Setenv("MACHINE_STORAGE_PATH", c.GlobalString("storage-path")) return nil } - app.Commands = Commands + app.Commands = commands.Commands app.CommandNotFound = cmdNotFound app.Usage = "Create and manage machines running Docker." app.Version = version.VERSION + " (" + version.GITCOMMIT + ")" @@ -70,3 +72,13 @@ func main() { app.Run(os.Args) } + +func cmdNotFound(c *cli.Context, command string) { + log.Fatalf( + "%s: '%s' is not a %s command. See '%s --help'.", + c.App.Name, + command, + c.App.Name, + c.App.Name, + ) +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000000..06ab7d0f9a --- /dev/null +++ b/main_test.go @@ -0,0 +1 @@ +package main