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"
)
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 {

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)
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
}

View File

@ -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
}

View File

@ -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{

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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) {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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{

View File

@ -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
}

View File

@ -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))
}
}
}

View File

@ -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
}

View File

@ -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) {

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
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/<your username>/.docker/machine/machines/dev"
export DOCKER_MACHINE_NAME="dev"
export DOCKER_CERT_PATH="/Users/<yourusername>/.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 hosts 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
<!DOCTYPE html>
<html>
<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
`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.

View File

@ -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."* ]]
}

View File

@ -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."* ]]
}

View File

@ -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."* ]]
}