mirror of https://github.com/docker/docs.git
Adding --filter flag to ls command
Initially supporting `swarm=`, `state=`, and `driver=` filters. Signed-off-by: Dave Henderson <Dave.Henderson@ca.ibm.com>
This commit is contained in:
parent
8ed87c37c2
commit
a3c8b3474e
|
@ -293,6 +293,11 @@ var Commands = []cli.Command{
|
||||||
Name: "quiet, q",
|
Name: "quiet, q",
|
||||||
Usage: "Enable quiet mode",
|
Usage: "Enable quiet mode",
|
||||||
},
|
},
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "filter",
|
||||||
|
Usage: "Filter output based on conditions provided",
|
||||||
|
Value: &cli.StringSlice{},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Name: "ls",
|
Name: "ls",
|
||||||
Usage: "List machines",
|
Usage: "List machines",
|
||||||
|
|
115
commands/ls.go
115
commands/ls.go
|
@ -2,16 +2,28 @@ package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/libmachine"
|
"github.com/docker/machine/libmachine"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FilterOptions -
|
||||||
|
type FilterOptions struct {
|
||||||
|
SwarmName []string
|
||||||
|
DriverName []string
|
||||||
|
State []string
|
||||||
|
}
|
||||||
|
|
||||||
func cmdLs(c *cli.Context) {
|
func cmdLs(c *cli.Context) {
|
||||||
quiet := c.Bool("quiet")
|
quiet := c.Bool("quiet")
|
||||||
|
filters, err := parseFilters(c.StringSlice("filter"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
mcn := getDefaultMcn(c)
|
mcn := getDefaultMcn(c)
|
||||||
hostList, err := mcn.List()
|
hostList, err := mcn.List()
|
||||||
|
@ -19,6 +31,8 @@ func cmdLs(c *cli.Context) {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hostList = filterHosts(hostList, filters)
|
||||||
|
|
||||||
// Just print out the names if we're being quiet
|
// Just print out the names if we're being quiet
|
||||||
if quiet {
|
if quiet {
|
||||||
for _, host := range hostList {
|
for _, host := range hostList {
|
||||||
|
@ -68,3 +82,102 @@ func cmdLs(c *cli.Context) {
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseFilters(filters []string) (FilterOptions, error) {
|
||||||
|
options := FilterOptions{}
|
||||||
|
for _, f := range filters {
|
||||||
|
kv := strings.SplitN(f, "=", 2)
|
||||||
|
key, value := kv[0], kv[1]
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "swarm":
|
||||||
|
options.SwarmName = append(options.SwarmName, value)
|
||||||
|
case "driver":
|
||||||
|
options.DriverName = append(options.DriverName, value)
|
||||||
|
case "state":
|
||||||
|
options.State = append(options.State, value)
|
||||||
|
default:
|
||||||
|
return options, fmt.Errorf("Unsupported filter key '%s'", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterHosts(hosts []*libmachine.Host, filters FilterOptions) []*libmachine.Host {
|
||||||
|
if len(filters.SwarmName) == 0 &&
|
||||||
|
len(filters.DriverName) == 0 &&
|
||||||
|
len(filters.State) == 0 {
|
||||||
|
return hosts
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredHosts := []*libmachine.Host{}
|
||||||
|
swarmMasters := getSwarmMasters(hosts)
|
||||||
|
|
||||||
|
for _, h := range hosts {
|
||||||
|
if filterHost(h, filters, swarmMasters) {
|
||||||
|
filteredHosts = append(filteredHosts, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredHosts
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSwarmMasters(hosts []*libmachine.Host) map[string]string {
|
||||||
|
swarmMasters := make(map[string]string)
|
||||||
|
for _, h := range hosts {
|
||||||
|
swarmOptions := h.HostOptions.SwarmOptions
|
||||||
|
if swarmOptions != nil && swarmOptions.Master {
|
||||||
|
swarmMasters[swarmOptions.Discovery] = h.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return swarmMasters
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterHost(host *libmachine.Host, filters FilterOptions, swarmMasters map[string]string) bool {
|
||||||
|
swarmMatches := matchesSwarmName(host, filters.SwarmName, swarmMasters)
|
||||||
|
driverMatches := matchesDriverName(host, filters.DriverName)
|
||||||
|
stateMatches := matchesState(host, filters.State)
|
||||||
|
|
||||||
|
return swarmMatches && driverMatches && stateMatches
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchesSwarmName(host *libmachine.Host, swarmNames []string, swarmMasters map[string]string) bool {
|
||||||
|
if len(swarmNames) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, n := range swarmNames {
|
||||||
|
if host.HostOptions.SwarmOptions != nil {
|
||||||
|
if n == swarmMasters[host.HostOptions.SwarmOptions.Discovery] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchesDriverName(host *libmachine.Host, driverNames []string) bool {
|
||||||
|
if len(driverNames) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, n := range driverNames {
|
||||||
|
if host.DriverName == n {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchesState(host *libmachine.Host, states []string) bool {
|
||||||
|
if len(states) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, n := range states {
|
||||||
|
s, err := host.Driver.GetState()
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
}
|
||||||
|
if n == s.String() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,232 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/machine/drivers/fakedriver"
|
||||||
|
"github.com/docker/machine/libmachine"
|
||||||
|
"github.com/docker/machine/libmachine/swarm"
|
||||||
|
"github.com/docker/machine/state"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseFiltersErrorsGivenInvalidFilter(t *testing.T) {
|
||||||
|
_, err := parseFilters([]string{"foo=bar"})
|
||||||
|
assert.EqualError(t, err, "Unsupported filter key 'foo'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFiltersSwarm(t *testing.T) {
|
||||||
|
actual, _ := parseFilters([]string{"swarm=foo"})
|
||||||
|
assert.Equal(t, actual, FilterOptions{SwarmName: []string{"foo"}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFiltersDriver(t *testing.T) {
|
||||||
|
actual, _ := parseFilters([]string{"driver=bar"})
|
||||||
|
assert.Equal(t, actual, FilterOptions{DriverName: []string{"bar"}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFiltersState(t *testing.T) {
|
||||||
|
actual, _ := parseFilters([]string{"state=Running"})
|
||||||
|
assert.Equal(t, actual, FilterOptions{State: []string{"Running"}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFiltersAll(t *testing.T) {
|
||||||
|
actual, _ := parseFilters([]string{"swarm=foo", "driver=bar", "state=Stopped"})
|
||||||
|
assert.Equal(t, actual, FilterOptions{SwarmName: []string{"foo"}, DriverName: []string{"bar"}, State: []string{"Stopped"}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFiltersDuplicates(t *testing.T) {
|
||||||
|
actual, _ := parseFilters([]string{"swarm=foo", "driver=bar", "swarm=baz", "driver=qux", "state=Running", "state=Starting"})
|
||||||
|
assert.Equal(t, actual, FilterOptions{SwarmName: []string{"foo", "baz"}, DriverName: []string{"bar", "qux"}, State: []string{"Running", "Starting"}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseFiltersValueWithEqual(t *testing.T) {
|
||||||
|
actual, _ := parseFilters([]string{"driver=bar=baz"})
|
||||||
|
assert.Equal(t, actual, FilterOptions{DriverName: []string{"bar=baz"}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterHostsReturnsSameGivenNoFilters(t *testing.T) {
|
||||||
|
opts := FilterOptions{}
|
||||||
|
hosts := []*libmachine.Host{
|
||||||
|
{
|
||||||
|
Name: "testhost",
|
||||||
|
DriverName: "fakedriver",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actual := filterHosts(hosts, opts)
|
||||||
|
assert.EqualValues(t, actual, hosts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterHostsReturnsEmptyGivenEmptyHosts(t *testing.T) {
|
||||||
|
opts := FilterOptions{
|
||||||
|
SwarmName: []string{"foo"},
|
||||||
|
}
|
||||||
|
hosts := []*libmachine.Host{}
|
||||||
|
assert.Empty(t, filterHosts(hosts, opts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterHostsReturnsEmptyGivenNonMatchingFilters(t *testing.T) {
|
||||||
|
opts := FilterOptions{
|
||||||
|
SwarmName: []string{"foo"},
|
||||||
|
}
|
||||||
|
hosts := []*libmachine.Host{
|
||||||
|
{
|
||||||
|
Name: "testhost",
|
||||||
|
DriverName: "fakedriver",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Empty(t, filterHosts(hosts, opts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterHostsBySwarmName(t *testing.T) {
|
||||||
|
opts := FilterOptions{
|
||||||
|
SwarmName: []string{"master"},
|
||||||
|
}
|
||||||
|
master :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "master",
|
||||||
|
HostOptions: &libmachine.HostOptions{
|
||||||
|
SwarmOptions: &swarm.SwarmOptions{Master: true, Discovery: "foo"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
node1 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node1",
|
||||||
|
HostOptions: &libmachine.HostOptions{
|
||||||
|
SwarmOptions: &swarm.SwarmOptions{Master: false, Discovery: "foo"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
othermaster :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "othermaster",
|
||||||
|
HostOptions: &libmachine.HostOptions{
|
||||||
|
SwarmOptions: &swarm.SwarmOptions{Master: true, Discovery: "bar"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
hosts := []*libmachine.Host{master, node1, othermaster}
|
||||||
|
expected := []*libmachine.Host{master, node1}
|
||||||
|
|
||||||
|
assert.EqualValues(t, filterHosts(hosts, opts), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterHostsByDriverName(t *testing.T) {
|
||||||
|
opts := FilterOptions{
|
||||||
|
DriverName: []string{"fakedriver"},
|
||||||
|
}
|
||||||
|
node1 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node1",
|
||||||
|
DriverName: "fakedriver",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
}
|
||||||
|
node2 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node2",
|
||||||
|
DriverName: "virtualbox",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
}
|
||||||
|
node3 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node3",
|
||||||
|
DriverName: "fakedriver",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
}
|
||||||
|
hosts := []*libmachine.Host{node1, node2, node3}
|
||||||
|
expected := []*libmachine.Host{node1, node3}
|
||||||
|
|
||||||
|
assert.EqualValues(t, filterHosts(hosts, opts), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterHostsByState(t *testing.T) {
|
||||||
|
opts := FilterOptions{
|
||||||
|
State: []string{"Paused", "Saved", "Stopped"},
|
||||||
|
}
|
||||||
|
node1 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node1",
|
||||||
|
DriverName: "fakedriver",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
Driver: &fakedriver.FakeDriver{MockState: state.Paused},
|
||||||
|
}
|
||||||
|
node2 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node2",
|
||||||
|
DriverName: "virtualbox",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
Driver: &fakedriver.FakeDriver{MockState: state.Stopped},
|
||||||
|
}
|
||||||
|
node3 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node3",
|
||||||
|
DriverName: "fakedriver",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
Driver: &fakedriver.FakeDriver{MockState: state.Running},
|
||||||
|
}
|
||||||
|
hosts := []*libmachine.Host{node1, node2, node3}
|
||||||
|
expected := []*libmachine.Host{node1, node2}
|
||||||
|
|
||||||
|
assert.EqualValues(t, filterHosts(hosts, opts), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterHostsMultiFlags(t *testing.T) {
|
||||||
|
opts := FilterOptions{
|
||||||
|
SwarmName: []string{},
|
||||||
|
DriverName: []string{"fakedriver", "virtualbox"},
|
||||||
|
}
|
||||||
|
node1 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node1",
|
||||||
|
DriverName: "fakedriver",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
}
|
||||||
|
node2 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node2",
|
||||||
|
DriverName: "virtualbox",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
}
|
||||||
|
node3 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node3",
|
||||||
|
DriverName: "softlayer",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
}
|
||||||
|
hosts := []*libmachine.Host{node1, node2, node3}
|
||||||
|
expected := []*libmachine.Host{node1, node2}
|
||||||
|
|
||||||
|
assert.EqualValues(t, filterHosts(hosts, opts), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterHostsDifferentFlagsProduceAND(t *testing.T) {
|
||||||
|
opts := FilterOptions{
|
||||||
|
DriverName: []string{"virtualbox"},
|
||||||
|
State: []string{"Running"},
|
||||||
|
}
|
||||||
|
node1 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node1",
|
||||||
|
DriverName: "fakedriver",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
Driver: &fakedriver.FakeDriver{MockState: state.Paused},
|
||||||
|
}
|
||||||
|
node2 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node2",
|
||||||
|
DriverName: "virtualbox",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
Driver: &fakedriver.FakeDriver{MockState: state.Stopped},
|
||||||
|
}
|
||||||
|
node3 :=
|
||||||
|
&libmachine.Host{
|
||||||
|
Name: "node3",
|
||||||
|
DriverName: "fakedriver",
|
||||||
|
HostOptions: &libmachine.HostOptions{},
|
||||||
|
Driver: &fakedriver.FakeDriver{MockState: state.Running},
|
||||||
|
}
|
||||||
|
hosts := []*libmachine.Host{node1, node2, node3}
|
||||||
|
expected := []*libmachine.Host{}
|
||||||
|
|
||||||
|
assert.EqualValues(t, filterHosts(hosts, opts), expected)
|
||||||
|
}
|
||||||
|
|
|
@ -784,7 +784,29 @@ dev * virtualbox Stopped
|
||||||
|
|
||||||
#### ls
|
#### ls
|
||||||
|
|
||||||
List machines.
|
```
|
||||||
|
Usage: docker-machine ls [OPTIONS] [arg...]
|
||||||
|
|
||||||
|
List machines
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
--quiet, -q Enable quiet mode
|
||||||
|
--filter [--filter option --filter option] Filter output based on conditions provided
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Filtering
|
||||||
|
|
||||||
|
The filtering flag (`-f` or `--filter)` format is a `key=value` pair. If there is more
|
||||||
|
than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`)
|
||||||
|
|
||||||
|
The currently supported filters are:
|
||||||
|
|
||||||
|
* driver (driver name)
|
||||||
|
* swarm (swarm master's name)
|
||||||
|
* state (`Running|Paused|Saved|Stopped|Stopping|Starting|Error`)
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
```
|
```
|
||||||
$ docker-machine ls
|
$ docker-machine ls
|
||||||
|
@ -792,9 +814,13 @@ NAME ACTIVE DRIVER STATE URL
|
||||||
dev virtualbox Stopped
|
dev virtualbox Stopped
|
||||||
foo0 virtualbox Running tcp://192.168.99.105:2376
|
foo0 virtualbox Running tcp://192.168.99.105:2376
|
||||||
foo1 virtualbox Running tcp://192.168.99.106:2376
|
foo1 virtualbox Running tcp://192.168.99.106:2376
|
||||||
foo2 virtualbox Running tcp://192.168.99.107:2376
|
foo2 * virtualbox Running tcp://192.168.99.107:2376
|
||||||
foo3 virtualbox Running tcp://192.168.99.108:2376
|
```
|
||||||
foo4 * virtualbox Running tcp://192.168.99.109:2376
|
|
||||||
|
```
|
||||||
|
$ docker-machine ls --filter driver=virtualbox --filter state=Stopped
|
||||||
|
NAME ACTIVE DRIVER STATE URL SWARM
|
||||||
|
dev virtualbox Stopped
|
||||||
```
|
```
|
||||||
|
|
||||||
#### regenerate-certs
|
#### regenerate-certs
|
||||||
|
@ -1030,7 +1056,7 @@ Options:
|
||||||
- `--google-scopes`: The scopes for OAuth 2.0 to Access Google APIs. See [Google Compute Engine Doc](https://cloud.google.com/storage/docs/authentication).
|
- `--google-scopes`: The scopes for OAuth 2.0 to Access Google APIs. See [Google Compute Engine Doc](https://cloud.google.com/storage/docs/authentication).
|
||||||
- `--google-disk-size`: The disk size of instance. Default: `10`
|
- `--google-disk-size`: The disk size of instance. Default: `10`
|
||||||
- `--google-disk-type`: The disk type of instance. Default: `pd-standard`
|
- `--google-disk-type`: The disk type of instance. Default: `pd-standard`
|
||||||
|
|
||||||
The GCE driver will use the `ubuntu-1404-trusty-v20150316` instance type unless otherwise specified.
|
The GCE driver will use the `ubuntu-1404-trusty-v20150316` instance type unless otherwise specified.
|
||||||
|
|
||||||
#### IBM Softlayer
|
#### IBM Softlayer
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
teardown() {
|
||||||
|
echo "$BATS_TEST_NAME
|
||||||
|
----------
|
||||||
|
$output
|
||||||
|
----------
|
||||||
|
|
||||||
|
" >> ${BATS_LOG}
|
||||||
|
machine rm -f testmachine
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ls: filter on driver" {
|
||||||
|
run machine create -d none --url tcp://127.0.0.1:2375 testmachine
|
||||||
|
run machine ls --filter driver=none
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ ${lines[1]} =~ "testmachine" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ls: filter on swarm" {
|
||||||
|
run machine create -d none --url tcp://127.0.0.1:2375 --swarm --swarm-master --swarm-discovery token://deadbeef testmachine
|
||||||
|
run machine ls --filter swarm=testmachine
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ ${lines[1]} =~ "testmachine" ]]
|
||||||
|
}
|
Loading…
Reference in New Issue