274 lines
6.5 KiB
Go
274 lines
6.5 KiB
Go
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
|
|
}
|