Add datastore list, filter and sort, add snapshot sorting

This commit is contained in:
yo
2022-06-18 16:09:22 +02:00
parent b4fd7caca7
commit 86e08ec0f7
7 changed files with 530 additions and 44 deletions

View File

@ -13,6 +13,7 @@ import (
"strconv"
"strings"
"io/ioutil"
"github.com/c2h5oh/datasize"
)
const (
@ -215,6 +216,11 @@ func executeCommandInJail(jail *Jail, cmdline string) (string, error) {
return string(out), err
}
/*****************************************************************************
*
* ZFS datasets/pools operations
*
*****************************************************************************/
func zfsSnapshot(dataset string, snapname string) error {
cmd := fmt.Sprintf("zfs snapshot %s@%s", dataset, snapname)
out, err := executeCommand(cmd)
@ -372,7 +378,7 @@ func getFstab(path string) ([]Mount, error) {
}
/********************************************************************************
* Get a specific jail source reference, to update properties after a range loop
* Get a specific jail reference, to update properties after a range loop
*******************************************************************************/
func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
for _, j := range jarray {
@ -383,6 +389,20 @@ func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
return &Jail{}, errors.New("Jail not found")
}
/*****************************************************************************
*
* Generic utilities
*
*****************************************************************************/
func isStringInArray(strarr []string, searched string) bool {
for _, s := range strarr {
if strings.EqualFold(s, searched) {
return true
}
}
return false
}
/********************************************************************************
* Recurse into structure, returning reflect.Kind of named field.
* Nested fields are named with a dot (ex "MyStruct.MyField")
@ -589,7 +609,7 @@ func setStructFieldValue(parentStruct interface{}, propName string, propValue st
/********************************************************************************
* Pretty display of jails field
* Fields to show are given in a string array parameter
* Ex. : displayJailsFields(["Name", "JID", "RootPath"])
* Ex. : displayJailsFields(gJails, ["Name", "JID", "RootPath"])
*******************************************************************************/
func displayJailsFields(jails []Jail, valsToDisplay []string) {
/* A line is defined by :
@ -782,7 +802,7 @@ func displayJailsFields(jails []Jail, valsToDisplay []string) {
}
}
// Draw line separator between jails
if !gNoLineSep {
if !gNoJailLineSep {
fmt.Printf("\n")
for i, f := range out[0] {
if i == 0 {
@ -796,7 +816,7 @@ func displayJailsFields(jails []Jail, valsToDisplay []string) {
}
fmt.Printf("\n")
}
if gNoLineSep {
if gNoJailLineSep {
for i, f := range out[0] {
if i == 0 {
fmt.Printf("+")
@ -814,7 +834,7 @@ func displayJailsFields(jails []Jail, valsToDisplay []string) {
/********************************************************************************
* Pretty display of snapshots field
* Fields to show are given in a string array parameter
* Ex. : displaySnapshotsFields(["Name", "Dsname", "Used"])
* Ex. : displaySnapshotsFields(snapshots, ["Name", "Dsname", "Used"])
*******************************************************************************/
func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) {
/* A line is defined by :
@ -1007,7 +1027,7 @@ func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) {
}
}
// Draw line separator between jails
if !gNoLineSep {
if !gNoSnapLineSep {
fmt.Printf("\n")
for i, f := range out[0] {
if i == 0 {
@ -1021,7 +1041,238 @@ func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) {
}
fmt.Printf("\n")
}
if gNoLineSep {
if gNoSnapLineSep {
for i, f := range out[0] {
if i == 0 {
fmt.Printf("+")
}
for i := 0; i < f.MaxLen+2; i++ {
fmt.Printf("-")
}
fmt.Printf("+")
}
}
fmt.Printf("\n")
}
/********************************************************************************
* Pretty display of datastores field
* Fields to show are given in a string array parameter
* Ex. : displaySnapshotsFields(datastores, ["Name", "ZFSDataset", "Available"])
*******************************************************************************/
func displayDatastoresFields(datastores []Datastore, valsToDisplay []string) {
/* A line is defined by :
* Nr of fields
* For each field :
* Its name
* Its max length
* Its value
*/
type Field struct {
Name string
MaxLen int
Value string
}
type Line []Field
type Output []Line
var out Output
//fmt.Printf("%d fields in a %d items structure\n", len(valsToDisplay), len(jails))
// Browse structure to get max length and values
for _, d := range datastores {
// Have to use a pointer, else reflect.Value.Elem() will panic : https://pkg.go.dev/reflect#Value.Elem
td := &d
line := make([]Field, len(valsToDisplay))
for i, f := range valsToDisplay {
a, f := getStructField(td, f)
field := Field{
Name: f,
}
if a.FieldByName(f).IsValid() {
// For now this just contains this item length, will adjust later
// We need to divide length by number of items in string fields (because we can have more than 1 ip4_addr)
if reflect.TypeOf(a.FieldByName(f).Interface()).Kind() == reflect.String {
itnr := len(strings.Split(string(a.FieldByName(f).Interface().(string)), ","))
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) / itnr
} else {
// Special case of disk size : We will print human readable values
if field.Name == "Used" || field.Name == "Referenced" || field.Name == "Available" {
var v datasize.ByteSize
v.UnmarshalText([]byte(fmt.Sprintf("%v", a.FieldByName(f).Interface())))
field.MaxLen = len(fmt.Sprintf("%v", v.HumanReadable()))
} else {
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface()))
}
}
field.Value = fmt.Sprintf("%v", a.FieldByName(f).Interface())
} else {
fmt.Printf("Invalid field name: %s\n", f)
}
line[i] = field
}
out = append(out, line)
}
if len(out) == 0 {
fmt.Printf("Nothing to see here!\n")
return
}
// Get real maximum length
maxlen := make([]int, len(valsToDisplay))
for i := 0; i < len(valsToDisplay); i++ {
maxlen[i] = len(valsToDisplay[i])
}
for _, l := range out {
for i, f := range l {
if f.MaxLen > maxlen[i] {
maxlen[i] = f.MaxLen
}
}
}
// Align maxlen to the real maximum length
for io, l := range out {
for ii, _ := range l {
// We need to access slice by index if we want to modify content
out[io][ii].MaxLen = maxlen[ii]
}
}
totalLen := 0
// For each field, add separator and 2 blank spaces
for _, f := range out[0] {
totalLen += f.MaxLen + 3
}
// Then add starting delimiter
totalLen += 1
Debug := false
if Debug == true {
for _, f := range out[0] {
fmt.Printf("%s max length : %d\n", f.Name, f.MaxLen)
}
}
// Lets draw things on the screen!
// First, headers: 1st separator line
for i, f := range out[0] {
if i == 0 {
fmt.Printf("+")
}
for i := 0; i < f.MaxLen+2; i++ {
fmt.Printf("=")
}
fmt.Printf("+")
}
fmt.Printf("\n")
// Column names
for i, f := range out[0] {
if i == 0 {
fmt.Printf("|")
}
/* Use vlsToDisplay to get real name (with "Config.")
* fmt.Printf(" %s", f.Name)
* for i := len(f.Name)+1 ; i < f.MaxLen+1 ; i++ { */
fmt.Printf(" %s", valsToDisplay[i])
for i := len(valsToDisplay[i]) + 1; i < f.MaxLen+1; i++ {
fmt.Printf(" ")
}
fmt.Printf(" |")
}
// Finally separator line
fmt.Printf("\n")
for i, f := range out[0] {
if i == 0 {
fmt.Printf("+")
}
for i := 0; i < f.MaxLen+2; i++ {
fmt.Printf("=")
}
fmt.Printf("+")
}
fmt.Printf("\n")
// Then display data
// Loop through lines
for _, l := range out {
// Loop through fields
// In case we need to add a line for a 2nd IP, or whatever object
var supplines = make(map[string]string)
for i, f := range l {
if i == 0 {
fmt.Printf("|")
}
// Special cases of value displaying
// Pretty print of sizes
if f.Name == "Used" || f.Name == "Referenced" || f.Name == "Available" {
var v datasize.ByteSize
err := v.UnmarshalText([]byte(f.Value))
if err != nil {
return
}
fmt.Printf(" %s", v.HumanReadable())
// Complete with spaces to the max length
for i := len(v.HumanReadable()) + 1; i < f.MaxLen+1; i++ {
fmt.Printf(" ")
}
} else {
fmt.Printf(" %s", f.Value)
// Complete with spaces to the max length
for i := len(f.Value) + 1; i < f.MaxLen+1; i++ {
fmt.Printf(" ")
}
}
fmt.Printf(" |")
}
// Draw supplementary lines
if len(supplines) > 0 {
for i, f := range l {
if i == 0 {
fmt.Printf("\n|")
}
// Overwrite value in this scope
v, exists := supplines[f.Name]
if exists {
fmt.Printf(" %s", v)
} else {
// 1st option : Do not redisplay precedent line values
fmt.Printf(" ")
// 2nd option : Redisplay precedenet line values
//fmt.Printf(" %s", f.Value)
}
// Complete with spaces to the max length
for i := len(v) + 1; i < f.MaxLen+1; i++ {
fmt.Printf(" ")
}
fmt.Printf(" |")
}
}
// Draw line separator between jails
if !gNoDSLineSep {
fmt.Printf("\n")
for i, f := range out[0] {
if i == 0 {
fmt.Printf("+")
}
for i := 0; i < f.MaxLen+2; i++ {
fmt.Printf("-")
}
fmt.Printf("+")
}
}
fmt.Printf("\n")
}
if gNoDSLineSep {
for i, f := range out[0] {
if i == 0 {
fmt.Printf("+")
@ -2087,14 +2338,108 @@ func (ss *snapshotSorter) Less(i, j int) bool {
/*****************************************************************************
*
* Generic utilities
* Sorting datastores
*
*****************************************************************************/
func isStringInArray(strarr []string, searched string) bool {
for _, s := range strarr {
if strings.EqualFold(s, searched) {
return true
}
// This struct hold "sort by datastores fields" functions
type datastoreLessFunc func(s1 *Datastore, s2 *Datastore) bool
func initDatastoreSortStruct() DatastoreSort {
ss := DatastoreSort{
NameInc: func(s1, s2 *Datastore) bool {
return s1.Name < s2.Name
},
NameDec: func(s1, s2 *Datastore) bool {
return s1.Name > s2.Name
},
MountpointInc: func(s1, s2 *Datastore) bool {
return s1.Mountpoint < s2.Mountpoint
},
MountpointDec: func(s1, s2 *Datastore) bool {
return s1.Mountpoint > s2.Mountpoint
},
ZFSDatasetInc: func(s1, s2 *Datastore) bool {
return s1.ZFSDataset < s2.ZFSDataset
},
ZFSDatasetDec: func(s1, s2 *Datastore) bool {
return s1.ZFSDataset > s2.ZFSDataset
},
UsedInc: func(s1, s2 *Datastore) bool {
return s1.Used < s2.Used
},
UsedDec: func(s1, s2 *Datastore) bool {
return s1.Used > s2.Used
},
ReferencedInc: func(s1, s2 *Datastore) bool {
return s1.Referenced < s2.Referenced
},
ReferencedDec: func(s1, s2 *Datastore) bool {
return s1.Referenced > s2.Referenced
},
AvailableInc: func(s1, s2 *Datastore) bool {
return s1.Available < s2.Available
},
AvailableDec: func(s1, s2 *Datastore) bool {
return s1.Available > s2.Available
},
}
return false
return ss
}
// DatastoreSorter implements the Sort interface, sorting the jails within.
type DatastoreSorter struct {
Datastores []Datastore
less []datastoreLessFunc
}
// Sort sorts the argument slice according to the less functions passed to OrderedBy.
func (ss *DatastoreSorter) Sort(Datastores []Datastore) {
ss.Datastores = Datastores
sort.Sort(ss)
}
// OrderedBy returns a Sorter that sorts using the less functions, in order.
// Call its Sort method to sort the data.
func DatastoresOrderedBy(less ...datastoreLessFunc) *DatastoreSorter {
return &DatastoreSorter{
less: less,
}
}
// Len is part of sort.Interface.
func (ss *DatastoreSorter) Len() int {
return len(ss.Datastores)
}
// Swap is part of sort.Interface.
func (ss *DatastoreSorter) Swap(i, j int) {
ss.Datastores[i], ss.Datastores[j] = ss.Datastores[j], ss.Datastores[i]
}
// Less is part of sort.Interface. It is implemented by looping along the
// less functions until it finds a comparison that discriminates between
// the two items (one is less than the other). Note that it can call the
// less functions twice per call. We could change the functions to return
// -1, 0, 1 and reduce the number of calls for greater efficiency: an
// exercise for the reader.
func (ss *DatastoreSorter) Less(i, j int) bool {
p, q := &ss.Datastores[i], &ss.Datastores[j]
// Try all but the last comparison.
var k int
for k = 0; k < len(ss.less)-1; k++ {
less := ss.less[k]
switch {
case less(p, q):
// p < q, so we have a decision.
return true
case less(q, p):
// p > q, so we have a decision.
return false
}
// p == q; try the next comparison.
}
// All comparisons to here said "equal", so just return whatever
// the final comparison reports.
return ss.less[k](p, q)
}