Add filter option, update Readme

This commit is contained in:
yo 2021-12-19 16:49:07 +01:00
parent eceb108f2c
commit fd0cb27798
3 changed files with 172 additions and 17 deletions

View File

@ -1,5 +1,65 @@
# gocage # gocage
Jail management tool for FreeBSD, written in Go. Jail management tool for FreeBSD, written in Go.
Support iocage jails, so they can coexist. Support iocage jails, so they can coexist.
Gocage is meant to be a complete jail management tool with network, snapshots, jail cloning support and a web interface. Gocage is meant to be a complete jail management tool with network, snapshots, jail cloning support and a web interface.
# List jails
Nothing fancy, use
gocage list
## Specify fields to display
Use -o to specify which fields you want to display:
gocage list -o JID,Name,Running,Config.Boot,Config.Comment
+=====+==========+=========+=============+================+
| JID | Name | Running | Config.Boot | Config.Comment |
+=====+==========+=========+=============+================+
| 183 | test | true | 1 | none |
+-----+----------+---------+-------------+----------------+
| 29 | srv-irc | true | 1 | |
+-----+----------+---------+-------------+----------------+
| | srv-web | false | 0 | |
+-----+----------+---------+-------------+----------------+
| 22 | srv-dns1 | true | 1 | |
+-----+----------+---------+-------------+----------------+
## Filter jails
### By name
Just add name on gocage list command :
gocage list srv-bdd srv-web
+=====+=========+=================+=======================+=========+
| JID | Name | Config.Release | Config.Ip4_addr | Running |
+=====+=========+=================+=======================+=========+
| 98 | srv-db | 13.0-RELEASE-p5 | vnet0|192.168.1.56/24 | true |
+-----+---------+-----------------+-----------------------+---------+
| 41 | srv-web | 13.0-RELEASE-p4 | vnet0|192.168.1.26/24 | true |
+-----+---------+-----------------+-----------------------+---------+
### By field value
You can filter jails with -f option, followed by key=value. Suppose you want to see only active at boot jails:
gocage list -f Config.Boot=1 -o JID,Name,Running,Config.Boot,Config.Comment
+=====+==========+=========+=============+================+
| JID | Name | Running | Config.Boot | Config.Comment |
+=====+==========+=========+=============+================+
| 183 | test | true | 1 | none |
+-----+----------+---------+-------------+----------------+
| 29 | srv-irc | true | 1 | |
+-----+----------+---------+-------------+----------------+
| | srv-db | false | 1 | none |
+-----+----------+---------+-------------+----------------+
| 22 | srv-dns1 | true | 1 | |
+-----+----------+---------+-------------+----------------+
Now, only active at boot and running :
gocage list -f Config.Boot=1,Running=true -o JID,Name,Running,Config.Boot
+=====+==========+=========+=============+
| JID | Name | Running | Config.Boot |
+=====+==========+=========+=============+
| 183 | test | true | 1 |
+-----+----------+---------+-------------+
| 29 | srv-irc | true | 1 |
+-----+----------+---------+-------------+
| 22 | srv-dns1 | true | 1 |
+-----+----------+---------+-------------+

View File

@ -4,6 +4,7 @@ import (
"os" "os"
"fmt" "fmt"
"log" "log"
"errors"
"reflect" "reflect"
"strings" "strings"
"io/ioutil" "io/ioutil"
@ -13,6 +14,46 @@ import (
) )
// Recurse into structure, returning reflect.Value of wanted field.
// Nested fields are named with a dot (ex "MyStruct.MyField")
func getStructFieldValue(parentStruct interface{}, fieldName string) (reflect.Value, string, error) {
v := reflect.ValueOf(parentStruct)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if false {
for i := 0 ; i < v.NumField(); i++ {
f := v.Field(i)
if f.Kind() == reflect.String {
fmt.Printf("%v=%v\n", v.Type().Field(i).Name, f.Interface())
}
}
}
if strings.Contains(fieldName, ".") {
fs := strings.Split(fieldName, ".")
f := v.FieldByName(fs[0])
if f.Kind() == reflect.Struct {
return getStructFieldValue(f.Interface(), strings.Join(fs[1:], "."))
} else {
log.Fatalln(fmt.Sprintf("%s is not a struct: %s\n", fs[0], f.Kind().String()))
}
} else {
f := v.FieldByName(fieldName)
if f.IsValid() {
return f, fieldName, nil
} else {
return v, fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName))
}
}
return v, fieldName, nil
}
// TODO : Replace by getStructFieldValue
// Recurse into structure, returning reflect.Value of wanted field. // Recurse into structure, returning reflect.Value of wanted field.
// Nested fields are named with a dot (ex "MyStruct.MyField") // Nested fields are named with a dot (ex "MyStruct.MyField")
func getStructField(parentStruct interface{}, fieldName string) (reflect.Value, string) { func getStructField(parentStruct interface{}, fieldName string) (reflect.Value, string) {
@ -101,6 +142,11 @@ func displayStructFields(jails []Jail, valsToDisplay []string) {
out = append(out, line) out = append(out, line)
} }
if len(out) == 0 {
fmt.Printf("Nothing to see here!\n")
return
}
// Get real maximum length // Get real maximum length
maxlen := make([]int, len(valsToDisplay)) maxlen := make([]int, len(valsToDisplay))
for i := 0; i< len(valsToDisplay); i++ { for i := 0; i< len(valsToDisplay); i++ {
@ -173,6 +219,7 @@ func displayStructFields(jails []Jail, valsToDisplay []string) {
// Then display data // Then display data
// Loop through lines // Loop through lines
for _, l := range out { for _, l := range out {
// Loop through fields // Loop through fields
// In case we need to add a line for a 2nd IP, or whatever object // In case we need to add a line for a 2nd IP, or whatever object
var supplines = make(map[string]string) var supplines = make(map[string]string)
@ -251,29 +298,67 @@ func displayStructFields(jails []Jail, valsToDisplay []string) {
/* Get Jails from datastores. Store config and running metadata into gJails global var */ /* Get Jails from datastores. Store config and running metadata into gJails global var */
func ListJails(args []string, display bool) { func ListJails(args []string, display bool) {
//fields := []string{"JID", "Name", "Config.Release", "Config.Ip4_addr", "RootPath", "Running", "Config.Jail_zfs", "Config.Jail_zfs_dataset", "Zpool"}
//fields := []string{"JID", "Name", "Config.Release", "Config.Ip4_addr", "Running", "Zpool"}
fields := strings.Split(gDisplayColumns, ",") fields := strings.Split(gDisplayColumns, ",")
for _, d := range viper.GetStringSlice("datastore") { for _, d := range viper.GetStringSlice("datastore") {
listJailsFromDatastore(d) listJailsFromDatastore(d)
} }
if display { // This is the structure we will filter, then display
if len(args) > 0 { var jails []Jail
var js []Jail
for _, a := range args { /************************************************************************************
for _, j := range gJails { / Filter jails with "filter" options
if j.Name == a { /***********************************************************************************/
js = append(js, j) if len(gFilterJails) > 0 && gFilterJails != "none" {
break flts := make(map[string]string)
for _, flt := range strings.Split(gFilterJails, ",") {
sa := strings.Split(flt, "=")
if len(sa) != 2 {
fmt.Printf("Invalid format for filter %s\n", gFilterJails)
return
}
flts[sa[0]] = sa[1]
}
if len(flts) > 0 {
// Browse global gJails array to get filtered results
for _, j := range gJails {
for key, val := range flts {
v, _, err := getStructFieldValue(&j, key)
if err != nil {
fmt.Printf("ERROR: Field %s not found\n", key)
return
}
if strings.EqualFold(fmt.Sprintf("%v", v.Interface()), val) {
jails = append(jails, j)
} }
} }
} }
displayStructFields(js, fields)
} else {
displayStructFields(gJails, fields)
} }
} else {
jails = gJails
}
/************************************************************************************
/ Filter jails by names given on command line
/***********************************************************************************/
if len(args) > 0 {
var js []Jail
for _, a := range args {
for _, j := range jails {
if j.Name == a {
js = append(js, j)
break
}
}
}
jails = js
}
if display {
displayStructFields(jails, fields)
} }
} }

View File

@ -20,6 +20,8 @@ var (
gConfigFile string gConfigFile string
gDisplayColumns string gDisplayColumns string
gFilterJails string
gSortFields string
gNoLineSep bool gNoLineSep bool
@ -73,11 +75,13 @@ func init() {
// Global switches // Global switches
rootCmd.PersistentFlags().StringVarP(&gConfigFile, "config", "c", "/usr/local/etc/gocage.conf.yml", "GoCage configuration file") rootCmd.PersistentFlags().StringVarP(&gConfigFile, "config", "c", "/usr/local/etc/gocage.conf.yml", "GoCage configuration file")
rootCmd.PersistentFlags().BoolVarP(&gUseSudo, "sudo", "s", false, "Use sudo to run commands") rootCmd.PersistentFlags().BoolVarP(&gUseSudo, "sudo", "u", false, "Use sudo to run commands")
// Command dependant switches // Command dependant switches
listCmd.PersistentFlags().StringVarP(&gDisplayColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output") 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().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 Config.Priority\" will sort jails by their start priority. NOT IMPLEMENTED YET")
// Now declare commands // Now declare commands
rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(versionCmd)
@ -114,6 +118,12 @@ func initConfig() {
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") gNoLineSep = 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")
}
} }
func Execute() { func Execute() {