7 Commits

4 changed files with 496 additions and 349 deletions

View File

@ -4,7 +4,6 @@ import (
"os"
"fmt"
"log"
"errors"
"reflect"
"strings"
"io/ioutil"
@ -13,291 +12,27 @@ import (
"github.com/spf13/viper"
)
// 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])
f := v.Elem().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.
// Nested fields are named with a dot (ex "MyStruct.MyField")
func getStructField(parentStruct interface{}, fieldName string) (reflect.Value, string) {
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 getStructField(f.Interface(), strings.Join(fs[1:], "."))
} else {
log.Fatalln(fmt.Sprintf("%s is not a struct: %s\n", fs[0], f.Kind().String()))
}
}
return v, fieldName
}
/* Pretty display of jails field
Fields to show are given in a string array parameter
Ex. : displayJails(["Name", "JID", "RootPath"])
*/
func displayStructFields(jails []Jail, 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 _, j := range jails {
// Have to use a pointer, else reflect.Value.Elem() will panic : https://pkg.go.dev/reflect#Value.Elem
tj := &j
line := make([]Field, len(valsToDisplay))
for i, f := range valsToDisplay {
a, f := getStructField(tj, 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 {
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
if f.Name == "JID" && f.Value == "0" {
fmt.Printf(" ")
} else if f.Name == "Ip4_addr" {
ia := strings.Split(f.Value, ",")
// If we have more than 1 value we need to finish this line, and store value for writing at the end of line loop
for i, inter := range ia {
if i > 0 {
supplines[f.Name] = inter
} else {
fmt.Printf(" %s", inter)
}
}
//fmt.Printf(" %s", strings.Split(strings.Split(f.Value, "|")[1], "/")[0])
} 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 !gNoLineSep {
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 gNoLineSep {
for i, f := range out[0] {
if i == 0 { fmt.Printf("+") }
for i := 0 ; i < f.MaxLen+2 ; i++ { fmt.Printf("-") }
fmt.Printf("+")
}
}
/********************************************************************************
* List all properties a jail have, with their internal name
*******************************************************************************/
func ListJailsProps(args []string) {
var conf Jail
var jailconf JailConfig
var result []string
fmt.Printf("\n")
conf.Config = jailconf
result = getStructFieldNames(conf, result, "")
for _, f := range result {
fmt.Printf("%s\n", f)
}
}
/* 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) {
fields := strings.Split(gDisplayColumns, ",")
@ -308,9 +43,9 @@ func ListJails(args []string, display bool) {
// This is the structure we will filter, then display
var jails []Jail
/************************************************************************************
/***************************************************************
/ Filter jails with "filter" options
/***********************************************************************************/
/**************************************************************/
if len(gFilterJails) > 0 && gFilterJails != "none" {
flts := make(map[string]string)
for _, flt := range strings.Split(gFilterJails, ",") {
@ -342,9 +77,9 @@ func ListJails(args []string, display bool) {
}
/************************************************************************************
/***************************************************************
/ Filter jails by names given on command line
/***********************************************************************************/
/**************************************************************/
if len(args) > 0 {
var js []Jail
for _, a := range args {
@ -358,10 +93,10 @@ func ListJails(args []string, display bool) {
jails = js
}
/************************************************************************************
/***************************************************************
/ Sort jails
/ We support 3 sort criteria max
/***********************************************************************************/
/**************************************************************/
if len(gSortFields) > 0 && gSortFields != "none" {
js := initSortStruct()
@ -399,9 +134,9 @@ func ListJails(args []string, display bool) {
}
}
/************************************************************************************
/***************************************************************
/ Finally, display jails
/***********************************************************************************/
/**************************************************************/
if display {
displayStructFields(jails, fields)
}

View File

@ -12,7 +12,7 @@ import (
)
const (
gVersion = "0.022a"
gVersion = "0.023"
)
var (
@ -30,23 +30,22 @@ var (
rootCmd = & cobra.Command{
Use: "gocage",
Short: "GoCage is a FreeBSD Jail management tool",
Long: `GoCage is a jail management tool. It support VNET, host-only, NAT networks. Provides snapshots and cloning.
Use: "gocage",
Short: "GoCage is a FreeBSD Jail management tool",
Long: `GoCage is a jail management tool. It support VNET, host-only, NAT networks. Provides snapshots and cloning.
It support iocage jails and can coexist with iocage.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Here we are in the Run")
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Here we are in the Run")
cleanAfterRun()
},
},
}
versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of GoCage",
Long: `Let this show you how much fail I had to get this *cough* perfect`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("GoCage v.%s on FreeBSD %.1f\n", gVersion, gHostVersion)
Use: "version",
Short: "Print the version number of GoCage",
Long: `Let this show you how much fail I had to get this *cough* perfect`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("GoCage v.%s on FreeBSD %.1f\n", gVersion, gHostVersion)
cleanAfterRun()
},
}
@ -63,10 +62,20 @@ ex: gocage list srv-db srv-web`,
},
}
listPropsCmd = &cobra.Command{
Use: "properties",
Short: "Print jails properties",
Long: "Display jails properties. You can use properties to filter, get or set them.",
Run: func(cmd *cobra.Command, args []string) {
ListJailsProps(args)
cleanAfterRun()
},
}
stopCmd = &cobra.Command{
Use: "stop",
Short: "stop jail",
Long: `shutdown jail`,
Long: "shutdown jail",
Run: func(cmd *cobra.Command, args []string) {
// Get the inventory
ListJails(args, false)
@ -85,6 +94,19 @@ ex: gocage list srv-db srv-web`,
cleanAfterRun()
},
}
setCmd = &cobra.Command{
Use: "set",
Short: "Set a jail property",
Long: `Set jail property value. Specify property=value, end command with jail name.
Multiples properties can be specified, separated with space (Ex: gocage set allow_mlock=1 boot=1 myjail)`,
Run: func(cmd *cobra.Command, args []string) {
// Get the inventory
ListJails(args, false)
SetJailProperties(args)
cleanAfterRun()
},
}
)
@ -93,20 +115,22 @@ func init() {
cobra.OnInitialize(initConfig)
// Global switches
rootCmd.PersistentFlags().StringVarP(&gConfigFile, "config", "c", "/usr/local/etc/gocage.conf.yml", "GoCage configuration file")
rootCmd.PersistentFlags().BoolVarP(&gUseSudo, "sudo", "u", false, "Use sudo to run commands")
rootCmd.PersistentFlags().StringVarP(&gConfigFile, "config", "c", "/usr/local/etc/gocage.conf.yml", "GoCage configuration file")
rootCmd.PersistentFlags().BoolVarP(&gUseSudo, "sudo", "u", false, "Use sudo to run commands")
// Command dependant switches
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.")
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.")
// Now declare commands
rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(listCmd)
rootCmd.AddCommand(stopCmd)
rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(listCmd)
listCmd.AddCommand(listPropsCmd)
rootCmd.AddCommand(stopCmd)
rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(setCmd)
// Get FreeBSD version
out, err := executeCommand("freebsd-version")
@ -115,7 +139,7 @@ func init() {
os.Exit(1)
}
gHostVersion, _ = strconv.ParseFloat(strings.Split(out, "-")[0], 32)
}
func initConfig() {
@ -164,21 +188,25 @@ func cleanAfterRun() {
for _, j := range gJails {
if j.ConfigUpdated {
// TODO : Marshall to disk
fmt.Printf("Config for jail %s will be updated\n", j.Name)
//fmt.Printf("Config for jail %s will be updated\n", j.Name)
marshaled, err := json.MarshalIndent(j.Config, "", " ")
if err != nil {
fmt.Printf("ERROR marshaling config: %s\n", err.Error())
}
fmt.Printf(string(marshaled))
//fmt.Printf(string(marshaled))
if os.WriteFile(j.ConfigPath, []byte(marshaled), 0644); err != nil {
fmt.Printf("Error writing config file %s: %v\n", j.ConfigPath, err)
os.Exit(1)
}
}
}
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@ -9,9 +9,53 @@ import (
// "os/exec"
// "reflect"
"strings"
// "strconv"
"strconv"
)
func SetJailProperties(args []string) {
type properties struct {
name string
value string
}
var jail Jail
var props []properties
if len(args) > 0 {
for i, a := range args {
// This is the jail name
if i == len(args)-1 {
jail.Name = a
} else {
kv := strings.Split(a, "=")
if len(kv) != 2 {
// TODO : Show help
fmt.Printf("Error parsing args: %s\n", a)
return
} else {
p := properties{name: kv[0], value: kv[1]}
props = append(props, p)
}
}
}
}
if len(jail.Name) == 0 || len(args) == 0 {
// TODO : Show help
fmt.Printf("Error\n")
return
}
for _, p := range props {
err := setJailProperty(&jail, p.name, p.value)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
}
}
}
// TODO : Support types. For now we just update int, other types will crash
// WIP. Not working now (need to address the real struct field, not a copy of it)
// setJailProperty takes a string as propValue, whatever the real property type is.
// It will be converted.
@ -27,8 +71,9 @@ func setJailProperty(jail *Jail, propName string, propValue string) error {
for i, j := range gJails {
if j.Name == jail.Name {
val, _, err := getStructFieldValue(&gJails[i], propName)
//val, _, err := getStructFieldValue(&gJails[i].Config, strings.Split(propName, ".")[1])
if err != nil {
return errors.New(fmt.Sprintf("Field not found: %s", propName))
return err
}
/*if kind == "string" {
@ -44,23 +89,24 @@ func setJailProperty(jail *Jail, propName string, propValue string) error {
return errors.New(fmt.Sprintf("Property %s have an unsupported type in setJailProperty!\n", propName))
}*/
// panic: reflect: reflect.Value.Set using unaddressable value
//val.Set(reflect.ValueOf(propValue).Elem())
// ...Because val settability is false :-(
fmt.Printf("settability of val: %v\n", val.CanSet())
// This is OK, using the index to get the real jail object
//gJails[i].Config.Allow_mlock = 1
// TODO : integrate this function
setJailConfigUpdated(jail)
if val.CanSet() {
ival, err := strconv.Atoi(propValue)
if err != nil {
return err
}
val.SetInt(int64(ival))
fmt.Printf("%s: %s set to %s\n", jail.Name, propName, propValue)
gJails[i].ConfigUpdated = true
} else {
return errors.New(fmt.Sprintf("Field is not writable : %s", propName))
}
}
}
return nil
}
// FIXME : Do not work?!
// We cant use internalName as the value exist only when jail is running
func setJailConfigUpdated(jail *Jail) error {
if len(jail.ConfigPath) == 0 {
@ -68,7 +114,8 @@ func setJailConfigUpdated(jail *Jail) error {
}
for i, j := range gJails {
if jail.ConfigPath == j.ConfigPath {
if jail.Name == j.Name {
fmt.Printf("Tag %s as configUpdated\n", jail.Name)
gJails[i].ConfigUpdated = true
return nil
}
@ -78,7 +125,7 @@ func setJailConfigUpdated(jail *Jail) error {
}
func mountProcFs(jail *Jail) error {
cmd = fmt.Sprintf("mount -t procfs proc %s/proc", jail.RootPath)
cmd := fmt.Sprintf("mount -t procfs proc %s/proc", jail.RootPath)
_, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error mounting procfs on %s/proc: %s", jail.RootPath, err.Error()))
@ -96,7 +143,7 @@ func mountLinProcFs(jail *Jail) error {
return errors.New(fmt.Sprintf("Error creating directory %s: %s", ldir, errDir.Error()))
}
}
cmd = fmt.Sprintf("mount -t linprocfs linproc %s", ldir)
cmd := fmt.Sprintf("mount -t linprocfs linproc %s", ldir)
_, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error mounting linprocfs on %s: %s", ldir, err.Error()))
@ -106,7 +153,7 @@ func mountLinProcFs(jail *Jail) error {
}
func mountDevFs(jail *Jail) error {
cmd = fmt.Sprintf("mount -t devfs dev %s/dev", jail.RootPath)
cmd := fmt.Sprintf("mount -t devfs dev %s/dev", jail.RootPath)
_, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error mounting devfs on %s/dev: %s", jail.RootPath, err.Error()))
@ -127,7 +174,7 @@ func mountFdescFs(jail *Jail) error {
return nil
}
cmd = fmt.Sprintf("mount -t fdescfs descfs %s/dev/fd", jail.RootPath)
cmd := fmt.Sprintf("mount -t fdescfs descfs %s/dev/fd", jail.RootPath)
_, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error mounting fdescfs on %s/dev/fd: %s", jail.RootPath, err.Error()))

View File

@ -3,6 +3,7 @@ package cmd
import (
"os"
"fmt"
"log"
"sort"
"bufio"
"errors"
@ -104,9 +105,9 @@ func getFstab(path string) ([]Mount, error) {
}
/*****************************************************************************
* Get a specific jail source reference, to update properties after a range loop
*****************************************************************************/
/********************************************************************************
* Get a specific jail source reference, to update properties after a range loop
*******************************************************************************/
func getJailFromArray(internalName string, jarray []Jail) (*Jail, error) {
for _, j := range jarray {
if internalName == j.InternalName {
@ -116,11 +117,10 @@ func getJailFromArray(internalName string, jarray []Jail) (*Jail, error) {
return &Jail{}, errors.New("Jail not found")
}
/*
*/
// Recurse into structure, returning reflect.Kind of named field.
// Nested fields are named with a dot (ex "MyStruct.MyField")
/********************************************************************************
* Recurse into structure, returning reflect.Kind of named field.
* Nested fields are named with a dot (ex "MyStruct.MyField")
*******************************************************************************/
func getStructFieldKind(parentStruct interface{}, fieldName string) (reflect.Kind, string, error) {
v := reflect.ValueOf(parentStruct)
@ -158,6 +158,343 @@ func getStructFieldKind(parentStruct interface{}, fieldName string) (reflect.Kin
return reflect.Kind(0), fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName))
}
/********************************************************************************
* Display struct attributes name for a given struct.
* Recurse into struct attributes of type struct
* Used to show user jail properties
*******************************************************************************/
func getStructFieldNames(parentStruct interface{}, result []string, prefix string) []string {
v := reflect.ValueOf(parentStruct)
for i := 0 ; i < v.NumField() ; i++ {
if v.Type().Field(i).Type.Kind() == reflect.Struct {
result = getStructFieldNames(v.Field(i).Interface(), result, v.Type().Field(i).Name)
} else {
if len(prefix) > 0 {
result = append(result, fmt.Sprintf("%s.%s", prefix, v.Type().Field(i).Name))
} else {
result = append(result, v.Type().Field(i).Name)
}
}
}
return result
}
/********************************************************************************
* 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)
// Get value while we're dealing with pointers
for ; v.Kind() == reflect.Ptr ; v = v.Elem() {}
if v.Kind() != reflect.Struct {
return &v, fieldName, errors.New(fmt.Sprintf("parentStruct is not a struct! Kind: %s", v.Kind().String()))
}
typeOfV := v.Type()
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())
}
}
}
var f reflect.Value
var found bool
fs := strings.Split(fieldName, ".")
// Loop through properties
for i, curF := range fs {
found = false
for j := 0 ; j < v.NumField() ; j++ {
if typeOfV.Field(j).Name == curF {
f = v.Field(j)
found = true
break
}
}
if !found {
return &v, fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName))
}
for ; f.Kind() == reflect.Ptr ; f = f.Elem() {}
/*fmt.Printf("v.kind() = %v\n", v.Kind().String())
fmt.Printf("v = %v\n", v)
fmt.Printf("f.kind() = %v\n", f.Kind().String())
fmt.Printf("f = %v\n", f)*/
// If this is the last loop, return result even if it's a struct
// FIXME : What if we got interface?
if f.Kind() != reflect.Struct && i < len(fs)-1 {
if f.IsValid() {
//fmt.Printf("Return f = %v of Kind %s\n", f, f.Kind().String())
return &f, fieldName, nil
} else {
return &v, fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName))
}
} else {
v = f
typeOfV = v.Type()
}
}
return &v, fieldName, nil
}
/********************************************************************************
* TODO : Replace by getStructFieldValue
* Recurse into structure, returning reflect.Value of wanted field.
* Nested fields are named with a dot (ex "MyStruct.MyField")
*******************************************************************************/
func getStructField(parentStruct interface{}, fieldName string) (reflect.Value, string) {
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 getStructField(f.Interface(), strings.Join(fs[1:], "."))
} else {
log.Fatalln(fmt.Sprintf("%s is not a struct: %s\n", fs[0], f.Kind().String()))
}
}
return v, fieldName
}
/********************************************************************************
* Pretty display of jails field
* Fields to show are given in a string array parameter
* Ex. : displayJails(["Name", "JID", "RootPath"])
*******************************************************************************/
func displayStructFields(jails []Jail, 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 _, j := range jails {
// Have to use a pointer, else reflect.Value.Elem() will panic : https://pkg.go.dev/reflect#Value.Elem
tj := &j
line := make([]Field, len(valsToDisplay))
for i, f := range valsToDisplay {
a, f := getStructField(tj, 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 {
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
if f.Name == "JID" && f.Value == "0" {
fmt.Printf(" ")
} else if f.Name == "Ip4_addr" {
ia := strings.Split(f.Value, ",")
// If we have more than 1 value we need to finish this line, and store value for writing at the end of line loop
for i, inter := range ia {
if i > 0 {
supplines[f.Name] = inter
} else {
fmt.Printf(" %s", inter)
}
}
//fmt.Printf(" %s", strings.Split(strings.Split(f.Value, "|")[1], "/")[0])
} 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 !gNoLineSep {
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 gNoLineSep {
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")
}
/*****************************************************************************