From 3612c556770b19e330356f8e285becfd392328b5 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 27 Nov 2015 18:32:13 +0100 Subject: [PATCH] Add version to machine ls Signed-off-by: David Gageot --- commands/active.go | 5 +- commands/commands.go | 5 +- commands/commandstest/fake_command_line.go | 4 +- commands/ls.go | 48 ++++++---- commands/ls_test.go | 40 ++++++--- commands/url.go | 2 +- commands/version.go | 35 +++++++- commands/version_test.go | 89 +++++++++++++++++++ contrib/completion/bash/docker-machine.bash | 8 ++ docs/install-machine.md | 4 +- libmachine/cert/cert.go | 22 +++-- libmachine/check/check_test.go | 6 ++ libmachine/host/host.go | 11 ++- libmachine/mcndockerclient/docker_client.go | 22 +++++ libmachine/mcndockerclient/docker_host.go | 16 ++++ .../mcndockerclient/docker_versioner.go | 29 ++++++ .../mcndockerclient/fake_docker_versioner.go | 20 +++++ libmachine/persist/filestore_test.go | 2 +- test/integration/core/core-commands.bats | 14 +++ 19 files changed, 332 insertions(+), 50 deletions(-) create mode 100644 commands/version_test.go create mode 100644 libmachine/mcndockerclient/docker_client.go create mode 100644 libmachine/mcndockerclient/docker_host.go create mode 100644 libmachine/mcndockerclient/docker_versioner.go create mode 100644 libmachine/mcndockerclient/fake_docker_versioner.go diff --git a/commands/active.go b/commands/active.go index 09661cc239..33743b08b3 100644 --- a/commands/active.go +++ b/commands/active.go @@ -9,13 +9,12 @@ import ( ) 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 { if len(c.Args()) > 0 { - return errTooManyArguments + return ErrTooManyArguments } hosts, hostsInError, err := persist.LoadAllHosts(api) diff --git a/commands/commands.go b/commands/commands.go index 8727bb3006..ab67792b67 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -22,6 +22,7 @@ var ( ErrUnknownShell = errors.New("Error: Unknown shell") 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") ) // CommandLine contains all the information passed to the commands on the command line. @@ -305,7 +306,7 @@ var Commands = []cli.Command{ }, { Name: "version", - Usage: "Show the Docker Machine version information", + Usage: "Show the Docker Machine version or a machine docker version", Action: fatalOnError(cmdVersion), }, } @@ -316,7 +317,9 @@ func printIP(h *host.Host) func() error { if err != nil { return fmt.Errorf("Error getting IP address: %s", err) } + fmt.Println(ip) + return nil } } diff --git a/commands/commandstest/fake_command_line.go b/commands/commandstest/fake_command_line.go index df11c488d6..4a070b5a19 100644 --- a/commands/commandstest/fake_command_line.go +++ b/commands/commandstest/fake_command_line.go @@ -10,7 +10,7 @@ type FakeFlagger struct { type FakeCommandLine struct { LocalFlags, GlobalFlags *FakeFlagger - HelpShown bool + HelpShown, VersionShown bool CliArgs []string } @@ -91,5 +91,5 @@ func (fcli *FakeCommandLine) Args() cli.Args { } func (fcli *FakeCommandLine) ShowVersion() { - return + fcli.VersionShown = true } diff --git a/commands/ls.go b/commands/ls.go index 71e9ad3ef5..1a30b68f51 100644 --- a/commands/ls.go +++ b/commands/ls.go @@ -33,13 +33,14 @@ type FilterOptions struct { } type HostListItem struct { - Name string - Active bool - DriverName string - State state.State - URL string - SwarmOptions *swarm.Options - Error string + Name string + Active bool + DriverName string + State state.State + URL string + SwarmOptions *swarm.Options + Error string + DockerVersion string } 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) 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 { swarmOptions := host.HostOptions.SwarmOptions @@ -97,8 +98,8 @@ func cmdLs(c CommandLine, api libmachine.API) error { swarmInfo = fmt.Sprintf("%s (master)", swarmInfo) } } - fmt.Fprintf(w, "%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) + 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.DockerVersion, item.Error) } w.Flush() @@ -232,11 +233,21 @@ func matchesName(host *host.Host, names []string) bool { func attemptGetHostState(h *host.Host, stateQueryChan chan<- HostListItem) { url := "" hostError := "" + dockerVersion := "Unknown" currentState, err := h.Driver.GetState() 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 { hostError = err.Error() } @@ -250,13 +261,14 @@ func attemptGetHostState(h *host.Host, stateQueryChan chan<- HostListItem) { } stateQueryChan <- HostListItem{ - Name: h.Name, - Active: isActive(currentState, url), - DriverName: h.Driver.DriverName(), - State: currentState, - URL: url, - SwarmOptions: swarmOptions, - Error: hostError, + Name: h.Name, + Active: isActive(currentState, url), + DriverName: h.Driver.DriverName(), + State: currentState, + URL: url, + SwarmOptions: swarmOptions, + DockerVersion: dockerVersion, + Error: hostError, } } diff --git a/commands/ls_test.go b/commands/ls_test.go index 1ed2bd4451..2b3ffdd5fa 100644 --- a/commands/ls_test.go +++ b/commands/ls_test.go @@ -10,6 +10,7 @@ import ( "github.com/docker/machine/drivers/fakedriver" "github.com/docker/machine/libmachine/host" + "github.com/docker/machine/libmachine/mcndockerclient" "github.com/docker/machine/libmachine/state" "github.com/docker/machine/libmachine/swarm" "github.com/stretchr/testify/assert" @@ -296,6 +297,9 @@ func TestFilterHostsDifferentFlagsProduceAND(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. os.Setenv("DOCKER_HOST", "tcp://active.host.com:2376") @@ -331,14 +335,15 @@ func TestGetHostListItems(t *testing.T) { } expected := []struct { - name string - state state.State - active bool - error string + name string + state state.State + active bool + version string + error string }{ - {"bar10", state.Error, false, "Unable to get ip"}, - {"bar100", state.Stopped, false, ""}, - {"foo", state.Running, true, ""}, + {"bar10", state.Error, false, "Unknown", "Unable to get ip"}, + {"bar100", state.Stopped, false, "Unknown", ""}, + {"foo", state.Running, true, "v1.9", ""}, } 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].state, items[i].State) 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) } @@ -355,6 +361,9 @@ func TestGetHostListItems(t *testing.T) { // issue #1908 func TestGetHostListItemsEnvDockerHostUnset(t *testing.T) { + mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Version: "1.9"} + defer mcndockerclient.CleanupDockerVersioner() + orgDockerHost := os.Getenv("DOCKER_HOST") defer func() { // revert DOCKER_HOST @@ -408,12 +417,13 @@ func TestGetHostListItemsEnvDockerHostUnset(t *testing.T) { } expected := map[string]struct { - state state.State - active bool + state state.State + active bool + version string }{ - "foo": {state.Running, false}, - "bar": {state.Stopped, false}, - "baz": {state.Saved, false}, + "foo": {state.Running, false, "v1.9"}, + "bar": {state.Stopped, false, "Unknown"}, + "baz": {state.Saved, false, "Unknown"}, } 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.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{}) hostItem := hostItems[0] @@ -496,6 +507,9 @@ func TestGetHostStateError(t *testing.T) { } func TestGetSomeHostInEror(t *testing.T) { + mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Version: "1.9"} + defer mcndockerclient.CleanupDockerVersioner() + hosts := []*host.Host{ { Name: "foo", diff --git a/commands/url.go b/commands/url.go index 7847c25d35..e5611f209b 100644 --- a/commands/url.go +++ b/commands/url.go @@ -16,7 +16,7 @@ func cmdURL(c CommandLine, api libmachine.API) error { return err } - url, err := host.GetURL() + url, err := host.URL() if err != nil { return err } diff --git a/commands/version.go b/commands/version.go index 835b511eeb..467af675e4 100644 --- a/commands/version.go +++ b/commands/version.go @@ -1,8 +1,39 @@ 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 { - 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 } diff --git a/commands/version_test.go b/commands/version_test.go new file mode 100644 index 0000000000..9675745d3a --- /dev/null +++ b/commands/version_test.go @@ -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") +} diff --git a/contrib/completion/bash/docker-machine.bash b/contrib/completion/bash/docker-machine.bash index ebbe4bb628..bca335ad64 100644 --- a/contrib/completion/bash/docker-machine.bash +++ b/contrib/completion/bash/docker-machine.bash @@ -185,6 +185,14 @@ _docker_machine_url() { 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() { if [[ "${cur}" == -* ]]; then COMPREPLY=($(compgen -W "--help" -- "${cur}")) diff --git a/docs/install-machine.md b/docs/install-machine.md index a7e2e3f4d6..dc9ed00430 100644 --- a/docs/install-machine.md +++ b/docs/install-machine.md @@ -54,8 +54,8 @@ instructions in the next section. 3. Check the installation by displaying the Machine version: - $ docker-machine -v - machine version 0.5.0 (3e06852) + $ docker-machine version + docker-machine version 0.5.2 (0456b9f) ## Installing bash completion scripts diff --git a/libmachine/cert/cert.go b/libmachine/cert/cert.go index 624cd3396a..b2db6cbf28 100644 --- a/libmachine/cert/cert.go +++ b/libmachine/cert/cert.go @@ -24,6 +24,7 @@ var defaultGenerator = NewX509CertGenerator() type Generator interface { GenerateCACertificate(certFile, keyFile, 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) } @@ -45,6 +46,10 @@ func ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) { return defaultGenerator.ValidateCertificate(addr, authOptions) } +func ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error) { + return defaultGenerator.ReadTLSConfig(addr, authOptions) +} + func SetCertGenerator(cg Generator) { defaultGenerator = cg } @@ -204,8 +209,8 @@ func (xcg *X509CertGenerator) GenerateCert(hosts []string, certFile, keyFile, ca return nil } -// ValidateCertificate validate the certificate installed on the vm. -func (xcg *X509CertGenerator) ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) { +// ReadTLSConfig reads the tls config for a machine. +func (xcg *X509CertGenerator) ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error) { caCertPath := authOptions.CaCertPath serverCertPath := authOptions.ServerCertPath serverKeyPath := authOptions.ServerKeyPath @@ -213,22 +218,27 @@ func (xcg *X509CertGenerator) ValidateCertificate(addr string, authOptions *auth log.Debugf("Reading CA certificate from %s", caCertPath) caCert, err := ioutil.ReadFile(caCertPath) if err != nil { - return false, err + return nil, err } log.Debugf("Reading server certificate from %s", serverCertPath) serverCert, err := ioutil.ReadFile(serverCertPath) if err != nil { - return false, err + return nil, err } log.Debugf("Reading server key from %s", serverKeyPath) serverKey, err := ioutil.ReadFile(serverKeyPath) 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 { return false, err } diff --git a/libmachine/check/check_test.go b/libmachine/check/check_test.go index 08972c6dd2..8054704e42 100644 --- a/libmachine/check/check_test.go +++ b/libmachine/check/check_test.go @@ -4,6 +4,8 @@ import ( "errors" "testing" + "crypto/tls" + "github.com/docker/machine/libmachine/auth" "github.com/docker/machine/libmachine/cert" "github.com/stretchr/testify/assert" @@ -30,6 +32,10 @@ func (fcg FakeCertGenerator) ValidateCertificate(addr string, authOptions *auth. 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) { errCertsExpired := errors.New("Certs have expired") diff --git a/libmachine/host/host.go b/libmachine/host/host.go index c0672affda..1c7d04a8c8 100644 --- a/libmachine/host/host.go +++ b/libmachine/host/host.go @@ -10,6 +10,7 @@ import ( "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/engine" "github.com/docker/machine/libmachine/log" + "github.com/docker/machine/libmachine/mcndockerclient" "github.com/docker/machine/libmachine/mcnutils" "github.com/docker/machine/libmachine/provision" "github.com/docker/machine/libmachine/provision/pkgaction" @@ -145,10 +146,18 @@ func (h *Host) Upgrade() error { return provisioner.Service("docker", serviceaction.Restart) } -func (h *Host) GetURL() (string, error) { +func (h *Host) URL() (string, error) { 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 { provisioner, err := provision.DetectProvisioner(h.Driver) if err != nil { diff --git a/libmachine/mcndockerclient/docker_client.go b/libmachine/mcndockerclient/docker_client.go new file mode 100644 index 0000000000..438bddd6ab --- /dev/null +++ b/libmachine/mcndockerclient/docker_client.go @@ -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) +} diff --git a/libmachine/mcndockerclient/docker_host.go b/libmachine/mcndockerclient/docker_host.go new file mode 100644 index 0000000000..d9b10f3c5f --- /dev/null +++ b/libmachine/mcndockerclient/docker_host.go @@ -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 +} diff --git a/libmachine/mcndockerclient/docker_versioner.go b/libmachine/mcndockerclient/docker_versioner.go new file mode 100644 index 0000000000..b8279c777e --- /dev/null +++ b/libmachine/mcndockerclient/docker_versioner.go @@ -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 +} diff --git a/libmachine/mcndockerclient/fake_docker_versioner.go b/libmachine/mcndockerclient/fake_docker_versioner.go new file mode 100644 index 0000000000..8d30360008 --- /dev/null +++ b/libmachine/mcndockerclient/fake_docker_versioner.go @@ -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 +} diff --git a/libmachine/persist/filestore_test.go b/libmachine/persist/filestore_test.go index 874ecda43b..243d565b0b 100644 --- a/libmachine/persist/filestore_test.go +++ b/libmachine/persist/filestore_test.go @@ -238,7 +238,7 @@ func TestStoreLoad(t *testing.T) { h.Driver = realDriver - actualURL, err := h.GetURL() + actualURL, err := h.URL() if err != nil { t.Fatal(err) } diff --git a/test/integration/core/core-commands.bats b/test/integration/core/core-commands.bats index 9d236f733e..7615ad1274 100644 --- a/test/integration/core/core-commands.bats +++ b/test/integration/core/core-commands.bats @@ -61,6 +61,12 @@ load ${BASE_TEST_DIR}/helpers.bash [[ ${lines[0]} =~ "total" ]] } +@test "$DRIVER: version" { + run machine version $NAME + echo ${output} + [ "$status" -eq 0 ] +} + @test "$DRIVER: docker commands with the socket should work" { run machine ssh $NAME -- sudo docker version echo ${output} @@ -93,6 +99,14 @@ load ${BASE_TEST_DIR}/helpers.bash [[ ${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" { run machine upgrade $NAME echo ${output}