Skip to content

Commit

Permalink
implement filter function in ps
Browse files Browse the repository at this point in the history
  • Loading branch information
mhewedy committed May 27, 2020
1 parent 7d2bfe7 commit d0bf3f8
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 15 deletions.
5 changes: 4 additions & 1 deletion cmd/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ var psCmd = &cobra.Command{
Short: "List VMs",
Long: `List VMs
Use the -a|--all flag to list all VMs
Use the -f|--filter flag to filter VMs based on the name, image and tags (contains and AND filter)
`,
Run: func(cmd *cobra.Command, args []string) {

all, _ := cmd.Flags().GetBool("all")
filters, _ := cmd.Flags().GetStringArray("filter")

ps, err := vms.Ps(all)
ps, err := vms.Ps(all, filters)
if err != nil {
fmt.Println(err)
os.Exit(1)
Expand All @@ -55,4 +57,5 @@ func init() {
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
psCmd.Flags().BoolP("all", "a", false, "List all VMs")
psCmd.Flags().StringArrayP("filter", "f", []string{}, "Filter VMs")
}
92 changes: 78 additions & 14 deletions vms/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/mhewedy/vermin/command"
"github.com/mhewedy/vermin/db"
"os"
"reflect"
"sort"
"strings"
"sync"
Expand All @@ -15,19 +16,21 @@ var (
header = fmt.Sprintf(format, "VM NAME", "IMAGE", "CPUS", "MEM", "DISK", "TAGS")
)

// --- vmInfo types

type vmInfo struct {
name string
image string
box *db.Box
box db.Box
disk string
tags string
}

func (v *vmInfo) String() string {
func (v vmInfo) String() string {
return fmt.Sprintf(format, v.name, v.image, v.box.CPU, v.box.Mem, v.disk, v.tags)
}

type vmInfoList []*vmInfo
type vmInfoList []vmInfo

func (l vmInfoList) String() string {
var out string
Expand All @@ -43,12 +46,72 @@ func (l vmInfoList) String() string {
return out
}

func Ps(all bool) (string, error) {
// --- filter types

type filter struct {
name string
value string
}

func (f filter) apply(list vmInfoList) vmInfoList {
filtered := make(vmInfoList, 0)
// fields should match name of fields from struct vmInfo
fields := []string{"name", "image", "tags"}

for _, e := range list {
rv := reflect.ValueOf(e)

for _, field := range fields {
if f.name == field && strings.Contains(rv.FieldByName(field).String(), f.value) {
filtered = append(filtered, e)
}
}
}
return filtered
}

type filters []filter

func (f filters) apply(list vmInfoList) vmInfoList {
if len(f) == 0 {
return list
}
for _, filter := range f {
list = filter.apply(list)
}
return list
}

// --- package functions

func Ps(all bool, f []string) (string, error) {
filters, err := parseFilters(f)
if err != nil {
return "", err
}

vms, err := List(all)
if err != nil {
return "", err
}
return getVMInfoList(vms), nil
return getVMInfoList(vms, filters), nil
}

func parseFilters(filters []string) ([]filter, error) {
if len(filters) == 0 {
return nil, nil
}

var out = make([]filter, len(filters))

for i, f := range filters {
parts := strings.Split(f, "=")
if len(parts) != 2 || len(parts[1]) == 0 {
return nil, fmt.Errorf("Failed to parse fitler: %s.\n", f)
}
out[i] = filter{name: parts[0], value: parts[1]}
}
return out, nil
}

// List return all vms that start with db.VMNamePrefix
Expand Down Expand Up @@ -80,16 +143,15 @@ func List(all bool) ([]string, error) {
return vms, nil
}

func getVMInfoList(vms []string) string {
func getVMInfoList(vms []string, filters filters) string {

numVms := len(vms)
if numVms == 0 {
if len(vms) == 0 {
return header
}

infoList := make(vmInfoList, numVms)
infoList := make(vmInfoList, len(vms))
var wg sync.WaitGroup
wg.Add(numVms)
wg.Add(len(vms))

for i, vmName := range vms {
go func(vm string, i int) {
Expand All @@ -99,23 +161,25 @@ func getVMInfoList(vms []string) string {
}
wg.Wait()

infoList = filters.apply(infoList)

return infoList.String()
}

func getVMInfo(vm string) *vmInfo {
func getVMInfo(vm string) vmInfo {
if _, err := os.Stat(db.GetVMPath(vm)); os.IsNotExist(err) {
return nil
return vmInfo{}
}

box, _ := db.GetBoxInfo(vm)
disk := getDiskSizeInGB(vm, box.HDLocation)
image, _ := db.ReadImageData(vm)
tags, _ := db.ReadTags(vm)

return &vmInfo{
return vmInfo{
name: vm,
image: image,
box: box,
box: *box,
disk: disk,
tags: tags,
}
Expand Down
50 changes: 50 additions & 0 deletions vms/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package vms

import (
"reflect"
"testing"
)

func Test_filters_apply(t *testing.T) {
type args struct {
list vmInfoList
}
tests := []struct {
name string
f filters
args args
want vmInfoList
}{
{
name: "test 3 entries in the list, and 2 of them matches on tags",
f: []filter{{name: "tags", value: "k8s"}},
args: args{list: vmInfoList{vmInfo{tags: "k8s-master"}, vmInfo{tags: "k8s-slave"}, vmInfo{tags: "redis"}}},
want: vmInfoList{vmInfo{tags: "k8s-master"}, vmInfo{tags: "k8s-slave"}},
},
{
name: "when no filter matches, return empty list",
f: []filter{{name: "name", value: "not found"}},
args: args{list: vmInfoList{vmInfo{tags: "k8s-master"}, vmInfo{tags: "k8s-slave"}, vmInfo{tags: "redis"}}},
want: vmInfoList{},
},
{
name: "when no filter provided return the list as is",
f: []filter{},
args: args{list: vmInfoList{vmInfo{tags: "k8s-master"}, vmInfo{tags: "k8s-slave"}, vmInfo{tags: "redis"}}},
want: vmInfoList{vmInfo{tags: "k8s-master"}, vmInfo{tags: "k8s-slave"}, vmInfo{tags: "redis"}},
},
{
name: "test two filters provided - AND ops will be applied",
f: []filter{{name: "tags", value: "k8s"}, {name: "image", value: "ubuntu"}, {name: "name", value: "vm"}},
args: args{list: vmInfoList{vmInfo{tags: "k8s-master"}, vmInfo{tags: "k8s-slave", image: "ubuntu/focal", name: "vm_01"}}},
want: vmInfoList{vmInfo{tags: "k8s-slave", image: "ubuntu/focal", name: "vm_01"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.f.apply(tt.args.list); !reflect.DeepEqual(got, tt.want) {
t.Errorf("apply() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit d0bf3f8

Please sign in to comment.