diff --git a/commands/commands.go b/commands/commands.go index e35d164ce9..d735c4eeb4 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -214,6 +214,9 @@ var Commands = []cli.Command{ Action: fatalOnError(cmdKill), }, { + Name: "ls", + Usage: "List machines", + Action: fatalOnError(cmdLs), Flags: []cli.Flag{ cli.BoolFlag{ Name: "quiet, q", @@ -229,10 +232,11 @@ var Commands = []cli.Command{ Usage: fmt.Sprintf("Timeout in seconds, default to %s", stateTimeoutDuration), Value: lsDefaultTimeout, }, + cli.StringFlag{ + Name: "format, f", + Usage: "Pretty-print machines using a Go template", + }, }, - Name: "ls", - Usage: "List machines", - Action: fatalOnError(cmdLs), }, { Name: "regenerate-certs", diff --git a/commands/ls.go b/commands/ls.go index cf4b2dde7f..25559bf582 100644 --- a/commands/ls.go +++ b/commands/ls.go @@ -11,6 +11,8 @@ import ( "text/template" "time" + "io" + "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/engine" @@ -23,11 +25,14 @@ import ( "github.com/skarademir/naturalsort" ) -const lsDefaultTimeout = 10 +const ( + lsDefaultTimeout = 10 + tableFormatKey = "table" + lsDefaultFormat = "table {{ .Name }}\t{{ .Active }}\t{{ .DriverName}}\t{{ .State }}\t{{ .URL }}\t{{ .Swarm }}\t{{ .DockerVersion }}\t{{ .Error}}" +) var ( stateTimeoutDuration = lsDefaultTimeout * time.Second - defaultLsTemplate = "{{ .Name }}\t{{ .Active }}\t{{ .DriverName}}\t{{ .State }}\t{{ .URL }}\t{{ .Swarm }}\t{{ .DockerVersion }}\t{{ .Error}}" ) // FilterOptions - @@ -54,11 +59,25 @@ type HostListItem struct { DockerVersion string } +type Headers struct { + Name string + Active string + ActiveHost string + ActiveSwarm string + DriverName string + State string + URL string + SwarmOptions string + Swarm string + EngineOptions string + Error string + DockerVersion string +} + func cmdLs(c CommandLine, api libmachine.API) error { stateTimeoutDuration = time.Duration(c.Int("timeout")) * time.Second log.Debugf("ls timeout set to %s", stateTimeoutDuration) - quiet := c.Bool("quiet") filters, err := parseFilters(c.StringSlice("filter")) if err != nil { return err @@ -72,27 +91,52 @@ func cmdLs(c CommandLine, api libmachine.API) error { hostList = filterHosts(hostList, filters) // Just print out the names if we're being quiet - if quiet { + if c.Bool("quiet") { for _, host := range hostList { fmt.Println(host.Name) } return nil } - t := template.New("lsConfig") - template, err := t.Parse(defaultLsTemplate + "\n") + template, table, err := parseFormat(c.String("format")) if err != nil { return err } + var w io.Writer + if table { + tabWriter := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) + defer tabWriter.Flush() + + w = tabWriter + + headers := &Headers{ + Name: "NAME", + Active: "ACTIVE", + ActiveHost: "DRIVER", + ActiveSwarm: "STATE", + DriverName: "URL", + State: "STATE", + URL: "URL", + SwarmOptions: "SWARM_OPTIONS", + Swarm: "SWARM", + EngineOptions: "ENGINE_OPTIONS", + Error: "ERRORS", + DockerVersion: "DOCKER", + } + + if err := template.Execute(w, headers); err != nil { + return err + } + } else { + w = os.Stdout + } + + items := getHostListItems(hostList, hostInError) + swarmMasters := make(map[string]string) swarmInfo := make(map[string]string) - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) - defer w.Flush() - - fmt.Fprintln(w, "NAME\tACTIVE\tDRIVER\tSTATE\tURL\tSWARM\tDOCKER\tERRORS") - for _, host := range hostList { if host.HostOptions != nil { swarmOptions := host.HostOptions.SwarmOptions @@ -106,7 +150,6 @@ func cmdLs(c CommandLine, api libmachine.API) error { } } - items := getHostListItems(hostList, hostInError) for _, item := range items { swarmColumn := "" if item.SwarmOptions != nil && item.SwarmOptions.Discovery != "" { @@ -125,6 +168,31 @@ func cmdLs(c CommandLine, api libmachine.API) error { return nil } +func parseFormat(format string) (*template.Template, bool, error) { + table := false + finalFormat := format + + if finalFormat == "" { + finalFormat = lsDefaultFormat + } + + if strings.HasPrefix(finalFormat, tableFormatKey) { + table = true + finalFormat = finalFormat[len(tableFormatKey):] + } + + finalFormat = strings.Trim(finalFormat, " ") + r := strings.NewReplacer(`\t`, "\t", `\n`, "\n") + finalFormat = r.Replace(finalFormat) + + template, err := template.New("").Parse(finalFormat + "\n") + if err != nil { + return nil, false, err + } + + return template, table, nil +} + func parseFilters(filters []string) (FilterOptions, error) { options := FilterOptions{} for _, f := range filters { diff --git a/contrib/completion/bash/docker-machine.bash b/contrib/completion/bash/docker-machine.bash index 70a3f4e216..f37b11c84f 100644 --- a/contrib/completion/bash/docker-machine.bash +++ b/contrib/completion/bash/docker-machine.bash @@ -96,7 +96,7 @@ _docker_machine_ls() { COMPREPLY=() ;; *) - COMPREPLY=($(compgen -W "--quiet --filter --timeout --help" -- "${cur}")) + COMPREPLY=($(compgen -W "--quiet --filter --format --timeout --help" -- "${cur}")) ;; esac } diff --git a/docs/reference/ls.md b/docs/reference/ls.md index 1b2a60e58c..a405b43dc2 100644 --- a/docs/reference/ls.md +++ b/docs/reference/ls.md @@ -16,9 +16,10 @@ parent="smn_machine_subcmds" Options: - --quiet, -q Enable quiet mode - --filter [--filter option --filter option] Filter output based on conditions provided - --timeout, -t Timeout in seconds, default to 10s + --quiet, -q Enable quiet mode + --filter [--filter option --filter option] Filter output based on conditions provided + --timeout, -t Timeout in seconds, default to 10s + --format, -f Pretty-print machines using a Go template ## Timeout @@ -47,6 +48,42 @@ The currently supported filters are: - name (Machine name returned by driver, supports [golang style](https://github.com/google/re2/wiki/Syntax) regular expressions) - label (Machine created with `--engine-label` option, can be filtered with `label=[=]`) +## Formatting + +The formatting option (`--format`) will pretty-print machines using a Go template. + +Valid placeholders for the Go template are listed below: + +| Placeholder | Description | +| -------------- | ---------------------------------------- | +| .Name | Machine name | +| .Active | Is the machine active? | +| .ActiveHost | Is the machine an active non-swarm host? | +| .ActiveSwarm | Is the machine an active swarm master? | +| .DriverName | Driver name | +| .State | Machine state (running, stopped...) | +| .URL | Machine URL | +| .Swarm | Machine swarm name | +| .Error | Machine errors | +| .DockerVersion | Docker Daemon version | + +When using the `--format` option, the `ls` command will either output the data exactly as the template declares or, +when using the table directive, will include column headers as well. + +The following example uses a template without headers and outputs the `Name` and `Driver` entries separated by a colon +for all running machines: + + $ docker-machine ls --format "{{.Name}}: {{.DriverName}}" + default: virtualbox + ec2: amazonec2 + +To list all machine names with their driver in a table format you can use: + + $ docker-machine ls --format "table {{.Name}}: {{.DriverName}}" + NAME DRIVER + default virtualbox + ec2 amazonec2 + ## Examples $ docker-machine ls diff --git a/test/integration/cli/ls.bats b/test/integration/cli/ls.bats index a57c952078..482deb2653 100644 --- a/test/integration/cli/ls.bats +++ b/test/integration/cli/ls.bats @@ -165,3 +165,27 @@ bootstrap_swarm () { [[ ${lines[1]} == "testswarm2" ]] [[ ${lines[2]} == "testswarm3" ]] } + +@test "ls: format on driver 'machine ls --format '{{ .DriverName }}'" { + run machine ls --format '{{ .DriverName }}' + [ "$status" -eq 0 ] + [[ ${#lines[@]} == 5 ]] + [[ ${lines[0]} =~ "none" ]] + [[ ${lines[1]} =~ "none" ]] + [[ ${lines[2]} =~ "none" ]] + [[ ${lines[3]} =~ "none" ]] + [[ ${lines[4]} =~ "none" ]] +} + + +@test "ls: format on name and driver 'machine ls --format 'table {{ .Name}}: {{ .DriverName }}'" { + run machine ls --format 'table {{ .Name}}: {{ .DriverName }}' + [ "$status" -eq 0 ] + [[ ${#lines[@]} == 6 ]] + [[ ${lines[1]} =~ "testmachine: none" ]] + [[ ${lines[2]} =~ "testmachine2: none" ]] + [[ ${lines[3]} =~ "testmachine3: none" ]] + [[ ${lines[4]} =~ "testmachine4: none" ]] + [[ ${lines[5]} =~ "testmachine5: none" ]] +} +