Add version to machine ls

Signed-off-by: David Gageot <david@gageot.net>
This commit is contained in:
David Gageot 2015-11-27 18:32:13 +01:00
parent e26c485013
commit 3612c55677
19 changed files with 332 additions and 50 deletions

View File

@ -9,13 +9,12 @@ import (
) )
var ( var (
errTooManyArguments = errors.New("Error: Too many arguments given") errNoActiveHost = errors.New("No active host found")
errNoActiveHost = errors.New("No active host found")
) )
func cmdActive(c CommandLine, api libmachine.API) error { func cmdActive(c CommandLine, api libmachine.API) error {
if len(c.Args()) > 0 { if len(c.Args()) > 0 {
return errTooManyArguments return ErrTooManyArguments
} }
hosts, hostsInError, err := persist.LoadAllHosts(api) hosts, hostsInError, err := persist.LoadAllHosts(api)

View File

@ -22,6 +22,7 @@ var (
ErrUnknownShell = errors.New("Error: Unknown shell") ErrUnknownShell = errors.New("Error: Unknown shell")
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")
) )
// CommandLine contains all the information passed to the commands on the command line. // CommandLine contains all the information passed to the commands on the command line.
@ -305,7 +306,7 @@ var Commands = []cli.Command{
}, },
{ {
Name: "version", Name: "version",
Usage: "Show the Docker Machine version information", Usage: "Show the Docker Machine version or a machine docker version",
Action: fatalOnError(cmdVersion), Action: fatalOnError(cmdVersion),
}, },
} }
@ -316,7 +317,9 @@ func printIP(h *host.Host) func() error {
if err != nil { if err != nil {
return fmt.Errorf("Error getting IP address: %s", err) return fmt.Errorf("Error getting IP address: %s", err)
} }
fmt.Println(ip) fmt.Println(ip)
return nil return nil
} }
} }

View File

@ -10,7 +10,7 @@ type FakeFlagger struct {
type FakeCommandLine struct { type FakeCommandLine struct {
LocalFlags, GlobalFlags *FakeFlagger LocalFlags, GlobalFlags *FakeFlagger
HelpShown bool HelpShown, VersionShown bool
CliArgs []string CliArgs []string
} }
@ -91,5 +91,5 @@ func (fcli *FakeCommandLine) Args() cli.Args {
} }
func (fcli *FakeCommandLine) ShowVersion() { func (fcli *FakeCommandLine) ShowVersion() {
return fcli.VersionShown = true
} }

View File

@ -33,13 +33,14 @@ type FilterOptions struct {
} }
type HostListItem struct { type HostListItem struct {
Name string Name string
Active bool Active bool
DriverName string DriverName string
State state.State State state.State
URL string URL string
SwarmOptions *swarm.Options SwarmOptions *swarm.Options
Error string Error string
DockerVersion string
} }
func cmdLs(c CommandLine, api libmachine.API) error { func cmdLs(c CommandLine, api libmachine.API) error {
@ -68,7 +69,7 @@ func cmdLs(c CommandLine, api libmachine.API) error {
swarmInfo := make(map[string]string) swarmInfo := make(map[string]string)
w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0)
fmt.Fprintln(w, "NAME\tACTIVE\tDRIVER\tSTATE\tURL\tSWARM\tERRORS") fmt.Fprintln(w, "NAME\tACTIVE\tDRIVER\tSTATE\tURL\tSWARM\tDOCKER\tERRORS")
for _, host := range hostList { for _, host := range hostList {
swarmOptions := host.HostOptions.SwarmOptions swarmOptions := host.HostOptions.SwarmOptions
@ -97,8 +98,8 @@ func cmdLs(c CommandLine, api libmachine.API) error {
swarmInfo = fmt.Sprintf("%s (master)", swarmInfo) swarmInfo = fmt.Sprintf("%s (master)", swarmInfo)
} }
} }
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
item.Name, activeString, item.DriverName, item.State, item.URL, swarmInfo, item.Error) item.Name, activeString, item.DriverName, item.State, item.URL, swarmInfo, item.DockerVersion, item.Error)
} }
w.Flush() w.Flush()
@ -232,11 +233,21 @@ func matchesName(host *host.Host, names []string) bool {
func attemptGetHostState(h *host.Host, stateQueryChan chan<- HostListItem) { func attemptGetHostState(h *host.Host, stateQueryChan chan<- HostListItem) {
url := "" url := ""
hostError := "" hostError := ""
dockerVersion := "Unknown"
currentState, err := h.Driver.GetState() currentState, err := h.Driver.GetState()
if err == nil { if err == nil {
url, err = h.GetURL() url, err = h.URL()
} }
if err == nil {
dockerVersion, err = h.DockerVersion()
if err != nil {
dockerVersion = "Unknown"
} else {
dockerVersion = fmt.Sprintf("v%s", dockerVersion)
}
}
if err != nil { if err != nil {
hostError = err.Error() hostError = err.Error()
} }
@ -250,13 +261,14 @@ func attemptGetHostState(h *host.Host, stateQueryChan chan<- HostListItem) {
} }
stateQueryChan <- HostListItem{ stateQueryChan <- HostListItem{
Name: h.Name, Name: h.Name,
Active: isActive(currentState, url), Active: isActive(currentState, url),
DriverName: h.Driver.DriverName(), DriverName: h.Driver.DriverName(),
State: currentState, State: currentState,
URL: url, URL: url,
SwarmOptions: swarmOptions, SwarmOptions: swarmOptions,
Error: hostError, DockerVersion: dockerVersion,
Error: hostError,
} }
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/machine/drivers/fakedriver" "github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine/host" "github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/mcndockerclient"
"github.com/docker/machine/libmachine/state" "github.com/docker/machine/libmachine/state"
"github.com/docker/machine/libmachine/swarm" "github.com/docker/machine/libmachine/swarm"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -296,6 +297,9 @@ func TestFilterHostsDifferentFlagsProduceAND(t *testing.T) {
} }
func TestGetHostListItems(t *testing.T) { func TestGetHostListItems(t *testing.T) {
mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Version: "1.9"}
defer mcndockerclient.CleanupDockerVersioner()
// TODO: Ideally this would mockable via interface instead. // TODO: Ideally this would mockable via interface instead.
os.Setenv("DOCKER_HOST", "tcp://active.host.com:2376") os.Setenv("DOCKER_HOST", "tcp://active.host.com:2376")
@ -331,14 +335,15 @@ func TestGetHostListItems(t *testing.T) {
} }
expected := []struct { expected := []struct {
name string name string
state state.State state state.State
active bool active bool
error string version string
error string
}{ }{
{"bar10", state.Error, false, "Unable to get ip"}, {"bar10", state.Error, false, "Unknown", "Unable to get ip"},
{"bar100", state.Stopped, false, ""}, {"bar100", state.Stopped, false, "Unknown", ""},
{"foo", state.Running, true, ""}, {"foo", state.Running, true, "v1.9", ""},
} }
items := getHostListItems(hosts, map[string]error{}) items := getHostListItems(hosts, map[string]error{})
@ -347,6 +352,7 @@ func TestGetHostListItems(t *testing.T) {
assert.Equal(t, expected[i].name, items[i].Name) assert.Equal(t, expected[i].name, items[i].Name)
assert.Equal(t, expected[i].state, items[i].State) assert.Equal(t, expected[i].state, items[i].State)
assert.Equal(t, expected[i].active, items[i].Active) assert.Equal(t, expected[i].active, items[i].Active)
assert.Equal(t, expected[i].version, items[i].DockerVersion)
assert.Equal(t, expected[i].error, items[i].Error) assert.Equal(t, expected[i].error, items[i].Error)
} }
@ -355,6 +361,9 @@ func TestGetHostListItems(t *testing.T) {
// issue #1908 // issue #1908
func TestGetHostListItemsEnvDockerHostUnset(t *testing.T) { func TestGetHostListItemsEnvDockerHostUnset(t *testing.T) {
mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Version: "1.9"}
defer mcndockerclient.CleanupDockerVersioner()
orgDockerHost := os.Getenv("DOCKER_HOST") orgDockerHost := os.Getenv("DOCKER_HOST")
defer func() { defer func() {
// revert DOCKER_HOST // revert DOCKER_HOST
@ -408,12 +417,13 @@ func TestGetHostListItemsEnvDockerHostUnset(t *testing.T) {
} }
expected := map[string]struct { expected := map[string]struct {
state state.State state state.State
active bool active bool
version string
}{ }{
"foo": {state.Running, false}, "foo": {state.Running, false, "v1.9"},
"bar": {state.Stopped, false}, "bar": {state.Stopped, false, "Unknown"},
"baz": {state.Saved, false}, "baz": {state.Saved, false, "Unknown"},
} }
items := getHostListItems(hosts, map[string]error{}) items := getHostListItems(hosts, map[string]error{})
@ -423,6 +433,7 @@ func TestGetHostListItemsEnvDockerHostUnset(t *testing.T) {
assert.Equal(t, expected.state, item.State) assert.Equal(t, expected.state, item.State)
assert.Equal(t, expected.active, item.Active) assert.Equal(t, expected.active, item.Active)
assert.Equal(t, expected.version, item.DockerVersion)
} }
} }
@ -463,7 +474,7 @@ func TestGetHostStateTimeout(t *testing.T) {
}, },
} }
stateTimeoutDuration = 1 * time.Second stateTimeoutDuration = 1 * time.Millisecond
hostItems := getHostListItems(hosts, map[string]error{}) hostItems := getHostListItems(hosts, map[string]error{})
hostItem := hostItems[0] hostItem := hostItems[0]
@ -496,6 +507,9 @@ func TestGetHostStateError(t *testing.T) {
} }
func TestGetSomeHostInEror(t *testing.T) { func TestGetSomeHostInEror(t *testing.T) {
mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Version: "1.9"}
defer mcndockerclient.CleanupDockerVersioner()
hosts := []*host.Host{ hosts := []*host.Host{
{ {
Name: "foo", Name: "foo",

View File

@ -16,7 +16,7 @@ func cmdURL(c CommandLine, api libmachine.API) error {
return err return err
} }
url, err := host.GetURL() url, err := host.URL()
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,8 +1,39 @@
package commands package commands
import "github.com/docker/machine/libmachine" import (
"fmt"
"io"
"os"
"github.com/docker/machine/libmachine"
)
func cmdVersion(c CommandLine, api libmachine.API) error { func cmdVersion(c CommandLine, api libmachine.API) error {
c.ShowVersion() return printVersion(c, api, os.Stdout)
}
func printVersion(c CommandLine, api libmachine.API, out io.Writer) error {
if len(c.Args()) == 0 {
c.ShowVersion()
return nil
}
if len(c.Args()) != 1 {
return ErrExpectedOneMachine
}
host, err := api.Load(c.Args().First())
if err != nil {
return err
}
version, err := host.DockerVersion()
if err != nil {
return err
}
fmt.Fprintln(out, version)
return nil return nil
} }

89
commands/version_test.go Normal file
View File

@ -0,0 +1,89 @@
package commands
import (
"testing"
"bytes"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/vmwarevsphere/errors"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/mcndockerclient"
"github.com/stretchr/testify/assert"
)
func TestCmdVersion(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{}
api := &libmachinetest.FakeAPI{}
err := cmdVersion(commandLine, api)
assert.True(t, commandLine.VersionShown)
assert.NoError(t, err)
}
func TestCmdVersionTooManyNames(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machine1", "machine2"},
}
api := &libmachinetest.FakeAPI{}
err := cmdVersion(commandLine, api)
assert.EqualError(t, err, "Error: Expected one machine name as an argument")
}
func TestCmdVersionNotFound(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"unknown"},
}
api := &libmachinetest.FakeAPI{}
err := cmdVersion(commandLine, api)
assert.EqualError(t, err, `Host does not exist: "unknown"`)
}
func TestCmdVersionOnHost(t *testing.T) {
mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Version: "1.9.1"}
defer mcndockerclient.CleanupDockerVersioner()
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machine"},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machine",
},
},
}
out := &bytes.Buffer{}
err := printVersion(commandLine, api, out)
assert.NoError(t, err)
assert.Equal(t, "1.9.1\n", out.String())
}
func TestCmdVersionFailure(t *testing.T) {
mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Err: errors.New("connection failure")}
defer mcndockerclient.CleanupDockerVersioner()
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machine"},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machine",
},
},
}
out := &bytes.Buffer{}
err := printVersion(commandLine, api, out)
assert.EqualError(t, err, "connection failure")
}

View File

@ -185,6 +185,14 @@ _docker_machine_url() {
fi fi
} }
_docker_machine_version() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_help() { _docker_machine_help() {
if [[ "${cur}" == -* ]]; then if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}")) COMPREPLY=($(compgen -W "--help" -- "${cur}"))

View File

@ -54,8 +54,8 @@ instructions in the next section.
3. Check the installation by displaying the Machine version: 3. Check the installation by displaying the Machine version:
$ docker-machine -v $ docker-machine version
machine version 0.5.0 (3e06852) docker-machine version 0.5.2 (0456b9f)
## Installing bash completion scripts ## Installing bash completion scripts

View File

@ -24,6 +24,7 @@ var defaultGenerator = NewX509CertGenerator()
type Generator interface { type Generator interface {
GenerateCACertificate(certFile, keyFile, org string, bits int) error GenerateCACertificate(certFile, keyFile, org string, bits int) error
GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org string, bits int) error GenerateCert(hosts []string, certFile, keyFile, caFile, caKeyFile, org string, bits int) error
ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error)
ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) ValidateCertificate(addr string, authOptions *auth.Options) (bool, error)
} }
@ -45,6 +46,10 @@ func ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) {
return defaultGenerator.ValidateCertificate(addr, authOptions) return defaultGenerator.ValidateCertificate(addr, authOptions)
} }
func ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error) {
return defaultGenerator.ReadTLSConfig(addr, authOptions)
}
func SetCertGenerator(cg Generator) { func SetCertGenerator(cg Generator) {
defaultGenerator = cg defaultGenerator = cg
} }
@ -204,8 +209,8 @@ func (xcg *X509CertGenerator) GenerateCert(hosts []string, certFile, keyFile, ca
return nil return nil
} }
// ValidateCertificate validate the certificate installed on the vm. // ReadTLSConfig reads the tls config for a machine.
func (xcg *X509CertGenerator) ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) { func (xcg *X509CertGenerator) ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error) {
caCertPath := authOptions.CaCertPath caCertPath := authOptions.CaCertPath
serverCertPath := authOptions.ServerCertPath serverCertPath := authOptions.ServerCertPath
serverKeyPath := authOptions.ServerKeyPath serverKeyPath := authOptions.ServerKeyPath
@ -213,22 +218,27 @@ func (xcg *X509CertGenerator) ValidateCertificate(addr string, authOptions *auth
log.Debugf("Reading CA certificate from %s", caCertPath) log.Debugf("Reading CA certificate from %s", caCertPath)
caCert, err := ioutil.ReadFile(caCertPath) caCert, err := ioutil.ReadFile(caCertPath)
if err != nil { if err != nil {
return false, err return nil, err
} }
log.Debugf("Reading server certificate from %s", serverCertPath) log.Debugf("Reading server certificate from %s", serverCertPath)
serverCert, err := ioutil.ReadFile(serverCertPath) serverCert, err := ioutil.ReadFile(serverCertPath)
if err != nil { if err != nil {
return false, err return nil, err
} }
log.Debugf("Reading server key from %s", serverKeyPath) log.Debugf("Reading server key from %s", serverKeyPath)
serverKey, err := ioutil.ReadFile(serverKeyPath) serverKey, err := ioutil.ReadFile(serverKeyPath)
if err != nil { if err != nil {
return false, err return nil, err
} }
tlsConfig, err := xcg.getTLSConfig(caCert, serverCert, serverKey, false) return xcg.getTLSConfig(caCert, serverCert, serverKey, false)
}
// ValidateCertificate validate the certificate installed on the vm.
func (xcg *X509CertGenerator) ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) {
tlsConfig, err := xcg.ReadTLSConfig(addr, authOptions)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -4,6 +4,8 @@ import (
"errors" "errors"
"testing" "testing"
"crypto/tls"
"github.com/docker/machine/libmachine/auth" "github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/cert" "github.com/docker/machine/libmachine/cert"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -30,6 +32,10 @@ func (fcg FakeCertGenerator) ValidateCertificate(addr string, authOptions *auth.
return fcg.fakeValidateCertificate.IsValid, fcg.fakeValidateCertificate.Err return fcg.fakeValidateCertificate.IsValid, fcg.fakeValidateCertificate.Err
} }
func (fcg FakeCertGenerator) ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error) {
return nil, nil
}
func TestCheckCert(t *testing.T) { func TestCheckCert(t *testing.T) {
errCertsExpired := errors.New("Certs have expired") errCertsExpired := errors.New("Certs have expired")

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/engine" "github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcndockerclient"
"github.com/docker/machine/libmachine/mcnutils" "github.com/docker/machine/libmachine/mcnutils"
"github.com/docker/machine/libmachine/provision" "github.com/docker/machine/libmachine/provision"
"github.com/docker/machine/libmachine/provision/pkgaction" "github.com/docker/machine/libmachine/provision/pkgaction"
@ -145,10 +146,18 @@ func (h *Host) Upgrade() error {
return provisioner.Service("docker", serviceaction.Restart) return provisioner.Service("docker", serviceaction.Restart)
} }
func (h *Host) GetURL() (string, error) { func (h *Host) URL() (string, error) {
return h.Driver.GetURL() return h.Driver.GetURL()
} }
func (h *Host) AuthOptions() *auth.Options {
return h.HostOptions.AuthOptions
}
func (h *Host) DockerVersion() (string, error) {
return mcndockerclient.DockerVersion(h)
}
func (h *Host) ConfigureAuth() error { func (h *Host) ConfigureAuth() error {
provisioner, err := provision.DetectProvisioner(h.Driver) provisioner, err := provision.DetectProvisioner(h.Driver)
if err != nil { if err != nil {

View File

@ -0,0 +1,22 @@
package mcndockerclient
import (
"fmt"
"github.com/docker/machine/libmachine/cert"
"github.com/samalba/dockerclient"
)
func DockerClient(host DockerHost) (*dockerclient.DockerClient, error) {
url, err := host.URL()
if err != nil {
return nil, err
}
tlsConfig, err := cert.ReadTLSConfig(url, host.AuthOptions())
if err != nil {
return nil, fmt.Errorf("Unable to read TLS config: %s", err)
}
return dockerclient.NewDockerClient(url, tlsConfig)
}

View File

@ -0,0 +1,16 @@
package mcndockerclient
import "github.com/docker/machine/libmachine/auth"
type URLer interface {
URL() (string, error)
}
type AuthOptionser interface {
AuthOptions() *auth.Options
}
type DockerHost interface {
URLer
AuthOptionser
}

View File

@ -0,0 +1,29 @@
package mcndockerclient
import "fmt"
var CurrentDockerVersioner DockerVersioner = &defaultDockerVersioner{}
type DockerVersioner interface {
DockerVersion(host DockerHost) (string, error)
}
func DockerVersion(host DockerHost) (string, error) {
return CurrentDockerVersioner.DockerVersion(host)
}
type defaultDockerVersioner struct{}
func (dv *defaultDockerVersioner) DockerVersion(host DockerHost) (string, error) {
client, err := DockerClient(host)
if err != nil {
return "", fmt.Errorf("Unable to query docker version: %s", err)
}
version, err := client.Version()
if err != nil {
return "", fmt.Errorf("Unable to query docker version: %s", err)
}
return version.Version, nil
}

View File

@ -0,0 +1,20 @@
package mcndockerclient
var originalDockerVersioner = CurrentDockerVersioner
func CleanupDockerVersioner() {
CurrentDockerVersioner = originalDockerVersioner
}
type FakeDockerVersioner struct {
Version string
Err error
}
func (dv *FakeDockerVersioner) DockerVersion(host DockerHost) (string, error) {
if dv.Err != nil {
return "", dv.Err
}
return dv.Version, nil
}

View File

@ -238,7 +238,7 @@ func TestStoreLoad(t *testing.T) {
h.Driver = realDriver h.Driver = realDriver
actualURL, err := h.GetURL() actualURL, err := h.URL()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -61,6 +61,12 @@ load ${BASE_TEST_DIR}/helpers.bash
[[ ${lines[0]} =~ "total" ]] [[ ${lines[0]} =~ "total" ]]
} }
@test "$DRIVER: version" {
run machine version $NAME
echo ${output}
[ "$status" -eq 0 ]
}
@test "$DRIVER: docker commands with the socket should work" { @test "$DRIVER: docker commands with the socket should work" {
run machine ssh $NAME -- sudo docker version run machine ssh $NAME -- sudo docker version
echo ${output} echo ${output}
@ -93,6 +99,14 @@ load ${BASE_TEST_DIR}/helpers.bash
[[ ${output} == *"not running. Please start"* ]] [[ ${output} == *"not running. Please start"* ]]
} }
@test "$DRIVER: version should show an error when machine is stopped" {
run machine version $NAME
echo ${output}
[ "$status" -eq 1 ]
[[ ${output} == *"not running"* ]]
}
@test "$DRIVER: machine should not allow upgrade when stopped" { @test "$DRIVER: machine should not allow upgrade when stopped" {
run machine upgrade $NAME run machine upgrade $NAME
echo ${output} echo ${output}