From 5000139c8e5ffdaccec1a16d254871cfa89d899a Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Thu, 14 Jan 2016 19:03:46 -0800 Subject: [PATCH] Add ability to imply 'default' VM in commands Signed-off-by: Nathan LeClaire --- commands/commands.go | 47 ++++++++++++- commands/config.go | 7 +- commands/env.go | 12 ++-- commands/env_test.go | 41 ++++++++++- commands/inspect.go | 9 ++- commands/ip_test.go | 65 +++++++++++++----- commands/kill_test.go | 2 +- commands/rm_test.go | 2 +- commands/ssh.go | 8 +-- commands/ssh_test.go | 4 +- commands/status.go | 9 ++- commands/stop_test.go | 109 +++++++++++++++++++++--------- commands/url.go | 9 ++- commands/url_test.go | 2 +- docs/get-started.md | 62 ++++++++++++++--- test/integration/cli/inspect.bats | 2 +- test/integration/cli/status.bats | 2 +- test/integration/cli/url.bats | 2 +- 18 files changed, 308 insertions(+), 86 deletions(-) diff --git a/commands/commands.go b/commands/commands.go index 5431983042..ad711117fb 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -18,7 +18,13 @@ import ( "github.com/docker/machine/libmachine/ssh" ) +const ( + defaultMachineName = "default" +) + var ( + ErrHostLoad = errors.New("All specified hosts had errors loading their configuration.") + ErrNoDefault = fmt.Errorf("Error: No machine name(s) specified and no %q machine exists.", defaultMachineName) ErrNoMachineSpecified = errors.New("Error: Expected to get one or more machine names as arguments") ErrExpectedOneMachine = errors.New("Error: Expected one machine name as an argument") ErrTooManyArguments = errors.New("Error: Too many arguments given") @@ -67,8 +73,45 @@ func (c *contextCommandLine) Application() *cli.App { return c.App } +// targetHost returns a specific host name if one is indicated by the first CLI +// arg, or the default host name if no host is specified. +func targetHost(c CommandLine, api libmachine.API) (string, error) { + if len(c.Args()) == 0 { + defaultExists, err := api.Exists(defaultMachineName) + if err != nil { + return "", fmt.Errorf("Error checking if host %q exists: %s", defaultMachineName, err) + } + + if defaultExists { + return defaultMachineName, nil + } + + return "", ErrNoDefault + } + + return c.Args()[0], nil +} + func runAction(actionName string, c CommandLine, api libmachine.API) error { - hosts, hostsInError := persist.LoadHosts(api, c.Args()) + var ( + hostsToLoad []string + ) + + // If user did not specify a machine name explicitly, use the 'default' + // machine if it exists. This allows short form commands such as + // 'docker-machine stop' for convenience. + if len(c.Args()) == 0 { + target, err := targetHost(c, api) + if err != nil { + return err + } + + hostsToLoad = []string{target} + } else { + hostsToLoad = c.Args() + } + + hosts, hostsInError := persist.LoadHosts(api, hostsToLoad) if len(hostsInError) > 0 { errs := []error{} @@ -79,7 +122,7 @@ func runAction(actionName string, c CommandLine, api libmachine.API) error { } if len(hosts) == 0 { - return ErrNoMachineSpecified + return ErrHostLoad } if errs := runActionForeachMachine(actionName, hosts); len(errs) > 0 { diff --git a/commands/config.go b/commands/config.go index 0b01f418b0..010b5eeba9 100644 --- a/commands/config.go +++ b/commands/config.go @@ -14,11 +14,12 @@ func cmdConfig(c CommandLine, api libmachine.API) error { // being run (it is intended to be run in a subshell) log.SetOutWriter(os.Stderr) - if len(c.Args()) != 1 { - return ErrExpectedOneMachine + target, err := targetHost(c, api) + if err != nil { + return err } - host, err := api.Load(c.Args().First()) + host, err := api.Load(target) if err != nil { return err } diff --git a/commands/env.go b/commands/env.go index 9de336f2c3..9ee6383be2 100644 --- a/commands/env.go +++ b/commands/env.go @@ -20,7 +20,6 @@ const ( ) var ( - errImproperEnvArgs = errors.New("Error: Expected one machine name") errImproperUnsetEnvArgs = errors.New("Error: Expected no machine name when the -u flag is present") defaultUsageHinter UsageHintGenerator ) @@ -68,11 +67,16 @@ func cmdEnv(c CommandLine, api libmachine.API) error { } func shellCfgSet(c CommandLine, api libmachine.API) (*ShellConfig, error) { - if len(c.Args()) != 1 { - return nil, errImproperEnvArgs + if len(c.Args()) > 1 { + return nil, ErrExpectedOneMachine } - host, err := api.Load(c.Args().First()) + target, err := targetHost(c, api) + if err != nil { + return nil, err + } + + host, err := api.Load(target) if err != nil { return nil, err } diff --git a/commands/env_test.go b/commands/env_test.go index 756df6ae4e..228355a64e 100644 --- a/commands/env_test.go +++ b/commands/env_test.go @@ -111,11 +111,14 @@ func TestShellCfgSet(t *testing.T) { }{ { description: "no host name specified", + api: &libmachinetest.FakeAPI{ + Hosts: []*host.Host{}, + }, commandLine: &commandstest.FakeCommandLine{ CliArgs: nil, }, expectedShellCfg: nil, - expectedErr: errImproperEnvArgs, + expectedErr: ErrNoDefault, }, { description: "bash shell set happy path without any flags set", @@ -153,6 +156,42 @@ func TestShellCfgSet(t *testing.T) { }, expectedErr: nil, }, + { + description: "bash shell set happy path with 'default' vm", + commandLine: &commandstest.FakeCommandLine{ + CliArgs: []string{}, + LocalFlags: &commandstest.FakeFlagger{ + Data: map[string]interface{}{ + "shell": "bash", + "swarm": false, + "no-proxy": false, + }, + }, + }, + api: &libmachinetest.FakeAPI{ + Hosts: []*host.Host{ + { + Name: defaultMachineName, + }, + }, + }, + connChecker: &FakeConnChecker{ + DockerHost: "tcp://1.2.3.4:2376", + AuthOptions: nil, + Err: nil, + }, + expectedShellCfg: &ShellConfig{ + Prefix: "export ", + Delimiter: "=\"", + Suffix: "\"\n", + DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), defaultMachineName), + DockerHost: "tcp://1.2.3.4:2376", + DockerTLSVerify: "1", + UsageHint: usageHint, + MachineName: defaultMachineName, + }, + expectedErr: nil, + }, { description: "fish shell set happy path", commandLine: &commandstest.FakeCommandLine{ diff --git a/commands/inspect.go b/commands/inspect.go index 9799839db7..03fe6e9ec8 100644 --- a/commands/inspect.go +++ b/commands/inspect.go @@ -21,12 +21,17 @@ var funcMap = template.FuncMap{ } func cmdInspect(c CommandLine, api libmachine.API) error { - if len(c.Args()) == 0 { + if len(c.Args()) > 1 { c.ShowHelp() return ErrExpectedOneMachine } - host, err := api.Load(c.Args().First()) + target, err := targetHost(c, api) + if err != nil { + return err + } + + host, err := api.Load(target) if err != nil { return err } diff --git a/commands/ip_test.go b/commands/ip_test.go index 35a2c5d86f..108b33b57d 100644 --- a/commands/ip_test.go +++ b/commands/ip_test.go @@ -5,6 +5,7 @@ import ( "github.com/docker/machine/commands/commandstest" "github.com/docker/machine/drivers/fakedriver" + "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/host" "github.com/docker/machine/libmachine/libmachinetest" "github.com/docker/machine/libmachine/state" @@ -17,30 +18,62 @@ func TestCmdIPMissingMachineName(t *testing.T) { err := cmdURL(commandLine, api) - assert.EqualError(t, err, "Error: Expected one machine name as an argument") + assert.Equal(t, err, ErrNoDefault) } func TestCmdIP(t *testing.T) { - commandLine := &commandstest.FakeCommandLine{ - CliArgs: []string{"machine"}, - } - api := &libmachinetest.FakeAPI{ - Hosts: []*host.Host{ - { - Name: "machine", - Driver: &fakedriver.Driver{ - MockState: state.Running, - MockIP: "1.2.3.4", + testCases := []struct { + commandLine CommandLine + api libmachine.API + expectedErr error + expectedOut string + }{ + { + commandLine: &commandstest.FakeCommandLine{ + CliArgs: []string{"machine"}, + }, + api: &libmachinetest.FakeAPI{ + Hosts: []*host.Host{ + { + Name: "machine", + Driver: &fakedriver.Driver{ + MockState: state.Running, + MockIP: "1.2.3.4", + }, + }, }, }, + expectedErr: nil, + expectedOut: "1.2.3.4\n", + }, + { + commandLine: &commandstest.FakeCommandLine{ + CliArgs: []string{}, + }, + api: &libmachinetest.FakeAPI{ + Hosts: []*host.Host{ + { + Name: "default", + Driver: &fakedriver.Driver{ + MockState: state.Running, + MockIP: "1.2.3.4", + }, + }, + }, + }, + expectedErr: nil, + expectedOut: "1.2.3.4\n", }, } - stdoutGetter := commandstest.NewStdoutGetter() - defer stdoutGetter.Stop() + for _, tc := range testCases { + stdoutGetter := commandstest.NewStdoutGetter() - err := cmdIP(commandLine, api) + err := cmdIP(tc.commandLine, tc.api) - assert.NoError(t, err) - assert.Equal(t, "1.2.3.4\n", stdoutGetter.Output()) + assert.Equal(t, tc.expectedErr, err) + assert.Equal(t, tc.expectedOut, stdoutGetter.Output()) + + stdoutGetter.Stop() + } } diff --git a/commands/kill_test.go b/commands/kill_test.go index 80deaf2bbe..23c1ef5a09 100644 --- a/commands/kill_test.go +++ b/commands/kill_test.go @@ -17,7 +17,7 @@ func TestCmdKillMissingMachineName(t *testing.T) { err := cmdKill(commandLine, api) - assert.EqualError(t, err, "Error: Expected to get one or more machine names as arguments") + assert.Equal(t, ErrNoDefault, err) } func TestCmdKill(t *testing.T) { diff --git a/commands/rm_test.go b/commands/rm_test.go index c76b5b4f5d..adfe6a29ae 100644 --- a/commands/rm_test.go +++ b/commands/rm_test.go @@ -18,7 +18,7 @@ func TestCmdRmMissingMachineName(t *testing.T) { err := cmdRm(commandLine, api) - assert.EqualError(t, err, "Error: Expected to get one or more machine names as arguments") + assert.Equal(t, ErrNoMachineSpecified, err) assert.True(t, commandLine.HelpShown) } diff --git a/commands/ssh.go b/commands/ssh.go index 3a9e1f5f9c..3c34174c09 100644 --- a/commands/ssh.go +++ b/commands/ssh.go @@ -23,12 +23,12 @@ func cmdSSH(c CommandLine, api libmachine.API) error { return nil } - name := firstArg - if name == "" { - return ErrExpectedOneMachine + target, err := targetHost(c, api) + if err != nil { + return err } - host, err := api.Load(name) + host, err := api.Load(target) if err != nil { return err } diff --git a/commands/ssh_test.go b/commands/ssh_test.go index 11fc76a4ef..ebfa10fdf7 100644 --- a/commands/ssh_test.go +++ b/commands/ssh_test.go @@ -53,10 +53,10 @@ func TestCmdSSH(t *testing.T) { }, { commandLine: &commandstest.FakeCommandLine{ - CliArgs: []string{""}, + CliArgs: []string{}, }, api: &libmachinetest.FakeAPI{}, - expectedErr: ErrExpectedOneMachine, + expectedErr: ErrNoDefault, }, { commandLine: &commandstest.FakeCommandLine{ diff --git a/commands/status.go b/commands/status.go index d5c30b060a..6a1c9d72d6 100644 --- a/commands/status.go +++ b/commands/status.go @@ -6,11 +6,16 @@ import ( ) func cmdStatus(c CommandLine, api libmachine.API) error { - if len(c.Args()) != 1 { + if len(c.Args()) > 1 { return ErrExpectedOneMachine } - host, err := api.Load(c.Args().First()) + target, err := targetHost(c, api) + if err != nil { + return err + } + + host, err := api.Load(target) if err != nil { return err } diff --git a/commands/stop_test.go b/commands/stop_test.go index 08bcd5240f..76af0955bc 100644 --- a/commands/stop_test.go +++ b/commands/stop_test.go @@ -5,52 +5,99 @@ import ( "github.com/docker/machine/commands/commandstest" "github.com/docker/machine/drivers/fakedriver" + "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/host" "github.com/docker/machine/libmachine/libmachinetest" "github.com/docker/machine/libmachine/state" "github.com/stretchr/testify/assert" ) -func TestCmdStopMissingMachineName(t *testing.T) { - commandLine := &commandstest.FakeCommandLine{} - api := &libmachinetest.FakeAPI{} - - err := cmdStop(commandLine, api) - - assert.EqualError(t, err, "Error: Expected to get one or more machine names as arguments") -} - func TestCmdStop(t *testing.T) { - commandLine := &commandstest.FakeCommandLine{ - CliArgs: []string{"machineToStop1", "machineToStop2"}, - } - api := &libmachinetest.FakeAPI{ - Hosts: []*host.Host{ - { - Name: "machineToStop1", - Driver: &fakedriver.Driver{ - MockState: state.Running, + testCases := []struct { + commandLine CommandLine + api libmachine.API + expectedErr error + expectedStates map[string]state.State + }{ + { + commandLine: &commandstest.FakeCommandLine{ + CliArgs: []string{}, + }, + api: &libmachinetest.FakeAPI{ + Hosts: []*host.Host{ + { + Name: "default", + Driver: &fakedriver.Driver{ + MockState: state.Running, + }, + }, }, }, - { - Name: "machineToStop2", - Driver: &fakedriver.Driver{ - MockState: state.Running, + expectedErr: nil, + expectedStates: map[string]state.State{ + "default": state.Stopped, + }, + }, + { + commandLine: &commandstest.FakeCommandLine{ + CliArgs: []string{}, + }, + api: &libmachinetest.FakeAPI{ + Hosts: []*host.Host{ + { + Name: "foobar", + Driver: &fakedriver.Driver{ + MockState: state.Running, + }, + }, }, }, - { - Name: "machine", - Driver: &fakedriver.Driver{ - MockState: state.Running, + expectedErr: ErrNoDefault, + expectedStates: map[string]state.State{ + "foobar": state.Running, + }, + }, + { + commandLine: &commandstest.FakeCommandLine{ + CliArgs: []string{"machineToStop1", "machineToStop2"}, + }, + api: &libmachinetest.FakeAPI{ + Hosts: []*host.Host{ + { + Name: "machineToStop1", + Driver: &fakedriver.Driver{ + MockState: state.Running, + }, + }, + { + Name: "machineToStop2", + Driver: &fakedriver.Driver{ + MockState: state.Running, + }, + }, + { + Name: "machine", + Driver: &fakedriver.Driver{ + MockState: state.Running, + }, + }, }, }, + expectedErr: nil, + expectedStates: map[string]state.State{ + "machineToStop1": state.Stopped, + "machineToStop2": state.Stopped, + "machine": state.Running, + }, }, } - err := cmdStop(commandLine, api) - assert.NoError(t, err) + for _, tc := range testCases { + err := cmdStop(tc.commandLine, tc.api) + assert.Equal(t, tc.expectedErr, err) - assert.Equal(t, state.Stopped, libmachinetest.State(api, "machineToStop1")) - assert.Equal(t, state.Stopped, libmachinetest.State(api, "machineToStop2")) - assert.Equal(t, state.Running, libmachinetest.State(api, "machine")) + for hostName, expectedState := range tc.expectedStates { + assert.Equal(t, expectedState, libmachinetest.State(tc.api, hostName)) + } + } } diff --git a/commands/url.go b/commands/url.go index e5611f209b..8552e14bf0 100644 --- a/commands/url.go +++ b/commands/url.go @@ -7,11 +7,16 @@ import ( ) func cmdURL(c CommandLine, api libmachine.API) error { - if len(c.Args()) != 1 { + if len(c.Args()) > 1 { return ErrExpectedOneMachine } - host, err := api.Load(c.Args().First()) + target, err := targetHost(c, api) + if err != nil { + return err + } + + host, err := api.Load(target) if err != nil { return err } diff --git a/commands/url_test.go b/commands/url_test.go index 72be4cfaa9..02f72689f6 100644 --- a/commands/url_test.go +++ b/commands/url_test.go @@ -17,7 +17,7 @@ func TestCmdURLMissingMachineName(t *testing.T) { err := cmdURL(commandLine, api) - assert.EqualError(t, err, "Error: Expected one machine name as an argument") + assert.Equal(t, ErrNoDefault, err) } func TestCmdURLTooManyNames(t *testing.T) { diff --git a/docs/get-started.md b/docs/get-started.md index 47ef6e136c..11b5e81318 100644 --- a/docs/get-started.md +++ b/docs/get-started.md @@ -62,7 +62,7 @@ Next, as noted in the output of the `docker-machine create` command, we have to tell Docker to talk to that machine. You can do this with the `docker-machine env` command. For example, - $ eval "$(docker-machine env dev)" + $ eval "$(docker-machine env default)" $ docker ps > **Note**: If you are using `fish`, or a Windows shell such as @@ -74,15 +74,15 @@ This will set environment variables that the Docker client will read which specify the TLS settings. Note that you will need to do that every time you open a new tab or restart your machine. -To see what will be set, run `docker-machine env dev`. +To see what will be set, we can run `docker-machine env default`. - $ docker-machine env dev + $ docker-machine env default export DOCKER_TLS_VERIFY="1" export DOCKER_HOST="tcp://172.16.62.130:2376" - export DOCKER_CERT_PATH="/Users//.docker/machine/machines/dev" - export DOCKER_MACHINE_NAME="dev" + export DOCKER_CERT_PATH="/Users//.docker/machine/machines/default" + export DOCKER_MACHINE_NAME="default" # Run this command to configure your shell: - # eval "$(docker-machine env dev)" + # eval "$(docker-machine env default)" You can now run Docker commands on this host: @@ -98,7 +98,7 @@ You can now run Docker commands on this host: Any exposed ports are available on the Docker host’s IP address, which you can get using the `docker-machine ip` command: - $ docker-machine ip dev + $ docker-machine ip default 192.168.99.100 For instance, you can try running a webserver ([nginx](https://www.nginx.com/) @@ -109,7 +109,7 @@ in a container with the following command: When the image is finished pulling, you can hit the server at port 8000 on the IP address given to you by `docker-machine ip`. For instance: - $ curl $(docker-machine ip dev):8000 + $ curl $(docker-machine ip default):8000 @@ -142,7 +142,47 @@ output of `docker-machine ls`. If you are finished using a host for the time being, you can stop it with `docker-machine stop` and later start it again with `docker-machine start`. -Make sure to specify the machine name as an argument: - $ docker-machine stop dev - $ docker-machine start dev + $ docker-machine stop default + $ docker-machine start default + +## Operating on machines without specifying the name + +Some commands will assume that the specified operation should be run on a +machine named `default` (if it exists) if no machine name is passed to them as +an argument. Because using a local VM named `default` is such a common pattern, +this allows you to save some typing on Machine commands that may be frequently +invoked. + +For instance: + + $ docker-machine stop + Stopping "default".... + Machine "default" was stopped. + + $ docker-machine start + Starting "default"... + (default) Waiting for an IP... + Machine "default" was started. + Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command. + + $ eval $(docker-machine env) + + $ docker-machine ip + 192.168.99.100 + +Commands that will follow this style are: + +- `docker-machine stop` +- `docker-machine start` +- `docker-machine regenerate-certs` +- `docker-machine restart` +- `docker-machine kill` +- `docker-machine upgrade` +- `docker-machine config` +- `docker-machine env` +- `docker-machine inspect` +- `docker-machine url` + +For machines other than `default`, and commands other than those listed above, +you must always specify the name explicitly as an argument. diff --git a/test/integration/cli/inspect.bats b/test/integration/cli/inspect.bats index 09c03ec130..d1674442cf 100644 --- a/test/integration/cli/inspect.bats +++ b/test/integration/cli/inspect.bats @@ -5,5 +5,5 @@ load ${BASE_TEST_DIR}/helpers.bash @test "inspect: show error in case of no args" { run machine inspect [ "$status" -eq 1 ] - [[ ${output} == *"Expected one machine name as an argument"* ]] + [[ ${output} == *"Error: No machine name(s) specified and no \"default\" machine exists."* ]] } diff --git a/test/integration/cli/status.bats b/test/integration/cli/status.bats index 6d52b73699..b3e8387e11 100644 --- a/test/integration/cli/status.bats +++ b/test/integration/cli/status.bats @@ -5,5 +5,5 @@ load ${BASE_TEST_DIR}/helpers.bash @test "status: show error in case of no args" { run machine status [ "$status" -eq 1 ] - [[ ${output} == *"Expected one machine name as an argument"* ]] + [[ ${output} == *"Error: No machine name(s) specified and no \"default\" machine exists."* ]] } diff --git a/test/integration/cli/url.bats b/test/integration/cli/url.bats index 75ec1d3f7e..671fd7317c 100644 --- a/test/integration/cli/url.bats +++ b/test/integration/cli/url.bats @@ -5,5 +5,5 @@ load ${BASE_TEST_DIR}/helpers.bash @test "url: show error in case of no args" { run machine url [ "$status" -eq 1 ] - [[ ${output} == *"Expected one machine name as an argument"* ]] + [[ ${output} == *"Error: No machine name(s) specified and no \"default\" machine exists."* ]] }