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

View File

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

View File

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

View File

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

View File

@ -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",

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

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
actualURL, err := h.GetURL()
actualURL, err := h.URL()
if err != nil {
t.Fatal(err)
}

View File

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