Merge pull request #2843 from nathanleclaire/short_form_cmds

Add ability to imply 'default' VM in commands
This commit is contained in:
David Gageot 2016-01-18 08:49:31 +01:00
commit 51c49091c3
18 changed files with 308 additions and 86 deletions

View File

@ -18,7 +18,13 @@ import (
"github.com/docker/machine/libmachine/ssh" "github.com/docker/machine/libmachine/ssh"
) )
const (
defaultMachineName = "default"
)
var ( 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") 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") ErrExpectedOneMachine = errors.New("Error: Expected one machine name as an argument")
ErrTooManyArguments = errors.New("Error: Too many arguments given") ErrTooManyArguments = errors.New("Error: Too many arguments given")
@ -67,8 +73,45 @@ func (c *contextCommandLine) Application() *cli.App {
return c.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 { 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 { if len(hostsInError) > 0 {
errs := []error{} errs := []error{}
@ -79,7 +122,7 @@ func runAction(actionName string, c CommandLine, api libmachine.API) error {
} }
if len(hosts) == 0 { if len(hosts) == 0 {
return ErrNoMachineSpecified return ErrHostLoad
} }
if errs := runActionForeachMachine(actionName, hosts); len(errs) > 0 { if errs := runActionForeachMachine(actionName, hosts); len(errs) > 0 {

View File

@ -14,11 +14,12 @@ func cmdConfig(c CommandLine, api libmachine.API) error {
// being run (it is intended to be run in a subshell) // being run (it is intended to be run in a subshell)
log.SetOutWriter(os.Stderr) log.SetOutWriter(os.Stderr)
if len(c.Args()) != 1 { target, err := targetHost(c, api)
return ErrExpectedOneMachine if err != nil {
return err
} }
host, err := api.Load(c.Args().First()) host, err := api.Load(target)
if err != nil { if err != nil {
return err return err
} }

View File

@ -20,7 +20,6 @@ const (
) )
var ( var (
errImproperEnvArgs = errors.New("Error: Expected one machine name")
errImproperUnsetEnvArgs = errors.New("Error: Expected no machine name when the -u flag is present") errImproperUnsetEnvArgs = errors.New("Error: Expected no machine name when the -u flag is present")
defaultUsageHinter UsageHintGenerator defaultUsageHinter UsageHintGenerator
) )
@ -68,11 +67,16 @@ func cmdEnv(c CommandLine, api libmachine.API) error {
} }
func shellCfgSet(c CommandLine, api libmachine.API) (*ShellConfig, error) { func shellCfgSet(c CommandLine, api libmachine.API) (*ShellConfig, error) {
if len(c.Args()) != 1 { if len(c.Args()) > 1 {
return nil, errImproperEnvArgs 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -111,11 +111,14 @@ func TestShellCfgSet(t *testing.T) {
}{ }{
{ {
description: "no host name specified", description: "no host name specified",
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{},
},
commandLine: &commandstest.FakeCommandLine{ commandLine: &commandstest.FakeCommandLine{
CliArgs: nil, CliArgs: nil,
}, },
expectedShellCfg: nil, expectedShellCfg: nil,
expectedErr: errImproperEnvArgs, expectedErr: ErrNoDefault,
}, },
{ {
description: "bash shell set happy path without any flags set", description: "bash shell set happy path without any flags set",
@ -153,6 +156,42 @@ func TestShellCfgSet(t *testing.T) {
}, },
expectedErr: nil, 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", description: "fish shell set happy path",
commandLine: &commandstest.FakeCommandLine{ commandLine: &commandstest.FakeCommandLine{

View File

@ -21,12 +21,17 @@ var funcMap = template.FuncMap{
} }
func cmdInspect(c CommandLine, api libmachine.API) error { func cmdInspect(c CommandLine, api libmachine.API) error {
if len(c.Args()) == 0 { if len(c.Args()) > 1 {
c.ShowHelp() c.ShowHelp()
return ErrExpectedOneMachine 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 { if err != nil {
return err return err
} }

View File

@ -5,6 +5,7 @@ import (
"github.com/docker/machine/commands/commandstest" "github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/fakedriver" "github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/host" "github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest" "github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/state" "github.com/docker/machine/libmachine/state"
@ -17,14 +18,21 @@ func TestCmdIPMissingMachineName(t *testing.T) {
err := cmdURL(commandLine, api) 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) { func TestCmdIP(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{ testCases := []struct {
commandLine CommandLine
api libmachine.API
expectedErr error
expectedOut string
}{
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"machine"}, CliArgs: []string{"machine"},
} },
api := &libmachinetest.FakeAPI{ api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{ Hosts: []*host.Host{
{ {
Name: "machine", Name: "machine",
@ -34,13 +42,38 @@ func TestCmdIP(t *testing.T) {
}, },
}, },
}, },
},
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",
},
} }
for _, tc := range testCases {
stdoutGetter := commandstest.NewStdoutGetter() stdoutGetter := commandstest.NewStdoutGetter()
defer stdoutGetter.Stop()
err := cmdIP(commandLine, api) err := cmdIP(tc.commandLine, tc.api)
assert.NoError(t, err) assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, "1.2.3.4\n", stdoutGetter.Output()) assert.Equal(t, tc.expectedOut, stdoutGetter.Output())
stdoutGetter.Stop()
}
} }

View File

@ -17,7 +17,7 @@ func TestCmdKillMissingMachineName(t *testing.T) {
err := cmdKill(commandLine, api) 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) { func TestCmdKill(t *testing.T) {

View File

@ -18,7 +18,7 @@ func TestCmdRmMissingMachineName(t *testing.T) {
err := cmdRm(commandLine, api) 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) assert.True(t, commandLine.HelpShown)
} }

View File

@ -23,12 +23,12 @@ func cmdSSH(c CommandLine, api libmachine.API) error {
return nil return nil
} }
name := firstArg target, err := targetHost(c, api)
if name == "" { if err != nil {
return ErrExpectedOneMachine return err
} }
host, err := api.Load(name) host, err := api.Load(target)
if err != nil { if err != nil {
return err return err
} }

View File

@ -53,10 +53,10 @@ func TestCmdSSH(t *testing.T) {
}, },
{ {
commandLine: &commandstest.FakeCommandLine{ commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{""}, CliArgs: []string{},
}, },
api: &libmachinetest.FakeAPI{}, api: &libmachinetest.FakeAPI{},
expectedErr: ErrExpectedOneMachine, expectedErr: ErrNoDefault,
}, },
{ {
commandLine: &commandstest.FakeCommandLine{ commandLine: &commandstest.FakeCommandLine{

View File

@ -6,11 +6,16 @@ import (
) )
func cmdStatus(c CommandLine, api libmachine.API) error { func cmdStatus(c CommandLine, api libmachine.API) error {
if len(c.Args()) != 1 { if len(c.Args()) > 1 {
return ErrExpectedOneMachine 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 { if err != nil {
return err return err
} }

View File

@ -5,26 +5,63 @@ import (
"github.com/docker/machine/commands/commandstest" "github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/fakedriver" "github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/host" "github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest" "github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/state" "github.com/docker/machine/libmachine/state"
"github.com/stretchr/testify/assert" "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) { func TestCmdStop(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{ 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,
},
},
},
},
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,
},
},
},
},
expectedErr: ErrNoDefault,
expectedStates: map[string]state.State{
"foobar": state.Running,
},
},
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"machineToStop1", "machineToStop2"}, CliArgs: []string{"machineToStop1", "machineToStop2"},
} },
api := &libmachinetest.FakeAPI{ api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{ Hosts: []*host.Host{
{ {
Name: "machineToStop1", Name: "machineToStop1",
@ -45,12 +82,22 @@ func TestCmdStop(t *testing.T) {
}, },
}, },
}, },
},
expectedErr: nil,
expectedStates: map[string]state.State{
"machineToStop1": state.Stopped,
"machineToStop2": state.Stopped,
"machine": state.Running,
},
},
} }
err := cmdStop(commandLine, api) for _, tc := range testCases {
assert.NoError(t, err) err := cmdStop(tc.commandLine, tc.api)
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, state.Stopped, libmachinetest.State(api, "machineToStop1")) for hostName, expectedState := range tc.expectedStates {
assert.Equal(t, state.Stopped, libmachinetest.State(api, "machineToStop2")) assert.Equal(t, expectedState, libmachinetest.State(tc.api, hostName))
assert.Equal(t, state.Running, libmachinetest.State(api, "machine")) }
}
} }

View File

@ -7,11 +7,16 @@ import (
) )
func cmdURL(c CommandLine, api libmachine.API) error { func cmdURL(c CommandLine, api libmachine.API) error {
if len(c.Args()) != 1 { if len(c.Args()) > 1 {
return ErrExpectedOneMachine 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 { if err != nil {
return err return err
} }

View File

@ -17,7 +17,7 @@ func TestCmdURLMissingMachineName(t *testing.T) {
err := cmdURL(commandLine, api) 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) { func TestCmdURLTooManyNames(t *testing.T) {

View File

@ -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 tell Docker to talk to that machine. You can do this with the `docker-machine
env` command. For example, env` command. For example,
$ eval "$(docker-machine env dev)" $ eval "$(docker-machine env default)"
$ docker ps $ docker ps
> **Note**: If you are using `fish`, or a Windows shell such as > **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 specify the TLS settings. Note that you will need to do that every time you
open a new tab or restart your machine. 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_TLS_VERIFY="1"
export DOCKER_HOST="tcp://172.16.62.130:2376" export DOCKER_HOST="tcp://172.16.62.130:2376"
export DOCKER_CERT_PATH="/Users/<your username>/.docker/machine/machines/dev" export DOCKER_CERT_PATH="/Users/<yourusername>/.docker/machine/machines/default"
export DOCKER_MACHINE_NAME="dev" export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell: # 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: 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 hosts IP address, which you can Any exposed ports are available on the Docker hosts IP address, which you can
get using the `docker-machine ip` command: get using the `docker-machine ip` command:
$ docker-machine ip dev $ docker-machine ip default
192.168.99.100 192.168.99.100
For instance, you can try running a webserver ([nginx](https://www.nginx.com/) 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 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: IP address given to you by `docker-machine ip`. For instance:
$ curl $(docker-machine ip dev):8000 $ curl $(docker-machine ip default):8000
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -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 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`. `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 stop default
$ docker-machine start dev $ 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.

View File

@ -5,5 +5,5 @@ load ${BASE_TEST_DIR}/helpers.bash
@test "inspect: show error in case of no args" { @test "inspect: show error in case of no args" {
run machine inspect run machine inspect
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
[[ ${output} == *"Expected one machine name as an argument"* ]] [[ ${output} == *"Error: No machine name(s) specified and no \"default\" machine exists."* ]]
} }

View File

@ -5,5 +5,5 @@ load ${BASE_TEST_DIR}/helpers.bash
@test "status: show error in case of no args" { @test "status: show error in case of no args" {
run machine status run machine status
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
[[ ${output} == *"Expected one machine name as an argument"* ]] [[ ${output} == *"Error: No machine name(s) specified and no \"default\" machine exists."* ]]
} }

View File

@ -5,5 +5,5 @@ load ${BASE_TEST_DIR}/helpers.bash
@test "url: show error in case of no args" { @test "url: show error in case of no args" {
run machine url run machine url
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
[[ ${output} == *"Expected one machine name as an argument"* ]] [[ ${output} == *"Error: No machine name(s) specified and no \"default\" machine exists."* ]]
} }