Skip to content

Commit 55a6356

Browse files
committed
ovnmon: list DB tables
The list command has one subcommand per table Each subcommand has an argument completer based on the possible fields. That way we can limit the number of fields we show Also, paging is supported out of the box Signed-off-by: Adrian Moreno <[email protected]>
1 parent 55b5fe4 commit 55a6356

File tree

5 files changed

+184
-5
lines changed

5 files changed

+184
-5
lines changed

cmd/ovnmon/ovnmon.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"flag"
5+
"fmt"
56
"log"
67
"os"
78

@@ -23,6 +24,14 @@ var (
2324
type ormSignal struct{}
2425

2526
func main() {
27+
flag.Usage = func() {
28+
fmt.Fprintf(os.Stderr, "%s [FLAGS] [COMMAND] \n", os.Args[0])
29+
fmt.Fprintf(os.Stderr, "FLAGS:\n")
30+
flag.PrintDefaults()
31+
fmt.Fprintf(os.Stderr, "COMMAND:\n")
32+
fmt.Fprintf(os.Stderr, "\tIf provided, it will run the command and exit. If not, it will enter interactive mode\n")
33+
fmt.Fprintf(os.Stderr, "\tFor a full description of available commands use the command 'help'")
34+
}
2635
flag.Parse()
2736

2837
var addr string
@@ -41,7 +50,7 @@ func main() {
4150
log.Fatal(err)
4251
}
4352

44-
shell := newOvnShell(*auto)
53+
shell := newOvnShell(*auto, dbModel)
4554
config := goovn.Config{
4655
Db: goovn.DBNB,
4756
Addr: addr,
@@ -53,5 +62,5 @@ func main() {
5362
panic(err)
5463
}
5564
defer orm.Close()
56-
shell.Run(orm)
65+
shell.Run(orm, flag.Args()...)
5766
}

cmd/ovnmon/shell.go

+82-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package main
22

33
import (
4+
"bytes"
5+
"fmt"
6+
"reflect"
7+
"strings"
48
"sync"
59

610
goovn "github.com/ebay/go-ovn"
@@ -12,6 +16,7 @@ type OvnShell struct {
1216
mutex *sync.RWMutex
1317
monitor bool
1418
orm goovn.ORMClient
19+
dbModel *goovn.DBModel
1520
}
1621

1722
func (s *OvnShell) Monitor(monitor bool) {
@@ -40,7 +45,7 @@ func (s *OvnShell) OnDeleted(m goovn.Model) {
4045
}
4146
}
4247

43-
func (s *OvnShell) Run(orm goovn.ORMClient) {
48+
func (s *OvnShell) Run(orm goovn.ORMClient, args ...string) {
4449
s.orm = orm
4550
shell := ishell.New()
4651
shell.Set("ovnShell", s)
@@ -85,12 +90,87 @@ func (s *OvnShell) Run(orm goovn.ORMClient) {
8590
}
8691
},
8792
})
93+
94+
// List Command
95+
// Add a subcommand per table
96+
listCmd := ishell.Cmd{
97+
Name: "list",
98+
Help: "List the content of a specific table",
99+
}
100+
101+
// To generate the completer for each subcommand we store all the possible fields per table
102+
tableFields := make(map[string][]string)
103+
for tname, mtype := range s.dbModel.GetTypes() {
104+
fields := []string{}
105+
for i := 0; i < mtype.Elem().NumField(); i++ {
106+
fields = append(fields, mtype.Elem().Field(i).Name)
107+
}
108+
tableFields[tname] = fields
109+
}
110+
111+
for name := range s.dbModel.GetTypes() {
112+
// Trick to be able to use name inside closures
113+
tableName := name
114+
subTableCmd := ishell.Cmd{
115+
Name: name,
116+
Help: fmt.Sprintf("%s [Field1 Field2 ...]", name),
117+
LongHelp: fmt.Sprintf(
118+
"List the content of Table %s", name) +
119+
fmt.Sprintf("\n\n%s [Field1 Field2 ...]", name) +
120+
"\n\t[Field1 Field2 ...]: List of fields to show (default: all fields will be shown)" +
121+
fmt.Sprintf("\n\t\tPossible Fields: %s", strings.Join(tableFields[name], ", ")),
122+
Func: func(c *ishell.Context) {
123+
ovnShell := c.Get("ovnShell")
124+
if ovnShell == nil {
125+
c.Println("Error: No context")
126+
}
127+
// Use a buffer to store the generated output table
128+
buffer := bytes.Buffer{}
129+
mtype := ovnShell.(*OvnShell).dbModel.GetTypes()[c.Cmd.Name]
130+
printer, err := NewStructPrinter(&buffer, mtype.Elem(), c.Args...)
131+
if err != nil {
132+
c.Println(err)
133+
return
134+
}
135+
136+
// call ORM.List()
137+
valueList := reflect.New(reflect.SliceOf(mtype.Elem()))
138+
orm := ovnShell.(*OvnShell).orm
139+
err = orm.List(valueList.Interface())
140+
if err != nil {
141+
if err == goovn.ErrorNotFound {
142+
return
143+
}
144+
c.Println(err)
145+
return
146+
}
147+
148+
// Render the result table
149+
printer.Append(reflect.Indirect(valueList).Interface())
150+
printer.Render()
151+
// Print the result table through shell so it can be paged
152+
c.ShowPaged(buffer.String())
153+
},
154+
Completer: func(args []string) []string {
155+
return tableFields[tableName]
156+
},
157+
}
158+
listCmd.AddCmd(&subTableCmd)
159+
}
160+
shell.AddCmd(&listCmd)
161+
162+
// If we have arguments, just run them and exit
163+
if len(args) > 0 {
164+
shell.Process(args...)
165+
return
166+
}
88167
shell.Run()
89168
}
90169

91-
func newOvnShell(auto bool) *OvnShell {
170+
func newOvnShell(auto bool, dbmodel *goovn.DBModel) *OvnShell {
92171
return &OvnShell{
93172
mutex: new(sync.RWMutex),
94173
monitor: auto,
174+
dbModel: dbmodel,
95175
}
96176
}

cmd/ovnmon/tables.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"reflect"
7+
8+
"github.com/olekukonko/tablewriter"
9+
)
10+
11+
// StructPrinter is a wrapper around tablewriter that
12+
// uses the struct's field names as columns
13+
type StructPrinter struct {
14+
cols []string
15+
table *tablewriter.Table
16+
}
17+
18+
// Append adds a list of objects to be printed
19+
func (sp *StructPrinter) Append(objList interface{}) error {
20+
objListVal := reflect.ValueOf(objList)
21+
if objListVal.Type().Kind() != reflect.Slice {
22+
return fmt.Errorf("Append expects a slice of objects. Instead go %s", objListVal.Type())
23+
}
24+
25+
data := make([][]string, 0, objListVal.Len())
26+
for i := 0; i < objListVal.Len(); i++ {
27+
objVal := objListVal.Index(i)
28+
objData := make([]string, 0, len(sp.cols))
29+
for _, col := range sp.cols {
30+
field := objVal.FieldByName(col)
31+
if !field.IsValid() {
32+
continue
33+
}
34+
objData = append(objData, fmt.Sprintf("%v", field.Interface()))
35+
}
36+
data = append(data, objData)
37+
}
38+
sp.table.AppendBulk(data)
39+
return nil
40+
}
41+
42+
// Render prints the table content
43+
func (sp *StructPrinter) Render() {
44+
sp.table.Render()
45+
}
46+
47+
// NewStructPrinter generates a StructPrinter based on a Writer for a given reflect.Type
48+
// Optionally, a list of field selectors can be given. If so, only those columns
49+
// will be printed.
50+
func NewStructPrinter(writer io.Writer, stype reflect.Type, fieldSel ...string) (*StructPrinter, error) {
51+
var cols []string
52+
table := tablewriter.NewWriter(writer)
53+
54+
if len(fieldSel) > 0 {
55+
for _, sel := range fieldSel {
56+
found := false
57+
for i := 0; i < stype.NumField(); i++ {
58+
if stype.Field(i).Name == sel {
59+
found = true
60+
break
61+
}
62+
}
63+
if !found {
64+
return nil, fmt.Errorf("Field %s not found in Type %s", sel, stype.Name())
65+
}
66+
}
67+
cols = fieldSel
68+
cols = fieldSel
69+
} else {
70+
for i := 0; i < stype.NumField(); i++ {
71+
field := stype.Field(i).Name
72+
cols = append(cols, field)
73+
}
74+
}
75+
table.SetHeader(cols)
76+
return &StructPrinter{
77+
cols: cols,
78+
table: table,
79+
}, nil
80+
}

go.mod

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ module github.com/amorenoz/ovnmodel
33
go 1.15
44

55
require (
6+
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
67
github.com/cenk/hub v1.0.1 // indirect
78
github.com/ebay/go-ovn v0.0.0-00010101000000-000000000000
89
github.com/ebay/libovsdb v0.2.0
910
github.com/fatih/color v1.10.0
11+
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
1012
github.com/k0kubun/pp v2.4.0+incompatible
13+
github.com/olekukonko/tablewriter v0.0.5
14+
gopkg.in/abiosoft/ishell.v2 v2.0.0
1115
)
1216

1317
replace (
14-
github.com/ebay/go-ovn => github.com/amorenoz/go-ovn v0.1.1-0.20210305093751-39f4443b448d
18+
github.com/ebay/go-ovn => github.com/amorenoz/go-ovn v0.1.1-0.20210309183059-6707d7ad0208
1519
github.com/ebay/libovsdb => github.com/amorenoz/libovsdb v0.2.1-0.20210305093614-80f588f3e2db
1620
)

go.sum

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/amorenoz/go-ovn v0.1.1-0.20210305093751-39f4443b448d h1:p7AjjLYdIZSQ+
88
github.com/amorenoz/go-ovn v0.1.1-0.20210305093751-39f4443b448d/go.mod h1:ozqnCmbSPuoGRJvZxDEOVAXd/F/Lyr27976cmLL9wl8=
99
github.com/amorenoz/go-ovn v0.1.1-0.20210309175803-fc72470a0885 h1:3FssF6nKW0245obz/fP5+s94EBZFe9abi7/DJkrSVKU=
1010
github.com/amorenoz/go-ovn v0.1.1-0.20210309175803-fc72470a0885/go.mod h1:ozqnCmbSPuoGRJvZxDEOVAXd/F/Lyr27976cmLL9wl8=
11+
github.com/amorenoz/go-ovn v0.1.1-0.20210309183059-6707d7ad0208 h1:jxI537Jk7qoxd7y3ae6JvLuSF1YUpHv1O6vWUwTF5/o=
12+
github.com/amorenoz/go-ovn v0.1.1-0.20210309183059-6707d7ad0208/go.mod h1:ozqnCmbSPuoGRJvZxDEOVAXd/F/Lyr27976cmLL9wl8=
1113
github.com/amorenoz/libovsdb v0.2.1-0.20210223160324-916daff1677b h1:48XQjWRAc+C1vS5dab7fJfAHlhdGjZFVfLiyAbhBJS8=
1214
github.com/amorenoz/libovsdb v0.2.1-0.20210223160324-916daff1677b/go.mod h1:PTzqmBaVTOgGqKH05sIO2B6tauEwb5X000PvIKwLGd4=
1315
github.com/amorenoz/libovsdb v0.2.1-0.20210305093614-80f588f3e2db h1:7ilcb1RL51pKspczGOknPjeRmAJpbbDqBjOIOE/fqqw=
@@ -32,6 +34,10 @@ github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ
3234
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
3335
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
3436
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
37+
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
38+
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
39+
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
40+
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
3541
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3642
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
3743
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

0 commit comments

Comments
 (0)