package cmd import ( "os" "fmt" "log" "reflect" "strings" "io/ioutil" "gocage/jail" "encoding/json" "github.com/spf13/viper" ) // 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 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) } // 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 for i := 0; i < totalLen ; i++ { fmt.Printf("-") } fmt.Printf("\n") 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(" |") } fmt.Printf("\n") for i := 0; i < totalLen ; i++ { fmt.Printf("-") } fmt.Printf("\n") // Then display data for _, l := range out { for i, f := range l { if i == 0 { fmt.Printf("|") } fmt.Printf(" %s", f.Value) for i := len(f.Value)+1 ; i < f.MaxLen+1 ; i++ { fmt.Printf(" ") } fmt.Printf(" |") } fmt.Printf("\n") } for i := 0; i < totalLen ; i++ { fmt.Printf("-") } fmt.Printf("\n") } /* Get Jails from datastores. Store config and running metadata into gJails global var */ func listJails(args []string, display bool) { fields := []string{"JID", "Name", "Config.Release", "Config.Ip4_addr", "RootPath", "Running", "Config.Jail_zfs", "Config.Jail_zfs_dataset"} for _, d := range viper.GetStringSlice("datastore") { listJailsFromDatastore(d) } if display { if len(args) > 0 { var js []Jail for _, a := range args { for _, j := range gJails { if j.Name == a { js = append(js, j) break } } } displayStructFields(js, fields) } else { displayStructFields(gJails, fields) } } } func listJailsFromDatastore(datastore string) { fileInfo, err := os.Stat(datastore) if err != nil { log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", datastore)) } if fileInfo.IsDir() == false { log.Fatalln(fmt.Sprintf("%s is not a directory", datastore)) } // A datastore have to contain a "jails" directory jailsDir := fmt.Sprintf("%s/jails", datastore) fileInfo, err = os.Stat(jailsDir) if err != nil { log.Fatalln(fmt.Sprintf("Unable to access %s, check path and/or rights", jailsDir)) } if fileInfo.IsDir() == false { log.Fatalln(fmt.Sprintf("%s is not a directory", jailsDir)) } listJailsFromDirectory(jailsDir) } func listJailsFromDirectory(dir string) ([]Jail) { files, err := ioutil.ReadDir(dir) if err != nil { log.Fatalln(fmt.Sprintf("Unable to browse %s, check path and/or rights", dir)) } for _, fi := range files { if fi.IsDir() == true { // 1. Get conf from config.json jailConfPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "config.json") jailConf, err := getJailConfig(jailConfPath) if err != nil { log.Println("ERROR reading jail config for %s", jailConfPath) } // 2. Build jail object from config jailRootPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "root") j := Jail{ Name: jailConf.Host_hostname, Config: jailConf, ConfigPath: jailConfPath, RootPath: jailRootPath, Running: false, } // 3. Add current running informations rjails, err := jail.GetJails() if err != nil { log.Fatalln("Unable to list running jails") } for _, rj := range rjails { if rj.Path == j.RootPath { j.JID = rj.Jid j.Running = true j.InternalName = rj.Name } } gJails = append(gJails, j) } } return gJails } func getJailConfig(jailConfigPath string) (JailConfig, error) { content, err := ioutil.ReadFile(jailConfigPath) if err != nil { log.Fatalln(fmt.Sprintf("Unable to read %s, check path and/or rights", jailConfigPath)) } var jc JailConfig err = json.Unmarshal([]byte(content), &jc) if err != nil { log.Fatalln(fmt.Sprintf("Error occured during unmarshaling %s: %s", jailConfigPath, err.Error())) } return jc, err }