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

@ -40,7 +40,7 @@ func ListJailsProps(args []string) {
* into gJails global var
*******************************************************************************/
func ListJails(args []string, display bool) {
fields := strings.Split(gDisplayColumns, ",")
fields := strings.Split(gDisplayJColumns, ",")
for _, d := range viper.GetStringSlice("datastore") {
listJailsFromDatastore(d)
@ -102,12 +102,12 @@ func ListJails(args []string, display bool) {
/ Sort jails
/ We support 3 sort criteria max
/**************************************************************/
if len(gSortFields) > 0 && gSortFields != "none" {
if len(gSortJailFields) > 0 && gSortJailFields != "none" {
js := initJailSortStruct()
// The way we manage criteria quantity is not very elegant...
var fct1, fct2, fct3 *reflect.Value
for i, c := range strings.Split(gSortFields, ",") {
for i, c := range strings.Split(gSortJailFields, ",") {
var fctName string
if strings.HasPrefix(c, "-") {
fctName = fmt.Sprintf("%sDec", strings.Replace(c, "-", "", 1))
@ -132,7 +132,7 @@ func ListJails(args []string, display bool) {
}
}
switch len(strings.Split(gSortFields, ",")) {
switch len(strings.Split(gSortJailFields, ",")) {
case 1:
JailsOrderedBy(fct1.Interface().(jailLessFunc)).Sort(jails)
case 2:

View File

@ -6,6 +6,8 @@ import (
"bufio"
"errors"
"strings"
"github.com/spf13/viper"
)
const (
@ -26,12 +28,22 @@ func MigrateJail(args []string) {
}
for _, jn := range jailNames {
// Check if destination dataset exist
cmd := fmt.Sprintf("zfs list %s/iocage/jails", gMigrateDestPool)
// Check if destination datastore exist
/*cmd := fmt.Sprintf("zfs list %s/iocage/jails", gMigrateDestDatastore)
out, err := executeCommand(cmd)
if err != nil {
fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out)
return
}*/
found := false
for _, ds := range viper.GetStringSlice("datastore") {
if strings.EqualFold(gMigrateDestDatastore, ds) {
found = true
}
}
if false == found {
fmt.Printf("Unkown datastore: %s\n", gMigrateDestDatastore)
return
}
cj, err := getJailFromArray(jn, gJails)
@ -52,7 +64,7 @@ func MigrateJail(args []string) {
}
}
/* TODO : Check dest pool (gMigrateDestPool) existence
/* TODO : Check dest pool (gMigrateDestDatastore) existence
zfs snapshot /iocage/jails/$jail@gocage_mig_first_snap
zfs snapshot /iocage/jails/$jail/root@gocage_mig_first_snap
zfs send jail@gocage_mig_first_snap | zfs receive destpool/jails/jail_name
@ -84,14 +96,16 @@ func MigrateJail(args []string) {
}
fmt.Printf("Done\n")
dsconfdest := strings.Join([]string{gMigrateDestPool, "iocage", "jails", jn}, "/")
// FIXME: Must use zfs dataset, not mountpoint stored in gMigrateDestDatastore
dsconfdest := strings.Join([]string{gMigrateDestDatastore, "iocage", "jails", jn}, "/")
fmt.Printf("Migrate jail config dataset to %s: ", dsconfdest)
if err := zfsCopy(fmt.Sprintf("%s@gocage_mig_init", dsconf), dsconfdest); err != nil {
fmt.Printf("Error: %v\n", err)
}
fmt.Printf("Done\n")
dsdatadest := strings.Join([]string{gMigrateDestPool, "iocage", "jails", jn, "root"}, "/")
// FIXME: Must use zfs dataset, not mountpoint stored in gMigrateDestDatastore
dsdatadest := strings.Join([]string{gMigrateDestDatastore, "iocage", "jails", jn, "root"}, "/")
fmt.Printf("Migrate jail filesystem dataset to %s: ", dsdatadest)
if err := zfsCopy(fmt.Sprintf("%s@gocage_mig_init", dsdata), dsdatadest); err != nil {
fmt.Printf("Error: %v\n", err)

View File

@ -24,23 +24,32 @@ const (
)
var (
gJails []Jail
gJails []Jail
gDatastores []Datastore
gUseSudo bool
gConfigFile string
gDisplayColumns string
gFilterJails string
gSortFields string
gNoLineSep bool
gConfigFile string
gDisplayJColumns string
gDisplaySColumns string
gDisplayDColumns string
gFilterJails string
gFilterSnaps string
gFilterDS string
gSortJailFields string
gSortSnapFields string
gSortDSFields string
gNoJailLineSep bool
gNoSnapLineSep bool
gNoDSLineSep bool
gHostVersion float64
gTimeZone string
gSnapshotName string
gMigrateDestPool string
gYesToAll bool
gMigrateDestDatastore string
gYesToAll bool
rootCmd = &cobra.Command{
Use: "gocage",
@ -99,6 +108,7 @@ ex: gocage list srv-db srv-web`,
Run: func(cmd *cobra.Command, args []string) {
// Load inventory
ListJails(args, false)
StartJail(args)
WriteConfigToDisk(false)
},
@ -200,7 +210,7 @@ You can specify multiple jails.`,
migrateCmd = &cobra.Command{
Use: "migrate",
Short: "Migrate jail to another zfs pool",
Short: "Migrate jail to another datastore",
Run: func(cmd *cobra.Command, args []string) {
// Load inventory
ListJails(args, false)
@ -219,6 +229,26 @@ You can specify multiple jails.`,
},
}
datastoreCmd = &cobra.Command{
Use: "datastore",
Short: "list datastores",
Long: "Commands to manage datastores. If no arguments given, list them.",
Run: func(cmd *cobra.Command, args []string) {
ListDatastores(args, true)
},
}
datastoreListCmd = &cobra.Command{
Use: "list",
Short: "list datastores",
Long: `List datastore by specifying its name.
List all datastores if no name specified.
You can specify multiple datastores.`,
Run: func(cmd *cobra.Command, args []string) {
ListDatastores(args, true)
},
}
)
// TODO : Init log level and log output
@ -232,11 +262,21 @@ func init() {
// Command dependant switches
// These are persistent so we can reuse them in "gocage list snapshot myjail" command (TODO)
listCmd.PersistentFlags().StringVarP(&gDisplayColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output")
listCmd.PersistentFlags().BoolVarP(&gNoLineSep, "nolinesep", "l", false, "Do not display line separator between jails")
listCmd.PersistentFlags().StringVarP(&gFilterJails, "filter", "f", "none", "Only display jails with these values. Ex: \"gocage list -f Config.Boot=1\" will only list started on boot jails")
listCmd.PersistentFlags().StringVarP(&gSortFields, "sort", "s", "none", "Display jails sorted by field values. Ex: \"gocage list -s +Name,-Config.Priority\" will sort jails by their decreasing name, then increasing start priority. 3 critera max supported.")
// We reuse these in "gocage snapshot list myjail" and 'gocage datastore list" commands (TODO)
listCmd.Flags().StringVarP(&gDisplayJColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output")
listCmd.Flags().BoolVarP(&gNoJailLineSep, "nolinesep", "l", false, "Do not display line separator between jails")
listCmd.Flags().StringVarP(&gFilterJails, "filter", "f", "none", "Only display jails with these values. Ex: \"gocage list -f Config.Boot=1\" will only list started on boot jails")
listCmd.Flags().StringVarP(&gSortJailFields, "sort", "s", "none", "Display jails sorted by field values. Ex: \"gocage list -s +Name,-Config.Priority\" will sort jails by their decreasing name, then increasing start priority. 3 critera max supported.")
snapshotListCmd.Flags().StringVarP(&gDisplaySColumns, "outcol", "o", "Jailname,Name,Creation,Referenced,Used", "Show these columns in output")
snapshotListCmd.Flags().BoolVarP(&gNoSnapLineSep, "nolinesep", "l", false, "Do not display line separator between snapshots")
snapshotListCmd.Flags().StringVarP(&gFilterSnaps, "filter", "f", "none", "Only display snapshots with these values. Ex: \"gocage snapshot list -f Config.Boot=1\" will only list started on boot jails")
snapshotListCmd.Flags().StringVarP(&gSortSnapFields, "sort", "s", "none", "Display snapshots sorted by field values. Ex: \"gocage snapshot list -s +Jailname,-Used\" will sort snapshots by jail decreasing name, then increasing used space. 3 critera max supported.")
datastoreListCmd.Flags().StringVarP(&gDisplayDColumns, "outcol", "o", "Name,Mountpoint,ZFSDataset,Available,Used,Referenced", "Show these columns in output")
datastoreListCmd.Flags().BoolVarP(&gNoDSLineSep, "nolinesep", "l", false, "Do not display line separator between datastores")
datastoreListCmd.Flags().StringVarP(&gFilterDS, "filter", "f", "none", "Only display datastores with these values. Ex: \"gocage datastore list -f Config.Boot=1\" will only list started on boot jails")
datastoreListCmd.Flags().StringVarP(&gSortDSFields, "sort", "s", "none", "Display datastores sorted by field values. Ex: \"gocage datastore list -s +Jailname,-Used\" will sort snapshots by jail decreasing name, then increasing used space. 3 critera max supported.")
// This is local flag : Only available to gocage snapshot create command
snapshotCreateCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to create")
@ -246,9 +286,9 @@ func init() {
snapshotRollbackCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to rollback to")
snapshotRollbackCmd.MarkFlagRequired("snapname")
migrateCmd.Flags().StringVarP(&gMigrateDestPool, "destpool", "d", "", "Name of zfs destination pool for jail")
migrateCmd.Flags().StringVarP(&gMigrateDestDatastore, "datastore", "d", "", "Path of destination datastore for jail (Ex: \"/iocage\")")
migrateCmd.Flags().BoolVarP(&gYesToAll, "yes", "y", false, "Answer yes to all questions")
migrateCmd.MarkFlagRequired("destpool")
migrateCmd.MarkFlagRequired("datastore")
// Now declare commands
rootCmd.AddCommand(versionCmd)
@ -261,11 +301,13 @@ func init() {
rootCmd.AddCommand(setCmd)
rootCmd.AddCommand(snapshotCmd)
rootCmd.AddCommand(migrateCmd)
rootCmd.AddCommand(datastoreCmd)
snapshotCmd.AddCommand(snapshotListCmd)
snapshotCmd.AddCommand(snapshotCreateCmd)
snapshotCmd.AddCommand(snapshotDeleteCmd)
snapshotCmd.AddCommand(snapshotRollbackCmd)
migrateCmd.AddCommand(migrateCleanCmd)
datastoreCmd.AddCommand(datastoreListCmd)
// Get FreeBSD version
out, err := executeCommand("freebsd-version")
@ -292,6 +334,13 @@ func initConfig() {
os.Exit(1)
}
// Load default configs from datastores
err := ListDatastores(viper.GetStringSlice("datastore"), false)
if err != nil {
fmt.Printf("ERROR: error checking datastores: %v\n", err)
os.Exit(1)
}
// fmt.Println("Using config file:", viper.ConfigFileUsed())
// fmt.Printf("datastore in config : %s\n", viper.GetStringSlice("datastore"))
// fmt.Printf("datastore.0 in config : %s\n", viper.GetStringSlice("datastore.0"))
@ -313,18 +362,18 @@ func initConfig() {
gTimeZone = strings.Trim(string(tz), "\n")
}
if listCmd.Flags().Lookup("outcol") != nil && false == listCmd.Flags().Lookup("outcol").Changed {
gDisplayColumns = viper.GetString("outcol")
gDisplayJColumns = viper.GetString("outcol")
}
if listCmd.Flags().Lookup("nolinesep") != nil && false == listCmd.Flags().Lookup("nolinesep").Changed {
gNoLineSep = viper.GetBool("nolinesep")
gNoJailLineSep = viper.GetBool("nolinesep")
}
if listCmd.Flags().Lookup("filter") != nil && false == listCmd.Flags().Lookup("filter").Changed {
gFilterJails = viper.GetString("filter")
}
if listCmd.Flags().Lookup("sort") != nil && false == listCmd.Flags().Lookup("sort").Changed {
gSortFields = viper.GetString("sort")
gSortJailFields = viper.GetString("sort")
}
if len(strings.Split(gSortFields, ",")) > 3 {
if len(strings.Split(gSortJailFields, ",")) > 3 {
fmt.Printf("More than 3 sort criteria is not supported!\n")
os.Exit(1)
}

View File

@ -7,6 +7,7 @@ import (
"os"
"regexp"
"strings"
"reflect"
"time"
)
@ -17,6 +18,9 @@ func ListJailsSnapshots(args []string) {
var jailNames []string
var snapshots []Snapshot
/***************************************************************
/ Filter snapshots by jailname
/**************************************************************/
if len(args) > 0 {
for _, a := range args {
jailNames = append(jailNames, a)
@ -36,7 +40,55 @@ func ListJailsSnapshots(args []string) {
}
}
}
displaySnapshotsFields(snapshots, []string{"Jailname", "Name", "Creation", "Referenced", "Used"})
fields := strings.Split(gDisplaySColumns, ",")
/***************************************************************
/ Sort snapshots
/ We support 3 sort criteria max
/**************************************************************/
if len(gSortSnapFields) > 0 && gSortSnapFields != "none" {
ss := initSnapshotSortStruct()
// The way we manage criteria quantity is not very elegant...
var fct1, fct2, fct3 *reflect.Value
for i, c := range strings.Split(gSortSnapFields, ",") {
var fctName string
if strings.HasPrefix(c, "-") {
fctName = fmt.Sprintf("%sDec", strings.Replace(c, "-", "", 1))
} else { // Par defaut (pas de prefix +/-) on considere un tri incremental
fctName = fmt.Sprintf("%sInc", strings.Replace(c, "+", "", 1))
}
// Get function by its name
fct, _, err := getStructFieldValue(ss, fctName)
if err != nil {
fieldName := strings.Replace(strings.Replace(c, "-", "", 1), "+", "", 1)
fmt.Printf("ERROR getting SnapshotSort struct field %s. Please check the field name: %s\n", fctName, fieldName)
return
}
switch i + 1 {
case 1:
fct1 = fct
case 2:
fct2 = fct
case 3:
fct3 = fct
}
}
switch len(strings.Split(gSortSnapFields, ",")) {
case 1:
SnapshotsOrderedBy(fct1.Interface().(snapshotLessFunc)).Sort(snapshots)
case 2:
SnapshotsOrderedBy(fct1.Interface().(snapshotLessFunc), fct2.Interface().(snapshotLessFunc)).Sort(snapshots)
case 3:
SnapshotsOrderedBy(fct1.Interface().(snapshotLessFunc), fct2.Interface().(snapshotLessFunc), fct3.Interface().(snapshotLessFunc)).Sort(snapshots)
}
}
displaySnapshotsFields(snapshots, fields)
}
/********************************************************************************

View File

@ -519,3 +519,28 @@ type JailHost struct {
default_gateway6 string
default_interface string
}
type Datastore struct {
Name string
Mountpoint string
ZFSDataset string
DefaultJailConfig JailConfig
Used uint64
Referenced uint64
Available uint64
}
type DatastoreSort struct {
NameInc datastoreLessFunc
NameDec datastoreLessFunc
MountpointInc datastoreLessFunc
MountpointDec datastoreLessFunc
ZFSDatasetInc datastoreLessFunc
ZFSDatasetDec datastoreLessFunc
UsedInc datastoreLessFunc
UsedDec datastoreLessFunc
ReferencedInc datastoreLessFunc
ReferencedDec datastoreLessFunc
AvailableInc datastoreLessFunc
AvailableDec datastoreLessFunc
}

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)
}

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.17
require (
github.com/c-robinson/iplib v1.0.3
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.9.0