Files
warewulf/internal/pkg/node/methods.go
2022-08-04 20:07:24 +02:00

654 lines
16 KiB
Go

package node
import (
"fmt"
"reflect"
"regexp"
"strings"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
)
/**********
*
* Filters
*
*********/
/*
Filter a given slice of NodeInfo against a given
regular expression
*/
func FilterByName(set []NodeInfo, searchList []string) []NodeInfo {
var ret []NodeInfo
unique := make(map[string]NodeInfo)
if len(searchList) > 0 {
for _, search := range searchList {
for _, entry := range set {
b, _ := regexp.MatchString("^"+search+"$", entry.Id.Get())
if b {
unique[entry.Id.Get()] = entry
}
}
}
for _, n := range unique {
ret = append(ret, n)
}
} else {
ret = set
}
return ret
}
/**********
*
* Sets
*
*********/
/*
Set value. If argument is 'UNDEF', 'DELETE',
'UNSET" or '--' the value is removed.
N.B. the '--' might never ever happen as '--'
is parsed out by cobra
*/
func (ent *Entry) Set(val string) {
if val == "" {
return
}
if val == "UNDEF" || val == "DELETE" || val == "UNSET" || val == "--" || val == "nil" {
wwlog.Debug("Removing value for %v\n", *ent)
ent.value = []string{""}
} else {
ent.value = []string{val}
}
}
/*
Set bool
*/
func (ent *Entry) SetB(val bool) {
if val {
ent.value = []string{"true"}
} else {
ent.value = []string{"false"}
}
}
func (ent *Entry) SetSlice(val []string) {
if len(val) == 0 {
return
}
if val[0] == "UNDEF" || val[0] == "DELETE" || val[0] == "UNSET" || val[0] == "--" {
ent.value = []string{}
} else {
ent.value = val
}
}
/*
Set alternative value
*/
func (ent *Entry) SetAlt(val string, from string) {
if val == "" {
return
}
ent.altvalue = []string{val}
ent.from = from
}
/*
Sets alternative bool
*/
func (ent *Entry) SetAltB(val bool, from string) {
if val {
ent.altvalue = []string{"true"}
ent.from = from
} else {
ent.altvalue = []string{"false"}
ent.from = from
}
}
/*
Sets alternative slice
*/
func (ent *Entry) SetAltSlice(val []string, from string) {
if len(val) == 0 {
return
}
ent.altvalue = val
ent.from = from
}
/*
Sets the default value of an entry.
*/
func (ent *Entry) SetDefault(val string) {
if val == "" {
return
}
ent.def = []string{val}
}
/*
Set the default entry as slice
*/
func (ent *Entry) SetDefaultSlice(val []string) {
if len(val) == 0 {
return
}
ent.def = val
}
/**********
*
* Gets
*
*********/
/*
Gets the the entry of the value in folowing order
* node value if set
* profile value if set
* default value if set
*/
func (ent *Entry) Get() string {
if len(ent.value) != 0 {
return ent.value[0]
}
if len(ent.altvalue) != 0 {
return ent.altvalue[0]
}
if len(ent.def) != 0 {
return ent.def[0]
}
return ""
}
/*
Get the bool value of an entry.
*/
func (ent *Entry) GetB() bool {
if len(ent.value) == 0 || ent.value[0] == "false" || ent.value[0] == "no" {
if len(ent.altvalue) == 0 || ent.altvalue[0] == "false" || ent.altvalue[0] == "no" {
return false
}
return false
}
return true
}
/*
Returns a string slice created from a comma seperated list of the value.
*/
func (ent *Entry) GetSlice() []string {
var retval []string
if len(ent.value) != 0 {
return ent.value
}
if len(ent.altvalue) != 0 {
return ent.altvalue
}
if len(ent.def) != 0 {
return ent.def
}
return retval
}
/*
Get the real value, not the alternative of default one.
*/
func (ent *Entry) GetReal() string {
if len(ent.value) == 0 {
return ""
}
return ent.value[0]
}
/*
Get the real value, not the alternative of default one.
*/
func (ent *Entry) GetRealSlice() []string {
if len(ent.value) == 0 {
return []string{}
}
return ent.value
}
/*
true if the entry has set a real value, else false.
*/
func (ent *Entry) GotReal() bool {
return len(ent.value) != 0
}
/**********
*
* Misc
*
*********/
/*
Returns the value of Entry if it was defined set or
alternative is presend. Default value is in '()'. If
nothing is defined '--' is returned.
*/
func (ent *Entry) Print() string {
if len(ent.value) != 0 {
return strings.Join(ent.value, ",")
}
if len(ent.altvalue) != 0 {
return strings.Join(ent.altvalue, ",")
}
if len(ent.def) != 0 {
return "(" + strings.Join(ent.def, ",") + ")"
}
return "--"
}
/*
Was used for combined stringSlice
func (ent *Entry) PrintComb() string {
if ent.value != "" && ent.altvalue != "" {
return "[" + ent.value + "," + ent.altvalue + "]"
}
return ent.Print()
}
*/
/*
same as GetB()
*/
func (ent *Entry) PrintB() string {
if len(ent.value) != 0 || len(ent.altvalue) != 0 {
return fmt.Sprintf("%t", ent.GetB())
}
return fmt.Sprintf("(%t)", ent.GetB())
}
/*
Returns SUPERSEDED if value was set per node or
per profile. Else -- is returned.
*/
func (ent *Entry) Source() string {
if len(ent.value) != 0 && len(ent.altvalue) != 0 {
return "SUPERSEDED"
//return fmt.Sprintf("[%s]", ent.from)
} else if ent.from == "" {
return "--"
}
return ent.from
}
/*
Check if value was defined.
*/
func (ent *Entry) Defined() bool {
if len(ent.value) != 0 {
return true
}
if len(ent.altvalue) != 0 {
return true
}
if len(ent.def) != 0 {
return true
}
return false
}
/*
Set the Entry trough an interface by trying to cast the interface
*/
func SetEntry(entryPtr interface{}, val interface{}) {
valKind := reflect.TypeOf(val)
if reflect.TypeOf(entryPtr) == reflect.TypeOf((*Entry)(nil)) {
entry := entryPtr.(*Entry)
if valKind.Kind() == reflect.String {
entry.Set(val.(string))
} else if valKind.Kind() == reflect.Slice {
if valKind.Elem().Kind() == reflect.String {
entry.SetSlice(val.([]string))
} else {
panic("Got unknown slice type")
}
}
} else if reflect.TypeOf(entryPtr) == reflect.TypeOf((*[]string)(nil)) {
entry := entryPtr.(*[]string)
if valKind.Kind() == reflect.String {
// most likely we got a comma seperated string slice
*entry = strings.Split(val.(string), ",")
} else if valKind.Kind() == reflect.Slice {
if valKind.Elem().Kind() == reflect.String {
*entry = val.([]string)
} else {
panic("Got unknown slice type")
}
}
} else {
panic(fmt.Sprintf("Can't convert %s to *node.Entry to call Set\n", reflect.TypeOf(entryPtr)))
}
}
/*
Add an entry in a map
*/
func AddEntry(entryMapInt interface{}, val interface{}) {
if reflect.TypeOf(entryMapInt) == reflect.TypeOf((*map[string]*Entry)(nil)) {
if reflect.ValueOf(entryMapInt).Elem().IsNil() {
newMap := make(map[string]*Entry)
entryMapInt = &newMap
}
entryMap := entryMapInt.(*map[string]*Entry)
str, ok := (val).(string)
if !ok {
panic("AddEntry must be called with string value")
}
for _, token := range strings.Split(str, ",") {
keyVal := strings.Split(token, "=")
if len(keyVal) == 2 {
_, mapOk := (*entryMap)[keyVal[0]]
if !mapOk {
(*entryMap)[keyVal[0]] = new(Entry)
}
(*entryMap)[keyVal[0]].Set(keyVal[1])
}
}
} else {
panic(fmt.Sprintf("Do not know how to add %v to %v\n", val, entryMapInt))
}
}
/*
Del an entry in a map
*/
func DelEntry(entryMapInt interface{}, val interface{}) {
if reflect.TypeOf(entryMapInt) == reflect.TypeOf((*map[string]*Entry)(nil)) {
entryMap := entryMapInt.(*map[string]*Entry)
str, ok := (val).(string)
if !ok {
panic("DelEntry must be called with string value")
}
for _, token := range strings.Split(str, ",") {
delete(*entryMap, token)
}
} else {
panic(fmt.Sprintf("Do not know how to del %v to %v\n", val, entryMapInt))
}
}
/*
Call SetEntry for given field (NodeInfo).
*/
func (node *NodeInfo) SetField(fieldName string, val interface{}) {
field := reflect.ValueOf(node).Elem().FieldByName(fieldName)
if field.IsValid() {
if field.Addr().Type() == reflect.TypeOf((*Entry)(nil)) {
SetEntry(field.Addr().Interface(), val)
} else if field.Addr().Type() == reflect.TypeOf((*[]string)(nil)) {
SetEntry(field.Addr().Interface(), val)
}
/*
else if field.Addr().Kind() == reflect.Map {
fmt.Println(field.Addr())
} else {
//fmt.Println("Not working field.Addr().Kind():", field.Addr().Type())
// is most likely NetDevEntry, ignore it
}
*/
} else {
fieldNames := strings.Split(fieldName, ".")
if len(fieldNames) >= 2 {
if fieldNames[0] == "del" || fieldNames[0] == "add" {
fieldMap := reflect.ValueOf(node).Elem().FieldByName(fieldNames[1])
if fieldMap.IsValid() {
if fieldNames[0] == "del" {
DelEntry(fieldMap.Addr().Elem().Interface(), val)
} else if fieldNames[0] == "add" {
AddEntry(fieldMap.Addr().Elem().Interface(), val)
}
} else {
panic(fmt.Sprintf("invalid del/add operation with name %s called, field %s does not exists\n", fieldName, fieldNames[0]))
}
} else {
nestedField := reflect.ValueOf(node).Elem().FieldByName(fieldNames[0])
if nestedField.IsValid() {
switch nestedField.Addr().Type() {
case reflect.TypeOf((**KernelEntry)(nil)):
entry := nestedField.Addr().Interface().(**KernelEntry)
(*entry).SetField(strings.Join(fieldNames[1:], "."), val)
case reflect.TypeOf((**IpmiEntry)(nil)):
entry := nestedField.Addr().Interface().(**IpmiEntry)
fmt.Println(fieldNames)
(*entry).SetField(strings.Join(fieldNames[1:], "."), val)
case reflect.TypeOf((*map[string]*NetDevEntry)(nil)):
if len(fieldNames) >= 3 {
entryMap := nestedField.Addr().Interface().(*map[string]*NetDevEntry)
if myVal, ok := (*entryMap)[fieldNames[1]]; ok {
myVal.SetField(strings.Join(fieldNames[2:], "."), val)
} else {
var newEntry NetDevEntry
(*entryMap)[fieldNames[1]] = &newEntry
newEntry.SetField(strings.Join(fieldNames[2:], "."), val)
}
}
default:
panic(fmt.Sprintf("not implemented type %v\n", nestedField.Addr().Type()))
}
} else {
panic(fmt.Sprintf("field %s is not a nested type of %s", fieldNames[0], fieldName))
}
}
} else {
panic(fmt.Sprintf("field %s does not exists in node.NodeInfo\n", fieldName))
}
}
}
/*
Call SetEntry for given field (KernelEntry)
*/
func (node *KernelEntry) SetField(fieldName string, val interface{}) {
field := reflect.ValueOf(node).Elem().FieldByName(fieldName)
if field.IsValid() {
SetEntry(field.Addr().Interface(), val)
} else {
valFields := strings.Split(fieldName, ".")
field = reflect.ValueOf(node).Elem().FieldByName(valFields[1])
if field.IsValid() && len(valFields) == 2 && valFields[0] == "add" {
AddEntry(field.Addr().Interface(), val)
} else if field.IsValid() && len(valFields) == 2 && valFields[0] == "del" {
DelEntry(field.Addr().Interface(), val)
} else {
panic(fmt.Sprintf("field %s does not exists in node.NetDevEntry\n", fieldName))
}
}
}
/*
Call SetEntry for given field (ImpiEntry)
*/
func (node *IpmiEntry) SetField(fieldName string, val interface{}) {
field := reflect.ValueOf(node).Elem().FieldByName(fieldName)
if field.IsValid() {
SetEntry(field.Addr().Interface(), val)
} else {
fmt.Println(fieldName)
valFields := strings.Split(fieldName, ".")
field = reflect.ValueOf(node).Elem().FieldByName(valFields[1])
if field.IsValid() && len(valFields) == 2 && valFields[0] == "add" {
AddEntry(field.Addr().Interface(), val)
} else if field.IsValid() && len(valFields) == 2 && valFields[0] == "del" {
DelEntry(field.Addr().Interface(), val)
} else {
panic(fmt.Sprintf("field %s does not exists in node.NetDevEntry\n", fieldName))
}
}
}
/*
Call SetEntry for given field (NetDevEntry)
*/
func (node *NetDevEntry) SetField(fieldName string, val interface{}) {
field := reflect.ValueOf(node).Elem().FieldByName(fieldName)
if field.IsValid() {
SetEntry(field.Addr().Interface(), val)
} else {
valFields := strings.Split(fieldName, ".")
field = reflect.ValueOf(node).Elem().FieldByName(valFields[1])
if field.IsValid() && len(valFields) == 2 && valFields[0] == "add" {
AddEntry(field.Addr().Interface(), val)
} else if field.IsValid() && len(valFields) == 2 && valFields[0] == "del" {
DelEntry(field.Addr().Interface(), val)
} else {
panic(fmt.Sprintf("field %s does not exists in node.NetDevEntry\n", fieldName))
}
}
}
/*
Get all names of the fields in the given struct (recursive)
and create a map[name of struct field]*string if the the field
of the struct bears the comment tag.
*/
func GetOptionsMap(theStruct interface{}) map[string]*string {
optionsMap := make(map[string]*string)
structVal := reflect.ValueOf(theStruct)
structTyp := structVal.Type()
for i := 0; i < structVal.NumField(); i++ {
field := structTyp.Field(i)
if field.Type.Kind() == reflect.Struct {
subStruct := GetOptionsMap(field)
for key, val := range subStruct {
optionsMap[key] = val
}
} else if field.Tag.Get("comment") != "" {
optionsMap[field.Name] = new(string)
}
}
return optionsMap
}
type CobraCommand struct {
*cobra.Command
}
/*
Get all names of the fields in the given struct (recursive)
and create a map[name of struct field]*string if the the field
of the struct bears the comment tag.
*/
func (baseCmd *CobraCommand) CreateFlags(theStruct interface{}, excludeList []string) map[string]*string {
optionsMap := make(map[string]*string)
structVal := reflect.ValueOf(theStruct)
structTyp := structVal.Type()
for i := 0; i < structVal.NumField(); i++ {
field := structTyp.Field(i)
//fmt.Printf("%s: field.Kind() == %s\n", field.Name, field.Type.Kind())
if field.Type.Kind() == reflect.Ptr {
a := structVal.Field(i).Elem().Interface()
subStruct := baseCmd.CreateFlags(a, excludeList)
for key, val := range subStruct {
optionsMap[field.Name+"."+key] = val
}
} else if field.Type.Kind() == reflect.Map {
// check the type of map
mapType := field.Type.Elem()
if mapType.Kind() == reflect.Ptr {
//a := reflect.ValueOf((mapType.Elem())) node.NetDevs
subMap := baseCmd.CreateFlags(reflect.New(mapType.Elem()).Elem().Interface(), excludeList)
for key, val := range subMap {
optionsMap[field.Name+"."+key] = val
}
if mapType == reflect.TypeOf((*NetDevs)(nil)) {
// set the option for the network name here
var netName string
optionsMap[field.Name] = &netName
baseCmd.PersistentFlags().StringVarP(&netName,
"netname", "n", "", "Define the network name to configure")
}
} else if mapType.Kind() == reflect.String {
if field.Tag.Get("lopt") != "" {
var addPair string
optionsMap["add"+"."+field.Name] = &addPair
baseCmd.PersistentFlags().StringVarP(&addPair,
field.Tag.Get("lopt")+"add", "", "", "Add key/value pair to "+field.Tag.Get("comment"))
var delPair string
optionsMap["del"+"."+field.Name] = &delPair
baseCmd.PersistentFlags().StringVarP(&delPair,
field.Tag.Get("lopt")+"del", "", "", "Delete key/value pair to "+field.Tag.Get("comment"))
}
} else {
// TODO: implement handling of string maps
wwlog.Warn("handling of %v not implemented\n", field.Type)
}
} else if field.Tag.Get("comment") != "" && !util.InSlice(excludeList, field.Tag.Get("lopt")) {
var newStr string
optionsMap[field.Name] = &newStr
if field.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().StringVarP(&newStr,
field.Tag.Get("lopt"),
field.Tag.Get("sopt"),
field.Tag.Get("default"),
field.Tag.Get("comment"))
} else if !util.InSlice(excludeList, field.Tag.Get("lopt")) {
baseCmd.PersistentFlags().StringVar(&newStr,
field.Tag.Get("lopt"),
field.Tag.Get("default"),
field.Tag.Get("comment"))
}
}
}
return optionsMap
}
/*
Helper function which gets the lopt of a given interface
*/
func GetLoptOf(myStruct interface{}, name string) string {
retStr := ""
if reflect.TypeOf(myStruct).Kind() != reflect.Struct {
return retStr
}
myType := reflect.TypeOf(myStruct)
field, ok := myType.FieldByName(name)
if ok {
retStr = field.Tag.Get("lopt")
}
return retStr
}
/*
Returns a translation map of field name and its associated lopt.
*/
func GetloptMap(myStruct interface{}) map[string]string {
retMap := make(map[string]string)
if reflect.TypeOf(myStruct).Kind() != reflect.Struct {
return retMap
}
structType := reflect.TypeOf(myStruct)
for i := 0; i < structType.NumField(); i++ {
retMap[structType.Field(i).Name] = structType.Field(i).Name
lopt := structType.Field(i).Tag.Get("lopt")
if lopt != "" {
retMap[structType.Field(i).Name] = lopt
}
}
return retMap
}