package main import ( "encoding/json" "fmt" "os" "os/exec" "path/filepath" "sort" "strings" "text/tabwriter" log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" "github.com/docker/machine/drivers" _ "github.com/docker/machine/drivers/amazonec2" _ "github.com/docker/machine/drivers/azure" _ "github.com/docker/machine/drivers/digitalocean" _ "github.com/docker/machine/drivers/google" _ "github.com/docker/machine/drivers/none" _ "github.com/docker/machine/drivers/openstack" _ "github.com/docker/machine/drivers/rackspace" _ "github.com/docker/machine/drivers/softlayer" _ "github.com/docker/machine/drivers/virtualbox" _ "github.com/docker/machine/drivers/vmwarefusion" _ "github.com/docker/machine/drivers/vmwarevcloudair" _ "github.com/docker/machine/drivers/vmwarevsphere" "github.com/docker/machine/state" "github.com/docker/machine/utils" ) type machineConfig struct { caCertPath string clientCertPath string clientKeyPath string machineUrl string } type hostListItem struct { Name string Active bool DriverName string State state.State URL string } type hostListItemByName []hostListItem func (h hostListItemByName) Len() int { return len(h) } func (h hostListItemByName) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h hostListItemByName) Less(i, j int) bool { return strings.ToLower(h[i].Name) < strings.ToLower(h[j].Name) } func setupCertificates(caCertPath, caKeyPath, clientCertPath, clientKeyPath string) error { org := utils.GetUsername() bits := 2048 if _, err := os.Stat(utils.GetMachineDir()); err != nil { if os.IsNotExist(err) { if err := os.MkdirAll(utils.GetMachineDir(), 0700); err != nil { log.Fatalf("Error creating machine config dir: %s", err) } } else { log.Fatal(err) } } if _, err := os.Stat(caCertPath); os.IsNotExist(err) { log.Infof("Creating CA: %s", caCertPath) // check if the key path exists; if so, error if _, err := os.Stat(caKeyPath); err == nil { log.Fatalf("The CA key already exists. Please remove it or specify a different key/cert.") } if err := utils.GenerateCACertificate(caCertPath, caKeyPath, org, bits); err != nil { log.Infof("Error generating CA certificate: %s", err) } } if _, err := os.Stat(clientCertPath); os.IsNotExist(err) { log.Infof("Creating client certificate: %s", clientCertPath) if _, err := os.Stat(utils.GetMachineClientCertDir()); err != nil { if os.IsNotExist(err) { if err := os.Mkdir(utils.GetMachineClientCertDir(), 0700); err != nil { log.Fatalf("Error creating machine client cert dir: %s", err) } } else { log.Fatal(err) } } // check if the key path exists; if so, error if _, err := os.Stat(clientKeyPath); err == nil { log.Fatalf("The client key already exists. Please remove it or specify a different key/cert.") } if err := utils.GenerateCert([]string{""}, clientCertPath, clientKeyPath, caCertPath, caKeyPath, org, bits); err != nil { log.Fatalf("Error generating client certificate: %s", err) } // copy ca.pem to client cert dir for docker client if err := utils.CopyFile(caCertPath, filepath.Join(utils.GetMachineClientCertDir(), "ca.pem")); err != nil { log.Fatalf("Error copying ca.pem to client cert dir: %s", err) } } return nil } var Commands = []cli.Command{ { Name: "active", Usage: "Get or set the active machine", Action: cmdActive, }, { Flags: append( drivers.GetCreateFlags(), cli.StringFlag{ Name: "driver, d", Usage: fmt.Sprintf( "Driver to create machine with. Available drivers: %s", strings.Join(drivers.GetDriverNames(), ", "), ), Value: "none", }, ), Name: "create", Usage: "Create a machine", Action: cmdCreate, }, { Name: "config", Usage: "Print the connection config for machine", Action: cmdConfig, }, { Name: "inspect", Usage: "Inspect information about a machine", Action: cmdInspect, }, { Name: "ip", Usage: "Get the IP address of a machine", Action: cmdIp, }, { Name: "kill", Usage: "Kill a machine", Action: cmdKill, }, { Flags: []cli.Flag{ cli.BoolFlag{ Name: "quiet, q", Usage: "Enable quiet mode", }, }, Name: "ls", Usage: "List machines", Action: cmdLs, }, { Name: "restart", Usage: "Restart a machine", Action: cmdRestart, }, { Flags: []cli.Flag{ cli.BoolFlag{ Name: "force, f", Usage: "Remove local configuration even if machine cannot be removed", }, }, Name: "rm", Usage: "Remove a machine", Action: cmdRm, }, { Name: "env", Usage: "Display the commands to set up the environment for the Docker client", Action: cmdEnv, }, { Name: "ssh", Usage: "Log into or run a command on a machine with SSH", Action: cmdSsh, }, { Name: "start", Usage: "Start a machine", Action: cmdStart, }, { Name: "stop", Usage: "Stop a machine", Action: cmdStop, }, { Name: "upgrade", Usage: "Upgrade a machine to the latest version of Docker", Action: cmdUpgrade, }, { Name: "url", Usage: "Get the URL of a machine", Action: cmdUrl, }, } func cmdActive(c *cli.Context) { name := c.Args().First() store := NewStore(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) if name == "" { host, err := store.GetActive() if err != nil { log.Fatalf("error getting active host: %v", err) } if host != nil { fmt.Println(host.Name) } } else if name != "" { host, err := store.Load(name) if err != nil { log.Fatalf("error loading host: %v", err) } if err := store.SetActive(host); err != nil { log.Fatalf("error setting active host: %v", err) } } else { cli.ShowCommandHelp(c, "active") } } func cmdCreate(c *cli.Context) { driver := c.String("driver") name := c.Args().First() if name == "" { cli.ShowCommandHelp(c, "create") log.Fatal("You must specify a machine name") } if err := setupCertificates(c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key"), c.GlobalString("tls-client-cert"), c.GlobalString("tls-client-key")); err != nil { log.Fatalf("Error generating certificates: %s", err) } store := NewStore(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) host, err := store.Create(name, driver, c) if err != nil { log.Errorf("Error creating machine: %s", err) log.Warn("You will want to check the provider to make sure the machine and associated resources were properly removed.") log.Fatal("Error creating machine") } if err := store.SetActive(host); err != nil { log.Fatalf("error setting active host: %v", err) } log.Infof("%q has been created and is now the active machine", name) log.Infof("Configure docker client with: $(%s env %s)", c.App.Name, name) } func cmdConfig(c *cli.Context) { cfg, err := getMachineConfig(c) if err != nil { log.Fatal(err) } fmt.Printf("--tls --tlscacert=%s --tlscert=%s --tlskey=%s -H=%q", cfg.caCertPath, cfg.clientCertPath, cfg.clientKeyPath, cfg.machineUrl) } 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 cmdKill(c *cli.Context) { if err := getHost(c).Driver.Kill(); err != nil { log.Fatal(err) } } func cmdLs(c *cli.Context) { quiet := c.Bool("quiet") store := NewStore(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) hostList, err := store.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") } items := []hostListItem{} hostListItems := make(chan hostListItem) for _, host := range hostList { if !quiet { tmpHost, err := store.GetActive() if err != nil { log.Errorf("There's a problem with the active host: %s", err) } if tmpHost == nil { log.Errorf("There's a problem finding the active host") } go getHostState(host, *store, hostListItems) } else { fmt.Fprintf(w, "%s\n", host.Name) } } if !quiet { for i := 0; i < len(hostList); i++ { items = append(items, <-hostListItems) } } close(hostListItems) sort.Sort(hostListItemByName(items)) for _, item := range items { activeString := "" if item.Active { activeString = "*" } fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", item.Name, activeString, item.DriverName, item.State, item.URL) } w.Flush() } func cmdRestart(c *cli.Context) { if err := getHost(c).Driver.Restart(); 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 store := NewStore(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) for _, host := range c.Args() { if err := store.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.") } } func cmdEnv(c *cli.Context) { cfg, err := getMachineConfig(c) if err != nil { log.Fatal(err) } fmt.Printf("export DOCKER_TLS_VERIFY=yes\nexport DOCKER_CERT_PATH=%s\nexport DOCKER_HOST=%s\n", utils.GetMachineClientCertDir(), cfg.machineUrl) } func cmdSsh(c *cli.Context) { var ( err error sshCmd *exec.Cmd ) name := c.Args().First() store := NewStore(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) if name == "" { host, err := store.GetActive() if err != nil { log.Fatalf("unable to get active host: %v", err) } name = host.Name } host, err := store.Load(name) if err != nil { log.Fatal(err) } if len(c.Args()) <= 1 { sshCmd, err = host.Driver.GetSSHCommand() } else { sshCmd, err = host.Driver.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) } } func cmdStart(c *cli.Context) { if err := getHost(c).Start(); err != nil { log.Fatal(err) } } func cmdStop(c *cli.Context) { if err := getHost(c).Stop(); err != nil { log.Fatal(err) } } func cmdUpgrade(c *cli.Context) { if err := getHost(c).Upgrade(); 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 getHost(c *cli.Context) *Host { name := c.Args().First() store := NewStore(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) if name == "" { host, err := store.GetActive() if err != nil { log.Fatalf("unable to get active host: %v", err) } if host == nil { log.Fatal("unable to get active host, active file not found") } return host } host, err := store.Load(name) if err != nil { log.Fatalf("unable to load host: %v", err) } return host } func getHostState(host Host, store Store, hostListItems chan<- hostListItem) { currentState, err := host.Driver.GetState() if err != nil { log.Errorf("error getting state for host %s: %s", host.Name, err) } url, err := host.GetURL() if err != nil { if err == drivers.ErrHostIsNotRunning { url = "" } else { log.Errorf("error getting URL for host %s: %s", host.Name, err) } } isActive, err := store.IsActive(&host) if err != nil { log.Debugf("error determining whether host %q is active: %s", host.Name, err) } hostListItems <- hostListItem{ Name: host.Name, Active: isActive, DriverName: host.Driver.DriverName(), State: currentState, URL: url, } } func getMachineConfig(c *cli.Context) (*machineConfig, error) { name := c.Args().First() store := NewStore(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) var machine *Host if name == "" { m, err := store.GetActive() if err != nil { log.Fatalf("error getting active host: %v", err) } if m == nil { return nil, fmt.Errorf("There is no active host") } machine = m } else { m, err := store.Load(name) if err != nil { return nil, fmt.Errorf("Error loading machine config: %s", err) } machine = m } caCert := filepath.Join(utils.GetMachineClientCertDir(), "ca.pem") clientCert := filepath.Join(utils.GetMachineClientCertDir(), "cert.pem") clientKey := filepath.Join(utils.GetMachineClientCertDir(), "key.pem") machineUrl, err := machine.GetURL() if err != nil { if err == drivers.ErrHostIsNotRunning { machineUrl = "" } else { return nil, fmt.Errorf("Unexpected error getting machine url: %s", err) } } return &machineConfig{ caCertPath: caCert, clientCertPath: clientCert, clientKeyPath: clientKey, machineUrl: machineUrl, }, nil }