diff --git a/Dockerfile b/Dockerfile index 6cff738012..f0a6d3a1df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN go get github.com/tools/godep RUN go get code.google.com/p/go.tools/cmd/cover ENV GOPATH /go/src/github.com/docker/machine/Godeps/_workspace:/go -ENV MACHINE_BINARY /go/src/github.com/docker/machine/docker-machine +ENV MACHINE_BINARY /go/src/github.com/docker/machine/docker-machine/docker-machine ENV USER root WORKDIR /go/src/github.com/docker/machine diff --git a/api/api.go b/api/api.go index 1edcb3f13a..1c413f3bf1 100644 --- a/api/api.go +++ b/api/api.go @@ -1,38 +1,37 @@ package api import ( - "io/ioutil" "os" "path/filepath" - "strings" + "github.com/docker/machine" "github.com/docker/machine/drivers" ) type Api struct { - store *Store + store *machine.Store } func NewApi(storePath, caCertPath, privateKeyPath string) (*Api, error) { - st := NewStore(storePath, caCertPath, privateKeyPath) + st := machine.NewStore(storePath, caCertPath, privateKeyPath) api := &Api{ store: st, } return api, nil } -func (m *Api) Create(name string, driverName string, flags drivers.DriverOptions) (*Machine, error) { +func (m *Api) Create(name string, driverName string, flags drivers.DriverOptions) (*machine.Machine, error) { exists, err := m.store.Exists(name) if err != nil { return nil, err } if exists { - return nil, ErrMachineExists + return nil, machine.ErrMachineExists } machinePath := filepath.Join(m.store.Path, name) - machine, err := NewMachine(name, driverName, machinePath, m.store.CaCertPath, m.store.PrivateKeyPath) + machine, err := machine.NewMachine(name, driverName, machinePath, m.store.CaCertPath, m.store.PrivateKeyPath) if err != nil { return machine, err } @@ -50,7 +49,7 @@ func (m *Api) Create(name string, driverName string, flags drivers.DriverOptions return nil, err } - if err := machine.SaveConfig(); err != nil { + if err := m.store.Save(machine); err != nil { return machine, err } @@ -85,48 +84,28 @@ func (m *Api) Remove(name string, force bool) error { return machine.Remove(force) } -func (m *Api) List() ([]Machine, []error) { - dir, err := ioutil.ReadDir(m.store.Path) - if err != nil && !os.IsNotExist(err) { - return nil, []error{err} - } - - machines := []Machine{} - errors := []error{} - - for _, file := range dir { - // don't load hidden dirs; used for configs - if file.IsDir() && strings.Index(file.Name(), ".") != 0 { - machine, err := m.Get(file.Name()) - if err != nil { - errors = append(errors, err) - continue - } - machines = append(machines, *machine) - } - } - return machines, nil +func (m *Api) List() ([]machine.Machine, []error) { + return m.store.List() } func (m *Api) Exists(name string) (bool, error) { return m.store.Exists(name) } -func (m *Api) Get(name string) (*Machine, error) { - machinePath := filepath.Join(m.store.Path, name) - return LoadMachine(name, machinePath) +func (m *Api) Get(name string) (*machine.Machine, error) { + return m.store.Get(name) } -func (m *Api) GetActive() (*Machine, error) { +func (m *Api) GetActive() (*machine.Machine, error) { return m.store.GetActive() } -func (m *Api) IsActive(machine *Machine) (bool, error) { - return m.store.IsActive(machine) +func (m *Api) IsActive(mcn *machine.Machine) (bool, error) { + return m.store.IsActive(mcn) } -func (m *Api) SetActive(machine *Machine) error { - return m.store.SetActive(machine) +func (m *Api) SetActive(mcn *machine.Machine) error { + return m.store.SetActive(mcn) } func (m *Api) RemoveActive() error { diff --git a/api/machine_test.go b/api/machine_test.go deleted file mode 100644 index 778f64ec17..0000000000 --- a/api/machine_test.go +++ /dev/null @@ -1 +0,0 @@ -package api diff --git a/api/store_test.go b/api/store_test.go deleted file mode 100644 index 1b8e68c9e5..0000000000 --- a/api/store_test.go +++ /dev/null @@ -1,201 +0,0 @@ -package api - -import ( - "testing" - - _ "github.com/docker/machine/drivers/none" -) - -func TestStoreList(t *testing.T) { - if err := clearHosts(); err != nil { - t.Fatal(err) - } - - flags := &DriverOptionsMock{ - Data: map[string]interface{}{ - "url": "unix:///var/run/docker.sock", - }, - } - - api, err := getTestApi() - if err != nil { - t.Fatal(err) - } - - if _, err := api.Create("test", "none", flags); err != nil { - t.Fatal(err) - } - - machines, errs := api.List() - if len(errs) != 0 { - t.Fatal(errs) - } - - if len(machines) != 1 { - t.Fatalf("list returned %d items", len(machines)) - } - - if machines[0].Name != "test" { - t.Fatalf("machines[0] name is incorrect, got: %s", machines[0].Name) - } - - if err := clearHosts(); err != nil { - t.Fatal(err) - } -} - -func TestStoreExists(t *testing.T) { - if err := clearHosts(); err != nil { - t.Fatal(err) - } - - flags := &DriverOptionsMock{ - Data: map[string]interface{}{ - "url": "unix:///var/run/docker.sock", - }, - } - - api, err := getTestApi() - if err != nil { - t.Fatal(err) - } - - if _, err := api.Create("test", "none", flags); err != nil { - t.Fatal(err) - } - - exists, err := api.Exists("test") - if err != nil { - t.Fatal(err) - } - - if !exists { - t.Fatal("exists returned false when it should have been true") - } - - if err := clearHosts(); err != nil { - t.Fatal(err) - } -} - -func TestStoreLoad(t *testing.T) { - if err := clearHosts(); err != nil { - t.Fatal(err) - } - - expectedURL := "unix:///foo/baz" - flags := &DriverOptionsMock{ - Data: map[string]interface{}{ - "url": expectedURL, - }, - } - - api, err := getTestApi() - if err != nil { - t.Fatal(err) - } - - machine, err := api.Create("test", "none", flags) - if err != nil { - t.Fatal(err) - } - - api, err = getTestApi() - if err != nil { - t.Fatal(err) - } - - machine, apiErr := api.Get("test") - if apiErr != nil { - t.Fatal(apiErr) - } - - if machine.Name != "test" { - t.Fatal("Host name is incorrect") - } - - actualURL, err := machine.GetURL() - if err != nil { - t.Fatal(err) - } - if actualURL != expectedURL { - t.Fatalf("GetURL is not %q, got %q", expectedURL, expectedURL) - } - - if err := clearHosts(); err != nil { - t.Fatal(err) - } -} - -func TestStoreGetSetActive(t *testing.T) { - if err := clearHosts(); err != nil { - t.Fatal(err) - } - - flags := &DriverOptionsMock{ - Data: map[string]interface{}{ - "url": "unix:///var/run/docker.sock", - }, - } - - api, err := getTestApi() - if err != nil { - t.Fatal(err) - } - - // No hosts set - machine, err := api.GetActive() - if err != nil { - t.Fatal(err) - } - - if machine != nil { - t.Fatalf("GetActive: active machine should not exist") - } - - // Set normal machine - originalMachine, err := api.Create("test", "none", flags) - if err != nil { - t.Fatal(err) - } - - if err := api.SetActive(originalMachine); err != nil { - t.Fatal(err) - } - - machine, err = api.GetActive() - if err != nil { - t.Fatal(err) - } - - if machine.Name != "test" { - t.Fatalf("active machine is not 'test', got %s", machine.Name) - } - - isActive, err := api.IsActive(machine) - if err != nil { - t.Fatal(err) - } - - if isActive != true { - t.Fatal("IsActive: active machine is not test") - } - - // remove active host altogether - if err := api.RemoveActive(); err != nil { - t.Fatal(err) - } - - machine, err = api.GetActive() - if err != nil { - t.Fatal(err) - } - - if machine != nil { - t.Fatalf("active machine %s is not nil", machine.Name) - } - - if err := clearHosts(); err != nil { - t.Fatal(err) - } -} diff --git a/commands.go b/commands.go deleted file mode 100644 index 56e8abf35c..0000000000 --- a/commands.go +++ /dev/null @@ -1,602 +0,0 @@ -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/api" - "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/state" - "github.com/docker/machine/utils" -) - -type machineConfig struct { - caCertPath string - clientCertPath string - clientKeyPath string - machineUrl string -} - -type machineListItem struct { - Name string - Active bool - DriverName string - State state.State - URL string -} - -type machineListItemByName []machineListItem - -func (m machineListItemByName) Len() int { - return len(m) -} - -func (m machineListItemByName) Swap(i, j int) { - m[i], m[j] = m[j], m[i] -} - -func (m machineListItemByName) Less(i, j int) bool { - return strings.ToLower(m[i].Name) < strings.ToLower(m[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() - mApi, err := api.NewApi(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) - if err != nil { - log.Fatal(err) - } - - if name == "" { - machine, err := mApi.GetActive() - if err != nil { - log.Fatalf("error getting active host: %v", err) - } - if machine != nil { - fmt.Println(machine.Name) - } - } else if name != "" { - machine, err := mApi.Get(name) - if err != nil { - log.Fatalf("error loading host: %v", err) - } - - if err := mApi.SetActive(machine); 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) - } - - mApi, err := api.NewApi(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) - if err != nil { - log.Fatal(err) - } - - machine, err := mApi.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 := mApi.SetActive(machine); 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("To point your Docker client at it, run this in your shell: $(%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(getMachine(c), "", " ") - if err != nil { - log.Fatal(err) - } - - fmt.Println(string(prettyJSON)) -} - -func cmdIp(c *cli.Context) { - ip, err := getMachine(c).Driver.GetIP() - if err != nil { - log.Fatal(err) - } - - fmt.Println(ip) -} - -func cmdKill(c *cli.Context) { - if err := getMachine(c).Driver.Kill(); err != nil { - log.Fatal(err) - } -} - -func cmdLs(c *cli.Context) { - quiet := c.Bool("quiet") - mApi, err := api.NewApi(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) - if err != nil { - log.Fatal(err) - } - - machineList, errs := mApi.List() - if errs != nil { - e := []string{} - for _, err := range errs { - e = append(e, err.Error()) - } - log.Fatal(e) - } - - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) - - if !quiet { - fmt.Fprintln(w, "NAME\tACTIVE\tDRIVER\tSTATE\tURL") - } - - items := []machineListItem{} - machineListItems := make(chan machineListItem) - - for _, machine := range machineList { - if !quiet { - tmpHost, err := mApi.GetActive() - if err != nil { - log.Errorf("There's a problem with the active machine: %s", err) - } - - if tmpHost == nil { - log.Errorf("There's a problem finding the active machine") - } - - go getMachineState(machine, *mApi, machineListItems) - } else { - fmt.Fprintf(w, "%s\n", machine.Name) - } - } - - if !quiet { - for i := 0; i < len(machineList); i++ { - items = append(items, <-machineListItems) - } - } - - close(machineListItems) - - sort.Sort(machineListItemByName(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 := getMachine(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 - - mApi, err := api.NewApi(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) - if err != nil { - log.Fatal(err) - } - - for _, machine := range c.Args() { - if err := mApi.Remove(machine, force); err != nil { - log.Errorf("Error removing machine %s: %s", machine, 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() - mApi, err := api.NewApi(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) - if err != nil { - log.Fatal(err) - } - - if name == "" { - machine, err := mApi.GetActive() - if err != nil { - log.Fatalf("unable to get active machine: %v", err) - } - - name = machine.Name - } - - machine, err := mApi.Get(name) - if err != nil { - log.Fatal(err) - } - - if len(c.Args()) <= 1 { - sshCmd, err = machine.Driver.GetSSHCommand() - } else { - sshCmd, err = machine.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 := getMachine(c).Start(); err != nil { - log.Fatal(err) - } -} - -func cmdStop(c *cli.Context) { - if err := getMachine(c).Stop(); err != nil { - log.Fatal(err) - } -} - -func cmdUpgrade(c *cli.Context) { - if err := getMachine(c).Upgrade(); err != nil { - log.Fatal(err) - } -} - -func cmdUrl(c *cli.Context) { - url, err := getMachine(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 getMachine(c *cli.Context) *api.Machine { - name := c.Args().First() - mApi, err := api.NewApi(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) - if err != nil { - log.Fatal(err) - } - - if name == "" { - machine, err := mApi.GetActive() - if err != nil { - log.Fatalf("unable to get active machine: %v", err) - } - - if machine == nil { - log.Fatal("unable to get active machine, active file not found") - } - return machine - } - - machine, err := mApi.Get(name) - if err != nil { - log.Fatalf("unable to load machine: %v", err) - } - return machine -} - -func getMachineState(machine api.Machine, mApi api.Api, machineListItems chan<- machineListItem) { - currentState, err := machine.Driver.GetState() - if err != nil { - log.Errorf("error getting state for machine %s: %s", machine.Name, err) - } - - url, err := machine.GetURL() - if err != nil { - if err == drivers.ErrHostIsNotRunning { - url = "" - } else { - log.Errorf("error getting URL for host %s: %s", machine.Name, err) - } - } - - isActive, err := mApi.IsActive(&machine) - if err != nil { - log.Debugf("error determining whether host %q is active: %s", - machine.Name, err) - } - - machineListItems <- machineListItem{ - Name: machine.Name, - Active: isActive, - DriverName: machine.Driver.DriverName(), - State: currentState, - URL: url, - } -} - -func getMachineConfig(c *cli.Context) (*machineConfig, error) { - name := c.Args().First() - - mApi, err := api.NewApi(c.GlobalString("storage-path"), c.GlobalString("tls-ca-cert"), c.GlobalString("tls-ca-key")) - if err != nil { - log.Fatal(err) - } - - var machine *api.Machine - - if name == "" { - m, err := mApi.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 := mApi.Get(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 -} diff --git a/commands_test.go b/commands_test.go deleted file mode 100644 index 0438847997..0000000000 --- a/commands_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - "io/ioutil" - "os/exec" - "testing" - - drivers "github.com/docker/machine/drivers" - "github.com/docker/machine/state" -) - -type FakeDriver struct { - MockState state.State -} - -func (d *FakeDriver) DriverName() string { - return "fakedriver" -} - -func (d *FakeDriver) SetConfigFromFlags(flags drivers.DriverOptions) error { - return nil -} - -func (d *FakeDriver) GetURL() (string, error) { - return "", nil -} - -func (d *FakeDriver) GetIP() (string, error) { - return "", nil -} - -func (d *FakeDriver) GetState() (state.State, error) { - return d.MockState, nil -} - -func (d *FakeDriver) PreCreateCheck() error { - return nil -} - -func (d *FakeDriver) Create() error { - return nil -} - -func (d *FakeDriver) Remove() error { - return nil -} - -func (d *FakeDriver) Start() error { - return nil -} - -func (d *FakeDriver) Stop() error { - return nil -} - -func (d *FakeDriver) Restart() error { - return nil -} - -func (d *FakeDriver) Kill() error { - return nil -} - -func (d *FakeDriver) Upgrade() error { - return nil -} - -func (d *FakeDriver) StartDocker() error { - return nil -} - -func (d *FakeDriver) StopDocker() error { - return nil -} - -func (d *FakeDriver) GetDockerConfigDir() string { - return "" -} - -func (d *FakeDriver) GetSSHCommand(args ...string) (*exec.Cmd, error) { - return &exec.Cmd{}, nil -} - -func TestGetHostState(t *testing.T) { - storePath, err := ioutil.TempDir("", ".docker") - if err != nil { - t.Fatal("Error creating tmp dir:", err) - } - hostListItems := make(chan hostListItem) - store := NewStore(storePath, "", "") - hosts := []Host{ - { - Name: "foo", - DriverName: "fakedriver", - Driver: &FakeDriver{ - MockState: state.Running, - }, - storePath: storePath, - }, - { - Name: "bar", - DriverName: "fakedriver", - Driver: &FakeDriver{ - MockState: state.Stopped, - }, - storePath: storePath, - }, - { - Name: "baz", - DriverName: "fakedriver", - Driver: &FakeDriver{ - MockState: state.Running, - }, - storePath: storePath, - }, - } - expected := map[string]state.State{ - "foo": state.Running, - "bar": state.Stopped, - "baz": state.Running, - } - items := []hostListItem{} - for _, host := range hosts { - go getHostState(host, *store, hostListItems) - } - for i := 0; i < len(hosts); i++ { - items = append(items, <-hostListItems) - } - for _, item := range items { - if expected[item.Name] != item.State { - t.Fatal("Expected state did not match for item", item) - } - } -} diff --git a/common_test.go b/common_test.go new file mode 100644 index 0000000000..fa343c3e70 --- /dev/null +++ b/common_test.go @@ -0,0 +1,54 @@ +package machine + +import ( + "os" + "path/filepath" +) + +var ( + TestStoreDir = ".store-test" + TestCaCertPath = "" + TestPrivateKeyPath = "" +) + +type DriverOptionsMock struct { + Data map[string]interface{} +} + +func (d DriverOptionsMock) String(key string) string { + return d.Data[key].(string) +} + +func (d DriverOptionsMock) Int(key string) int { + return d.Data[key].(int) +} + +func (d DriverOptionsMock) Bool(key string) bool { + return d.Data[key].(bool) +} + +func cleanup() error { + return os.RemoveAll(TestStoreDir) +} + +func getTestMachine() (*Machine, error) { + name := "test" + flags := &DriverOptionsMock{ + Data: map[string]interface{}{ + "url": "unix:///var/run/docker.sock", + }, + } + + machinePath := filepath.Join(TestStoreDir, name) + + m, err := NewMachine(name, "none", machinePath, "", "") + if err != nil { + return nil, err + } + + if err := m.Driver.SetConfigFromFlags(flags); err != nil { + return nil, err + } + + return m, nil +} diff --git a/api/errors.go b/errors.go similarity index 93% rename from api/errors.go rename to errors.go index c55e578078..327d82a8a7 100644 --- a/api/errors.go +++ b/errors.go @@ -1,4 +1,4 @@ -package api +package machine import ( "errors" diff --git a/imports_windows.go b/imports_windows.go deleted file mode 100644 index 883462072b..0000000000 --- a/imports_windows.go +++ /dev/null @@ -1,3 +0,0 @@ -package main - -import _ "github.com/docker/machine/drivers/hyperv" diff --git a/log.go b/log.go deleted file mode 100644 index cdbbd4408f..0000000000 --- a/log.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "os" - - log "github.com/Sirupsen/logrus" -) - -func initLogging(lvl log.Level) { - log.SetOutput(os.Stderr) - log.SetLevel(lvl) -} diff --git a/api/machine.go b/machine.go similarity index 85% rename from api/machine.go rename to machine.go index 09b99cf249..899c81674a 100644 --- a/api/machine.go +++ b/machine.go @@ -1,4 +1,4 @@ -package api +package machine import ( "encoding/json" @@ -18,8 +18,8 @@ import ( ) var ( - validHostNameChars = `[a-zA-Z0-9\-\.]` - validHostNamePattern = regexp.MustCompile(`^` + validHostNameChars + `+$`) + validHostnameChars = `[a-zA-Z0-9\-\.]` + validHostnamePattern = regexp.MustCompile(`^` + validHostnameChars + `+$`) ) type ( @@ -30,7 +30,7 @@ type ( CaCertPath string PrivateKeyPath string ClientCertPath string - storePath string + StorePath string } DockerConfig struct { @@ -55,25 +55,12 @@ func NewMachine(name, driverName, storePath, caCert, privateKey string) (*Machin Driver: driver, CaCertPath: caCert, PrivateKeyPath: privateKey, - storePath: storePath, + StorePath: storePath, }, nil } -func LoadMachine(name string, storePath string) (*Machine, error) { - if _, err := os.Stat(storePath); os.IsNotExist(err) { - return nil, ErrMachineDoesNotExist - } - - machine := &Machine{Name: name, storePath: storePath} - if err := machine.LoadConfig(); err != nil { - return nil, err - } - - return machine, nil -} - -func ValidateHostName(name string) (string, error) { - if !validHostNamePattern.MatchString(name) { +func ValidateHostname(name string) (string, error) { + if !validHostnamePattern.MatchString(name) { return name, ErrInvalidHostname } return name, nil @@ -112,8 +99,8 @@ func (m *Machine) ConfigureAuth() error { return err } - serverCertPath := filepath.Join(m.storePath, "server.pem") - serverKeyPath := filepath.Join(m.storePath, "server-key.pem") + serverCertPath := filepath.Join(m.StorePath, "server.pem") + serverKeyPath := filepath.Join(m.StorePath, "server-key.pem") org := m.Name bits := 2048 @@ -233,7 +220,7 @@ func (m *Machine) generateDockerConfig(dockerPort int, caCertPath string, server switch d.DriverName() { - case "virtualbox", "vmwarefusion", "vmwarevsphere", "hyper-v": + case "virtualbox", "vmwarefusion", "vmwarevsphere", "hyper-v", "none": daemonOpts = fmt.Sprintf("-H tcp://0.0.0.0:%d", dockerPort) daemonOptsCfg = path.Join(d.GetDockerConfigDir(), "profile") opts := fmt.Sprintf("%s %s", defaultDaemonOpts, daemonOpts) @@ -256,7 +243,7 @@ DOCKER_TLS=no`, opts, caCertPath, serverKeyPath, serverCertPath) } func (m *Machine) Create(name string) error { - name, err := ValidateHostName(name) + name, err := ValidateHostname(name) if err != nil { return err } @@ -294,14 +281,14 @@ func (m *Machine) Remove(force bool) error { } func (m *Machine) removeStorePath() error { - file, err := os.Stat(m.storePath) + file, err := os.Stat(m.StorePath) if err != nil { return err } if !file.IsDir() { - return fmt.Errorf("%q is not a directory", m.storePath) + return fmt.Errorf("%q is not a directory", m.StorePath) } - return os.RemoveAll(m.storePath) + return os.RemoveAll(m.StorePath) } func (m *Machine) GetURL() (string, error) { @@ -309,7 +296,7 @@ func (m *Machine) GetURL() (string, error) { } func (m *Machine) LoadConfig() error { - data, err := ioutil.ReadFile(filepath.Join(m.storePath, "config.json")) + data, err := ioutil.ReadFile(filepath.Join(m.StorePath, "config.json")) if err != nil { return err } @@ -320,7 +307,7 @@ func (m *Machine) LoadConfig() error { return err } - driver, err := drivers.NewDriver(config.DriverName, m.Name, m.storePath, m.CaCertPath, m.PrivateKeyPath) + driver, err := drivers.NewDriver(config.DriverName, m.Name, m.StorePath, m.CaCertPath, m.PrivateKeyPath) if err != nil { return err } @@ -339,7 +326,7 @@ func (m *Machine) SaveConfig() error { if err != nil { return err } - if err := ioutil.WriteFile(filepath.Join(m.storePath, "config.json"), data, 0600); err != nil { + if err := ioutil.WriteFile(filepath.Join(m.StorePath, "config.json"), data, 0600); err != nil { return err } return nil diff --git a/machine_test.go b/machine_test.go new file mode 100644 index 0000000000..892144cd60 --- /dev/null +++ b/machine_test.go @@ -0,0 +1,103 @@ +package machine + +import ( + "fmt" + "path/filepath" + "strings" + "testing" +) + +func TestMachineLoadMachine(t *testing.T) { + if err := cleanup(); err != nil { + t.Fatal(err) + } + + st := NewStore(TestStoreDir, "", "") + + m, err := getTestMachine() + if err != nil { + t.Fatal(err) + } + + if err := st.Save(m); err != nil { + t.Fatal(err) + } + + machinePath := filepath.Join(st.Path, "test") + + mcn, err := loadMachine(m.Name, machinePath) + if err != nil { + t.Fatal(err) + } + + if mcn.Name != m.Name { + t.Fatalf("expected machine name %s; received %s", m.Name, mcn.Name) + } + + if err := cleanup(); err != nil { + t.Fatal(err) + } +} + +func TestMachineValidHostname(t *testing.T) { + hostname := "test-host" + name, err := ValidateHostname(hostname) + if err != nil { + t.Fatal(err) + } + + if name != hostname { + t.Fatal("expected hostname %s; received %s", hostname, name) + } +} + +func TestMachineValidHostnameInvalid(t *testing.T) { + hostname := "test,%" + if _, err := ValidateHostname(hostname); err == nil { + t.Fatal("expected failure") + } +} + +func TestMachineGenerateDockerConfig(t *testing.T) { + if err := cleanup(); err != nil { + t.Fatal(err) + } + + st := NewStore(TestStoreDir, "", "") + + m, err := getTestMachine() + if err != nil { + t.Fatal(err) + } + + if err := st.Save(m); err != nil { + t.Fatal(err) + } + + port := 1234 + caCertPath := "/tmp/ca" + serverKeyPath := "/tmp/server-key" + serverCertPath := "/tmp/server-cert" + + config := m.generateDockerConfig(port, caCertPath, serverKeyPath, serverCertPath) + + if !strings.Contains(config.EngineConfig, caCertPath) { + t.Fatal("failed to find caCertPath") + } + + if !strings.Contains(config.EngineConfig, serverKeyPath) { + t.Fatal("failed to find serverKeyPath") + } + + if !strings.Contains(config.EngineConfig, serverCertPath) { + t.Fatal("failed to find serverCertPath") + } + + if !strings.Contains(config.EngineConfig, fmt.Sprintf("-H tcp://0.0.0.0:%d", port)) { + t.Fatal("failed to find serverCertPath") + } + + if err := cleanup(); err != nil { + t.Fatal(err) + } +} diff --git a/main.go b/main.go deleted file mode 100644 index 3d26007e58..0000000000 --- a/main.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "os" - "path" - "path/filepath" - - log "github.com/Sirupsen/logrus" - "github.com/codegangsta/cli" - "github.com/docker/machine/utils" -) - -func main() { - for _, f := range os.Args { - if f == "-D" || f == "--debug" || f == "-debug" { - os.Setenv("DEBUG", "1") - initLogging(log.DebugLevel) - } - } - - app := cli.NewApp() - app.Name = path.Base(os.Args[0]) - app.Author = "Docker Machine Contributors" - app.Email = "https://github.com/docker/machine" - app.Commands = Commands - app.CommandNotFound = cmdNotFound - app.Usage = "Create and manage machines running Docker." - app.Version = VERSION - - app.Flags = []cli.Flag{ - cli.BoolFlag{ - Name: "debug, D", - Usage: "Enable debug mode", - }, - cli.StringFlag{ - EnvVar: "MACHINE_STORAGE_PATH", - Name: "storage-path", - Usage: "Configures storage path", - }, - cli.StringFlag{ - EnvVar: "MACHINE_TLS_CA_CERT", - Name: "tls-ca-cert", - Usage: "CA to verify remotes against", - Value: filepath.Join(utils.GetMachineDir(), "ca.pem"), - }, - cli.StringFlag{ - EnvVar: "MACHINE_TLS_CA_KEY", - Name: "tls-ca-key", - Usage: "Private key to generate certificates", - Value: filepath.Join(utils.GetMachineDir(), "key.pem"), - }, - cli.StringFlag{ - EnvVar: "MACHINE_TLS_CLIENT_CERT", - Name: "tls-client-cert", - Usage: "Client cert to use for TLS", - Value: filepath.Join(utils.GetMachineClientCertDir(), "cert.pem"), - }, - cli.StringFlag{ - EnvVar: "MACHINE_TLS_CLIENT_KEY", - Name: "tls-client-key", - Usage: "Private key used in client TLS auth", - Value: filepath.Join(utils.GetMachineClientCertDir(), "key.pem"), - }, - } - - app.Run(os.Args) -} diff --git a/script/build b/script/build index 3718840bc6..d33703b16d 100755 --- a/script/build +++ b/script/build @@ -13,6 +13,6 @@ else OS_ARCH_ARG=($2) fi -rm -f docker-machine* +rm -f docker-machine/docker-machine* docker build -t docker-machine . -exec docker run --rm -v `pwd`:/go/src/github.com/docker/machine docker-machine gox "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" -output="docker-machine_{{.OS}}-{{.Arch}}" +exec docker run --rm -v `pwd`:/go/src/github.com/docker/machine -w /go/src/github.com/docker/machine/docker-machine docker-machine gox "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" -output="../docker-machine_{{.OS}}-{{.Arch}}" diff --git a/script/release b/script/release index c7d53d488d..797d2f3f20 100755 --- a/script/release +++ b/script/release @@ -20,7 +20,7 @@ docker run --rm -e GITHUB_TOKEN docker-machine github-release release \ --name $VERSION \ --description "" \ --pre-release -for BINARY in docker-machine_*; do +for BINARY in docker-machine/docker-machine_*; do docker run --rm -e GITHUB_TOKEN -v `pwd`:/go/src/github.com/docker/machine \ docker-machine github-release upload \ --user docker \ diff --git a/api/store.go b/store.go similarity index 57% rename from api/store.go rename to store.go index 9cf3ded2ac..93fc9c1aec 100644 --- a/api/store.go +++ b/store.go @@ -1,9 +1,11 @@ -package api +package machine import ( + "encoding/json" "io/ioutil" "os" "path/filepath" + "strings" "github.com/docker/machine/utils" ) @@ -23,6 +25,35 @@ func NewStore(rootPath string, caCert string, privateKey string) *Store { return &Store{Path: rootPath, CaCertPath: caCert, PrivateKeyPath: privateKey} } +func loadMachine(name string, storePath string) (*Machine, error) { + if _, err := os.Stat(storePath); os.IsNotExist(err) { + return nil, ErrMachineDoesNotExist + } + + machine := &Machine{Name: name, StorePath: storePath} + if err := machine.LoadConfig(); err != nil { + return nil, err + } + + return machine, nil +} + +func (s *Store) Save(m *Machine) error { + data, err := json.Marshal(m) + if err != nil { + return err + } + + if err := os.MkdirAll(m.StorePath, 0700); err != nil { + return err + } + + if err := ioutil.WriteFile(filepath.Join(m.StorePath, "config.json"), data, 0600); err != nil { + return err + } + return nil +} + func (s *Store) Exists(name string) (bool, error) { _, err := os.Stat(filepath.Join(s.Path, name)) if os.IsNotExist(err) { @@ -35,7 +66,30 @@ func (s *Store) Exists(name string) (bool, error) { func (s *Store) Get(name string) (*Machine, error) { machinePath := filepath.Join(s.Path, name) - return LoadMachine(name, machinePath) + return loadMachine(name, machinePath) +} + +func (s *Store) List() ([]Machine, []error) { + dir, err := ioutil.ReadDir(s.Path) + if err != nil && !os.IsNotExist(err) { + return nil, []error{err} + } + + machines := []Machine{} + errors := []error{} + + for _, file := range dir { + // don't load hidden dirs; used for configs + if file.IsDir() && strings.Index(file.Name(), ".") != 0 { + machine, err := s.Get(file.Name()) + if err != nil { + errors = append(errors, err) + continue + } + machines = append(machines, *machine) + } + } + return machines, nil } func (s *Store) GetActive() (*Machine, error) { diff --git a/store_test.go b/store_test.go new file mode 100644 index 0000000000..2f26f814d3 --- /dev/null +++ b/store_test.go @@ -0,0 +1,189 @@ +package machine + +import ( + "testing" + + _ "github.com/docker/machine/drivers/none" +) + +func TestStoreList(t *testing.T) { + if err := cleanup(); err != nil { + t.Fatal(err) + } + + st := NewStore(TestStoreDir, "", "") + + m, err := getTestMachine() + if err != nil { + t.Fatal(err) + } + + if err := st.Save(m); err != nil { + t.Fatal(err) + } + + machines, errs := st.List() + if len(errs) != 0 { + t.Fatal(errs) + } + + if len(machines) != 1 { + t.Fatalf("list returned %d items", len(machines)) + } + + if machines[0].Name != "test" { + t.Fatalf("machines[0] name is incorrect, got: %s", machines[0].Name) + } + + if err := cleanup(); err != nil { + t.Fatal(err) + } +} + +func TestStoreExists(t *testing.T) { + if err := cleanup(); err != nil { + t.Fatal(err) + } + + st := NewStore(TestStoreDir, "", "") + + m, err := getTestMachine() + if err != nil { + t.Fatal(err) + } + + if err := st.Save(m); err != nil { + t.Fatal(err) + } + + exists, err := st.Exists("test") + if err != nil { + t.Fatal(err) + } + + if !exists { + t.Fatal("exists returned false when it should have been true") + } + + if err := cleanup(); err != nil { + t.Fatal(err) + } +} + +func TestStoreLoad(t *testing.T) { + if err := cleanup(); err != nil { + t.Fatal(err) + } + + st := NewStore(TestStoreDir, "", "") + + m, err := getTestMachine() + if err != nil { + t.Fatal(err) + } + + if err := st.Save(m); err != nil { + t.Fatal(err) + } + + exists, err := st.Exists("test") + if err != nil { + t.Fatal(err) + } + + if !exists { + t.Fatal("exists returned false when it should have been true") + } + + expectedUrl := "unix:///var/run/docker.sock" + + machine, err := st.Get("test") + if err != nil { + t.Fatal(err) + } + + if machine.Name != "test" { + t.Fatal("Host name is incorrect") + } + + actualUrl, err := machine.GetURL() + if err != nil { + t.Fatal(err) + } + + if actualUrl != expectedUrl { + t.Fatalf("expected url %q, received %q", expectedUrl, actualUrl) + } + + if err := cleanup(); err != nil { + t.Fatal(err) + } +} + +func TestStoreGetSetActive(t *testing.T) { + if err := cleanup(); err != nil { + t.Fatal(err) + } + + st := NewStore(TestStoreDir, "", "") + + // No machine set + machine, err := st.GetActive() + if err != nil { + t.Fatal(err) + } + + if machine != nil { + t.Fatalf("GetActive: active machine should not exist") + } + + // Set normal machine + originalMachine, err := getTestMachine() + if err != nil { + t.Fatal(err) + } + + if err := st.Save(originalMachine); err != nil { + t.Fatal(err) + } + + if err := st.SetActive(originalMachine); err != nil { + t.Fatal(err) + } + + machine, err = st.GetActive() + if err != nil { + t.Fatal(err) + } + + if machine.Name != "test" { + t.Fatalf("active machine is not 'test', got %s", machine.Name) + } + + isActive, err := st.IsActive(machine) + if err != nil { + t.Fatal(err) + } + + if isActive != true { + t.Fatal("IsActive: active machine is not test") + } + + // remove active machine + if err := st.RemoveActive(); err != nil { + t.Fatal(err) + } + + machine, err = st.GetActive() + if err != nil { + t.Fatal(err) + } + + if machine != nil { + t.Fatalf("active machine %s is not nil", machine.Name) + } + + if err := cleanup(); err != nil { + t.Fatal(err) + } +} diff --git a/version.go b/version.go index a44ae0ec70..27aae43d8a 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ -package main +package machine const VERSION = "0.1.0"