mirror of https://github.com/docker/docs.git
1188 lines
28 KiB
Go
1188 lines
28 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
"github.com/codegangsta/cli"
|
|
"github.com/skarademir/naturalsort"
|
|
|
|
"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/hyperv"
|
|
_ "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/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"
|
|
)
|
|
|
|
type machineConfig struct {
|
|
machineName string
|
|
machineDir string
|
|
machineUrl string
|
|
clientKeyPath string
|
|
serverCertPath string
|
|
clientCertPath string
|
|
caCertPath string
|
|
caKeyPath string
|
|
serverKeyPath string
|
|
AuthOptions auth.AuthOptions
|
|
SwarmOptions swarm.SwarmOptions
|
|
}
|
|
|
|
type hostListItem struct {
|
|
Name string
|
|
Active bool
|
|
DriverName string
|
|
State state.State
|
|
URL string
|
|
SwarmOptions swarm.SwarmOptions
|
|
}
|
|
|
|
func sortHostListItemsByName(items []hostListItem) {
|
|
m := make(map[string]hostListItem, len(items))
|
|
s := make([]string, len(items))
|
|
for i, v := range items {
|
|
name := strings.ToLower(v.Name)
|
|
m[name] = v
|
|
s[i] = name
|
|
}
|
|
sort.Sort(naturalsort.NaturalSort(s))
|
|
for i, v := range s {
|
|
items[i] = m[v]
|
|
}
|
|
}
|
|
|
|
func confirmInput(msg string) bool {
|
|
fmt.Printf("%s (y/n): ", msg)
|
|
var resp string
|
|
_, err := fmt.Scanln(&resp)
|
|
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if strings.Index(strings.ToLower(resp), "y") == 0 {
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func newMcn(store libmachine.Store) (*libmachine.Machine, error) {
|
|
return libmachine.New(store)
|
|
}
|
|
|
|
func getMachineDir(rootPath string) string {
|
|
return filepath.Join(rootPath, "machines")
|
|
}
|
|
|
|
func getDefaultStore(rootPath, caCertPath, privateKeyPath string) (libmachine.Store, error) {
|
|
return libmachine.NewFilestore(
|
|
rootPath,
|
|
caCertPath,
|
|
privateKeyPath,
|
|
), nil
|
|
}
|
|
|
|
func setupCertificates(caCertPath, caKeyPath, clientCertPath, clientKeyPath string) error {
|
|
org := utils.GetUsername()
|
|
bits := 2048
|
|
|
|
if _, err := os.Stat(utils.GetMachineCertDir()); err != nil {
|
|
if os.IsNotExist(err) {
|
|
if err := os.MkdirAll(utils.GetMachineCertDir(), 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.GetMachineCertDir()); err != nil {
|
|
if os.IsNotExist(err) {
|
|
if err := os.Mkdir(utils.GetMachineCertDir(), 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)
|
|
}
|
|
}
|
|
|
|
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",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "swarm",
|
|
Usage: "Configure Machine with Swarm",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "swarm-master",
|
|
Usage: "Configure Machine to be a Swarm master",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "swarm-discovery",
|
|
Usage: "Discovery service to use with Swarm",
|
|
Value: "",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "swarm-host",
|
|
Usage: "ip/socket to listen on for Swarm master",
|
|
Value: "tcp://0.0.0.0:3376",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "swarm-addr",
|
|
Usage: "addr to advertise for Swarm (default: detect and use the machine IP)",
|
|
Value: "",
|
|
},
|
|
),
|
|
Name: "create",
|
|
Usage: "Create a machine",
|
|
Action: cmdCreate,
|
|
},
|
|
{
|
|
Name: "config",
|
|
Usage: "Print the connection config for machine",
|
|
Description: "Argument is a machine name. Will use the active machine if none is provided.",
|
|
Action: cmdConfig,
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "swarm",
|
|
Usage: "Display the Swarm config instead of the Docker daemon",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "inspect",
|
|
Usage: "Inspect information about a machine",
|
|
Description: "Argument is a machine name. Will use the active machine if none is provided.",
|
|
Action: cmdInspect,
|
|
},
|
|
{
|
|
Name: "ip",
|
|
Usage: "Get the IP address of a machine",
|
|
Description: "Argument is a machine name. Will use the active machine if none is provided.",
|
|
Action: cmdIp,
|
|
},
|
|
{
|
|
Name: "kill",
|
|
Usage: "Kill a machine",
|
|
Description: "Argument(s) are one or more machine names. Will use the active machine if none is provided.",
|
|
Action: cmdKill,
|
|
},
|
|
{
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "quiet, q",
|
|
Usage: "Enable quiet mode",
|
|
},
|
|
},
|
|
Name: "ls",
|
|
Usage: "List machines",
|
|
Action: cmdLs,
|
|
},
|
|
{
|
|
Name: "regenerate-certs",
|
|
Usage: "Regenerate TLS Certificates for a machine",
|
|
Description: "Argument(s) are one or more machine names. Will use the active machine if none is provided.",
|
|
Action: cmdRegenerateCerts,
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "force, f",
|
|
Usage: "Force rebuild and do not prompt",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "restart",
|
|
Usage: "Restart a machine",
|
|
Description: "Argument(s) are one or more machine names. Will use the active machine if none is provided.",
|
|
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",
|
|
Description: "Argument(s) are one or more machine names.",
|
|
Action: cmdRm,
|
|
},
|
|
{
|
|
Name: "env",
|
|
Usage: "Display the commands to set up the environment for the Docker client",
|
|
Description: "Argument is a machine name. Will use the active machine if none is provided.",
|
|
Action: cmdEnv,
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "swarm",
|
|
Usage: "Display the Swarm config instead of the Docker daemon",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "unset, u",
|
|
Usage: "Unset variables instead of setting them",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "ssh",
|
|
Usage: "Log into or run a command on a machine with SSH",
|
|
Description: "Arguments are [machine-name] command - Will use the active machine if none is provided.",
|
|
Action: cmdSsh,
|
|
},
|
|
{
|
|
Name: "start",
|
|
Usage: "Start a machine",
|
|
Description: "Argument(s) are one or more machine names. Will use the active machine if none is provided.",
|
|
Action: cmdStart,
|
|
},
|
|
{
|
|
Name: "stop",
|
|
Usage: "Stop a machine",
|
|
Description: "Argument(s) are one or more machine names. Will use the active machine if none is provided.",
|
|
Action: cmdStop,
|
|
},
|
|
{
|
|
Name: "upgrade",
|
|
Usage: "Upgrade a machine to the latest version of Docker",
|
|
Description: "Argument(s) are one or more machine names. Will use the active machine if none is provided.",
|
|
Action: cmdUpgrade,
|
|
},
|
|
{
|
|
Name: "url",
|
|
Usage: "Get the URL of a machine",
|
|
Description: "Argument is a machine name. Will use the active machine if none is provided.",
|
|
Action: cmdUrl,
|
|
},
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
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 <machine name>.", 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) {
|
|
commands := map[string](func() error){
|
|
"configureAuth": host.ConfigureAuth,
|
|
"start": host.Start,
|
|
"stop": host.Stop,
|
|
"restart": host.Restart,
|
|
"kill": host.Kill,
|
|
"upgrade": host.Upgrade,
|
|
}
|
|
|
|
log.Debugf("command=%s machine=%s", actionName, host.Name)
|
|
|
|
if err := commands[actionName](); err != nil {
|
|
errorChan <- err
|
|
return
|
|
}
|
|
|
|
errorChan <- nil
|
|
}
|
|
|
|
// runActionForeachMachine will run the command across multiple machines
|
|
func runActionForeachMachine(actionName string, machines []*libmachine.Host) {
|
|
var (
|
|
numConcurrentActions = 0
|
|
serialMachines = []*libmachine.Host{}
|
|
errorChan = make(chan error)
|
|
)
|
|
|
|
for _, machine := range machines {
|
|
// Virtualbox is temperamental about doing things concurrently,
|
|
// so we schedule the actions in a "queue" to be executed serially
|
|
// after the concurrent actions are scheduled.
|
|
switch machine.DriverName {
|
|
case "virtualbox":
|
|
machine := machine
|
|
serialMachines = append(serialMachines, machine)
|
|
default:
|
|
numConcurrentActions++
|
|
go machineCommand(actionName, machine, errorChan)
|
|
}
|
|
}
|
|
|
|
// While the concurrent actions are running,
|
|
// do the serial actions. As the name implies,
|
|
// these run one at a time.
|
|
for _, machine := range serialMachines {
|
|
serialChan := make(chan error)
|
|
go machineCommand(actionName, machine, serialChan)
|
|
if err := <-serialChan; err != nil {
|
|
log.Errorln(err)
|
|
}
|
|
close(serialChan)
|
|
}
|
|
|
|
// TODO: We should probably only do 5-10 of these
|
|
// at a time, since otherwise cloud providers might
|
|
// rate limit us.
|
|
for i := 0; i < numConcurrentActions; i++ {
|
|
if err := <-errorChan; err != nil {
|
|
log.Errorln(err)
|
|
}
|
|
}
|
|
|
|
close(errorChan)
|
|
}
|
|
|
|
func runActionWithContext(actionName string, c *cli.Context) error {
|
|
machines, err := getHosts(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// No args specified, so use active.
|
|
if len(machines) == 0 {
|
|
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)
|
|
}
|
|
|
|
activeHost, err := mcn.GetActive()
|
|
if err != nil {
|
|
log.Fatalf("Unable to get active host: %v", err)
|
|
}
|
|
if activeHost == nil {
|
|
log.Fatalf("There is no active host. Please set it with %s active <machine name>.", c.App.Name)
|
|
}
|
|
machines = []*libmachine.Host{activeHost}
|
|
}
|
|
|
|
runActionForeachMachine(actionName, machines)
|
|
|
|
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() {
|
|
machine, err := loadMachine(n, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
machines = append(machines, machine)
|
|
}
|
|
|
|
return machines, nil
|
|
}
|
|
|
|
func loadMachine(name string, c *cli.Context) (*libmachine.Host, error) {
|
|
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)
|
|
}
|
|
|
|
host, err := mcn.Get(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return host, nil
|
|
}
|
|
|
|
func getHost(c *cli.Context) *libmachine.Host {
|
|
name := c.Args().First()
|
|
|
|
defaultStore, err := getDefaultStore(
|
|
c.GlobalString("storage-path"),
|
|
c.GlobalString("tls-ca-cert"),
|
|
c.GlobalString("tls-ca-key"),
|
|
)
|
|
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.Fatal("unable to get active host, active file not found")
|
|
}
|
|
return host
|
|
}
|
|
|
|
host, err := mcn.Get(name)
|
|
if err != nil {
|
|
log.Fatalf("unable to load host: %v", err)
|
|
}
|
|
return host
|
|
}
|
|
|
|
func getHostState(host libmachine.Host, store libmachine.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,
|
|
SwarmOptions: *host.HostOptions.SwarmOptions,
|
|
}
|
|
}
|
|
|
|
func getMachineConfig(c *cli.Context) (*machineConfig, error) {
|
|
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)
|
|
}
|
|
|
|
var machine *libmachine.Host
|
|
|
|
if name == "" {
|
|
m, err := mcn.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 := mcn.Get(name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error loading machine config: %s", err)
|
|
}
|
|
machine = m
|
|
}
|
|
|
|
machineDir := filepath.Join(utils.GetMachineDir(), machine.Name)
|
|
caCert := filepath.Join(machineDir, "ca.pem")
|
|
caKey := filepath.Join(utils.GetMachineCertDir(), "ca-key.pem")
|
|
clientCert := filepath.Join(machineDir, "cert.pem")
|
|
clientKey := filepath.Join(machineDir, "key.pem")
|
|
serverCert := filepath.Join(machineDir, "server.pem")
|
|
serverKey := filepath.Join(machineDir, "server-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{
|
|
machineName: name,
|
|
machineDir: machineDir,
|
|
machineUrl: machineUrl,
|
|
clientKeyPath: clientKey,
|
|
clientCertPath: clientCert,
|
|
serverCertPath: serverCert,
|
|
caKeyPath: caKey,
|
|
caCertPath: caCert,
|
|
serverKeyPath: serverKey,
|
|
AuthOptions: *machine.HostOptions.AuthOptions,
|
|
SwarmOptions: *machine.HostOptions.SwarmOptions,
|
|
}, nil
|
|
}
|
|
|
|
// getCertPaths 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 getCertPathInfo(c *cli.Context) libmachine.CertPathInfo {
|
|
// setup cert paths
|
|
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(utils.GetMachineCertDir(), "ca.pem")
|
|
}
|
|
|
|
if caKeyPath == "" {
|
|
caKeyPath = filepath.Join(utils.GetMachineCertDir(), "ca-key.pem")
|
|
}
|
|
|
|
if clientCertPath == "" {
|
|
clientCertPath = filepath.Join(utils.GetMachineCertDir(), "cert.pem")
|
|
}
|
|
|
|
if clientKeyPath == "" {
|
|
clientKeyPath = filepath.Join(utils.GetMachineCertDir(), "key.pem")
|
|
}
|
|
|
|
return libmachine.CertPathInfo{
|
|
CaCertPath: caCertPath,
|
|
CaKeyPath: caKeyPath,
|
|
ClientCertPath: clientCertPath,
|
|
ClientKeyPath: clientKeyPath,
|
|
}
|
|
}
|