Add datastore list, filter and sort, add snapshot sorting
This commit is contained in:
parent
b4fd7caca7
commit
86e08ec0f7
@ -40,7 +40,7 @@ func ListJailsProps(args []string) {
|
|||||||
* into gJails global var
|
* into gJails global var
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
func ListJails(args []string, display bool) {
|
func ListJails(args []string, display bool) {
|
||||||
fields := strings.Split(gDisplayColumns, ",")
|
fields := strings.Split(gDisplayJColumns, ",")
|
||||||
|
|
||||||
for _, d := range viper.GetStringSlice("datastore") {
|
for _, d := range viper.GetStringSlice("datastore") {
|
||||||
listJailsFromDatastore(d)
|
listJailsFromDatastore(d)
|
||||||
@ -102,12 +102,12 @@ func ListJails(args []string, display bool) {
|
|||||||
/ Sort jails
|
/ Sort jails
|
||||||
/ We support 3 sort criteria max
|
/ We support 3 sort criteria max
|
||||||
/**************************************************************/
|
/**************************************************************/
|
||||||
if len(gSortFields) > 0 && gSortFields != "none" {
|
if len(gSortJailFields) > 0 && gSortJailFields != "none" {
|
||||||
js := initJailSortStruct()
|
js := initJailSortStruct()
|
||||||
|
|
||||||
// The way we manage criteria quantity is not very elegant...
|
// The way we manage criteria quantity is not very elegant...
|
||||||
var fct1, fct2, fct3 *reflect.Value
|
var fct1, fct2, fct3 *reflect.Value
|
||||||
for i, c := range strings.Split(gSortFields, ",") {
|
for i, c := range strings.Split(gSortJailFields, ",") {
|
||||||
var fctName string
|
var fctName string
|
||||||
if strings.HasPrefix(c, "-") {
|
if strings.HasPrefix(c, "-") {
|
||||||
fctName = fmt.Sprintf("%sDec", strings.Replace(c, "-", "", 1))
|
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:
|
case 1:
|
||||||
JailsOrderedBy(fct1.Interface().(jailLessFunc)).Sort(jails)
|
JailsOrderedBy(fct1.Interface().(jailLessFunc)).Sort(jails)
|
||||||
case 2:
|
case 2:
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -26,12 +28,22 @@ func MigrateJail(args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, jn := range jailNames {
|
for _, jn := range jailNames {
|
||||||
// Check if destination dataset exist
|
// Check if destination datastore exist
|
||||||
cmd := fmt.Sprintf("zfs list %s/iocage/jails", gMigrateDestPool)
|
/*cmd := fmt.Sprintf("zfs list %s/iocage/jails", gMigrateDestDatastore)
|
||||||
out, err := executeCommand(cmd)
|
out, err := executeCommand(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out)
|
fmt.Printf("Error executing command %s: %v; command returned: %s\n", cmd, err, out)
|
||||||
return
|
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)
|
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@gocage_mig_first_snap
|
||||||
zfs snapshot /iocage/jails/$jail/root@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
|
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")
|
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)
|
fmt.Printf("Migrate jail config dataset to %s: ", dsconfdest)
|
||||||
if err := zfsCopy(fmt.Sprintf("%s@gocage_mig_init", dsconf), dsconfdest); err != nil {
|
if err := zfsCopy(fmt.Sprintf("%s@gocage_mig_init", dsconf), dsconfdest); err != nil {
|
||||||
fmt.Printf("Error: %v\n", err)
|
fmt.Printf("Error: %v\n", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Done\n")
|
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)
|
fmt.Printf("Migrate jail filesystem dataset to %s: ", dsdatadest)
|
||||||
if err := zfsCopy(fmt.Sprintf("%s@gocage_mig_init", dsdata), dsdatadest); err != nil {
|
if err := zfsCopy(fmt.Sprintf("%s@gocage_mig_init", dsdata), dsdatadest); err != nil {
|
||||||
fmt.Printf("Error: %v\n", err)
|
fmt.Printf("Error: %v\n", err)
|
||||||
|
89
cmd/root.go
89
cmd/root.go
@ -24,23 +24,32 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gJails []Jail
|
gJails []Jail
|
||||||
|
gDatastores []Datastore
|
||||||
|
|
||||||
gUseSudo bool
|
gUseSudo bool
|
||||||
|
|
||||||
gConfigFile string
|
gConfigFile string
|
||||||
gDisplayColumns string
|
gDisplayJColumns string
|
||||||
gFilterJails string
|
gDisplaySColumns string
|
||||||
gSortFields string
|
gDisplayDColumns string
|
||||||
gNoLineSep bool
|
gFilterJails string
|
||||||
|
gFilterSnaps string
|
||||||
|
gFilterDS string
|
||||||
|
gSortJailFields string
|
||||||
|
gSortSnapFields string
|
||||||
|
gSortDSFields string
|
||||||
|
gNoJailLineSep bool
|
||||||
|
gNoSnapLineSep bool
|
||||||
|
gNoDSLineSep bool
|
||||||
|
|
||||||
gHostVersion float64
|
gHostVersion float64
|
||||||
|
|
||||||
gTimeZone string
|
gTimeZone string
|
||||||
gSnapshotName string
|
gSnapshotName string
|
||||||
|
|
||||||
gMigrateDestPool string
|
gMigrateDestDatastore string
|
||||||
gYesToAll bool
|
gYesToAll bool
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "gocage",
|
Use: "gocage",
|
||||||
@ -99,6 +108,7 @@ ex: gocage list srv-db srv-web`,
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Load inventory
|
// Load inventory
|
||||||
ListJails(args, false)
|
ListJails(args, false)
|
||||||
|
|
||||||
StartJail(args)
|
StartJail(args)
|
||||||
WriteConfigToDisk(false)
|
WriteConfigToDisk(false)
|
||||||
},
|
},
|
||||||
@ -200,7 +210,7 @@ You can specify multiple jails.`,
|
|||||||
|
|
||||||
migrateCmd = &cobra.Command{
|
migrateCmd = &cobra.Command{
|
||||||
Use: "migrate",
|
Use: "migrate",
|
||||||
Short: "Migrate jail to another zfs pool",
|
Short: "Migrate jail to another datastore",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Load inventory
|
// Load inventory
|
||||||
ListJails(args, false)
|
ListJails(args, false)
|
||||||
@ -218,6 +228,26 @@ You can specify multiple jails.`,
|
|||||||
CleanMigrateMess(args)
|
CleanMigrateMess(args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -232,12 +262,22 @@ func init() {
|
|||||||
|
|
||||||
// Command dependant switches
|
// Command dependant switches
|
||||||
|
|
||||||
// These are persistent so we can reuse them in "gocage list snapshot myjail" command (TODO)
|
// We reuse these in "gocage snapshot list myjail" and 'gocage datastore list" commands (TODO)
|
||||||
listCmd.PersistentFlags().StringVarP(&gDisplayColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output")
|
listCmd.Flags().StringVarP(&gDisplayJColumns, "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.Flags().BoolVarP(&gNoJailLineSep, "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.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.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.")
|
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
|
// This is local flag : Only available to gocage snapshot create command
|
||||||
snapshotCreateCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to create")
|
snapshotCreateCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to create")
|
||||||
snapshotCreateCmd.MarkFlagRequired("snapname")
|
snapshotCreateCmd.MarkFlagRequired("snapname")
|
||||||
@ -246,9 +286,9 @@ func init() {
|
|||||||
snapshotRollbackCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to rollback to")
|
snapshotRollbackCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to rollback to")
|
||||||
snapshotRollbackCmd.MarkFlagRequired("snapname")
|
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.Flags().BoolVarP(&gYesToAll, "yes", "y", false, "Answer yes to all questions")
|
||||||
migrateCmd.MarkFlagRequired("destpool")
|
migrateCmd.MarkFlagRequired("datastore")
|
||||||
|
|
||||||
// Now declare commands
|
// Now declare commands
|
||||||
rootCmd.AddCommand(versionCmd)
|
rootCmd.AddCommand(versionCmd)
|
||||||
@ -261,11 +301,13 @@ func init() {
|
|||||||
rootCmd.AddCommand(setCmd)
|
rootCmd.AddCommand(setCmd)
|
||||||
rootCmd.AddCommand(snapshotCmd)
|
rootCmd.AddCommand(snapshotCmd)
|
||||||
rootCmd.AddCommand(migrateCmd)
|
rootCmd.AddCommand(migrateCmd)
|
||||||
|
rootCmd.AddCommand(datastoreCmd)
|
||||||
snapshotCmd.AddCommand(snapshotListCmd)
|
snapshotCmd.AddCommand(snapshotListCmd)
|
||||||
snapshotCmd.AddCommand(snapshotCreateCmd)
|
snapshotCmd.AddCommand(snapshotCreateCmd)
|
||||||
snapshotCmd.AddCommand(snapshotDeleteCmd)
|
snapshotCmd.AddCommand(snapshotDeleteCmd)
|
||||||
snapshotCmd.AddCommand(snapshotRollbackCmd)
|
snapshotCmd.AddCommand(snapshotRollbackCmd)
|
||||||
migrateCmd.AddCommand(migrateCleanCmd)
|
migrateCmd.AddCommand(migrateCleanCmd)
|
||||||
|
datastoreCmd.AddCommand(datastoreListCmd)
|
||||||
|
|
||||||
// Get FreeBSD version
|
// Get FreeBSD version
|
||||||
out, err := executeCommand("freebsd-version")
|
out, err := executeCommand("freebsd-version")
|
||||||
@ -291,6 +333,13 @@ func initConfig() {
|
|||||||
fmt.Printf("ERROR reading config file %s : %s\n", gConfigFile, err.Error())
|
fmt.Printf("ERROR reading config file %s : %s\n", gConfigFile, err.Error())
|
||||||
os.Exit(1)
|
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.Println("Using config file:", viper.ConfigFileUsed())
|
||||||
// fmt.Printf("datastore in config : %s\n", viper.GetStringSlice("datastore"))
|
// fmt.Printf("datastore in config : %s\n", viper.GetStringSlice("datastore"))
|
||||||
@ -313,18 +362,18 @@ func initConfig() {
|
|||||||
gTimeZone = strings.Trim(string(tz), "\n")
|
gTimeZone = strings.Trim(string(tz), "\n")
|
||||||
}
|
}
|
||||||
if listCmd.Flags().Lookup("outcol") != nil && false == listCmd.Flags().Lookup("outcol").Changed {
|
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 {
|
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 {
|
if listCmd.Flags().Lookup("filter") != nil && false == listCmd.Flags().Lookup("filter").Changed {
|
||||||
gFilterJails = viper.GetString("filter")
|
gFilterJails = viper.GetString("filter")
|
||||||
}
|
}
|
||||||
if listCmd.Flags().Lookup("sort") != nil && false == listCmd.Flags().Lookup("sort").Changed {
|
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")
|
fmt.Printf("More than 3 sort criteria is not supported!\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,6 +18,9 @@ func ListJailsSnapshots(args []string) {
|
|||||||
var jailNames []string
|
var jailNames []string
|
||||||
var snapshots []Snapshot
|
var snapshots []Snapshot
|
||||||
|
|
||||||
|
/***************************************************************
|
||||||
|
/ Filter snapshots by jailname
|
||||||
|
/**************************************************************/
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
for _, a := range args {
|
for _, a := range args {
|
||||||
jailNames = append(jailNames, a)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************************************************
|
/********************************************************************************
|
||||||
|
@ -519,3 +519,28 @@ type JailHost struct {
|
|||||||
default_gateway6 string
|
default_gateway6 string
|
||||||
default_interface 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
|
||||||
|
}
|
||||||
|
373
cmd/utils.go
373
cmd/utils.go
@ -13,6 +13,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"github.com/c2h5oh/datasize"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -215,6 +216,11 @@ func executeCommandInJail(jail *Jail, cmdline string) (string, error) {
|
|||||||
return string(out), err
|
return string(out), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************
|
||||||
|
*
|
||||||
|
* ZFS datasets/pools operations
|
||||||
|
*
|
||||||
|
*****************************************************************************/
|
||||||
func zfsSnapshot(dataset string, snapname string) error {
|
func zfsSnapshot(dataset string, snapname string) error {
|
||||||
cmd := fmt.Sprintf("zfs snapshot %s@%s", dataset, snapname)
|
cmd := fmt.Sprintf("zfs snapshot %s@%s", dataset, snapname)
|
||||||
out, err := executeCommand(cmd)
|
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) {
|
func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
|
||||||
for _, j := range jarray {
|
for _, j := range jarray {
|
||||||
@ -383,6 +389,20 @@ func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
|
|||||||
return &Jail{}, errors.New("Jail not found")
|
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.
|
* Recurse into structure, returning reflect.Kind of named field.
|
||||||
* Nested fields are named with a dot (ex "MyStruct.MyField")
|
* 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
|
* Pretty display of jails field
|
||||||
* Fields to show are given in a string array parameter
|
* 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) {
|
func displayJailsFields(jails []Jail, valsToDisplay []string) {
|
||||||
/* A line is defined by :
|
/* A line is defined by :
|
||||||
@ -782,7 +802,7 @@ func displayJailsFields(jails []Jail, valsToDisplay []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Draw line separator between jails
|
// Draw line separator between jails
|
||||||
if !gNoLineSep {
|
if !gNoJailLineSep {
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
for i, f := range out[0] {
|
for i, f := range out[0] {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
@ -796,7 +816,7 @@ func displayJailsFields(jails []Jail, valsToDisplay []string) {
|
|||||||
}
|
}
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
}
|
}
|
||||||
if gNoLineSep {
|
if gNoJailLineSep {
|
||||||
for i, f := range out[0] {
|
for i, f := range out[0] {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
fmt.Printf("+")
|
fmt.Printf("+")
|
||||||
@ -814,7 +834,7 @@ func displayJailsFields(jails []Jail, valsToDisplay []string) {
|
|||||||
/********************************************************************************
|
/********************************************************************************
|
||||||
* Pretty display of snapshots field
|
* Pretty display of snapshots field
|
||||||
* Fields to show are given in a string array parameter
|
* 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) {
|
func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) {
|
||||||
/* A line is defined by :
|
/* A line is defined by :
|
||||||
@ -1007,7 +1027,7 @@ func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Draw line separator between jails
|
// Draw line separator between jails
|
||||||
if !gNoLineSep {
|
if !gNoSnapLineSep {
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
for i, f := range out[0] {
|
for i, f := range out[0] {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
@ -1021,7 +1041,238 @@ func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) {
|
|||||||
}
|
}
|
||||||
fmt.Printf("\n")
|
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] {
|
for i, f := range out[0] {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
fmt.Printf("+")
|
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 {
|
// This struct hold "sort by datastores fields" functions
|
||||||
if strings.EqualFold(s, searched) {
|
type datastoreLessFunc func(s1 *Datastore, s2 *Datastore) bool
|
||||||
return true
|
|
||||||
}
|
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
1
go.mod
@ -4,6 +4,7 @@ go 1.17
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/c-robinson/iplib v1.0.3
|
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/sirupsen/logrus v1.8.1
|
||||||
github.com/spf13/cobra v1.2.1
|
github.com/spf13/cobra v1.2.1
|
||||||
github.com/spf13/viper v1.9.0
|
github.com/spf13/viper v1.9.0
|
||||||
|
Loading…
Reference in New Issue
Block a user