Refactor the CreateFlags command

* added the additional type flag, which can be bool,IP
* UNDEF is recognized for the bool flag
* 0.0.0.0 must be used as UNDEF for IP flag

Signed-off-by: Christian Goll <cgoll@suse.de>
This commit is contained in:
Christian Goll
2023-02-02 17:06:03 +01:00
parent 09d14039b0
commit 13bc378a6b
11 changed files with 241 additions and 116 deletions

View File

@@ -9,6 +9,7 @@ import (
"github.com/spf13/cobra"
)
<<<<<<< HEAD
/*
RunE needs a function of type func(*cobraCommand,[]string) err, but
in order to avoid global variables which mess up testing a function of
@@ -16,6 +17,10 @@ the required type is returned
*/
func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
// run converters for different types
for _, c := range Converters {
c()
}
// remove the default network as all network values are assigned
// to this network
if _, ok := vars.nodeConf.NetDevs["default"]; ok && vars.netName != "" {

View File

@@ -14,6 +14,7 @@ import (
type variables struct {
netName string
nodeConf node.NodeConf
converters []func()
}
// Returns the newly created command
@@ -28,9 +29,8 @@ func GetCommand() *cobra.Command {
RunE: CobraRunE(&vars),
Args: cobra.MinimumNArgs(1),
}
vars.nodeConf.CreateFlags(baseCmd, []string{"tagdel", "nettagdel", "ipmitagdel"})
vars.converters = vars.nodeConf.CreateFlags(baseCmd, []string{"tagdel", "nettagdel", "ipmitagdel"})
baseCmd.PersistentFlags().StringVar(&vars.netName, "netname", "", "Set network name for network options")
// register the command line completions
if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := container.ListSources()

View File

@@ -13,6 +13,10 @@ import (
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
// run converters for different types
for _, c := range Converters {
c()
}
// remove the default network as the all network values are assigned
// to this network
if NetName != "default" {

View File

@@ -15,7 +15,7 @@ var (
DisableFlagsInUseLine: true,
Use: "set [OPTIONS] PATTERN [PATTERN ...]",
Short: "Configure node properties",
Long: "This command sets configuration properties for nodes matching PATTERN.\n\nNote: use the string 'UNSET' to remove a configuration",
Long: "This command sets configuration properties for nodes matching PATTERN.\n\nNote: use the string 'UNSET'/'0.0.0.0' to remove a configuration",
Args: cobra.MinimumNArgs(0),
RunE: CobraRunE,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
@@ -38,11 +38,12 @@ var (
SetYes bool
SetForce bool
NodeConf node.NodeConf
Converters []func()
)
func init() {
NodeConf = node.NewConf()
NodeConf.CreateFlags(baseCmd, []string{})
Converters = NodeConf.CreateFlags(baseCmd, []string{})
baseCmd.PersistentFlags().StringVarP(&SetNetDevDel, "netdel", "D", "", "Delete the node's network device")
baseCmd.PersistentFlags().StringVar(&NetName, "netname", "default", "Set network name for network options")
baseCmd.PersistentFlags().BoolVarP(&SetNodeAll, "all", "a", false, "Set all nodes")

View File

@@ -14,6 +14,10 @@ import (
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
// run converters for different types
for _, c := range Converters {
c()
}
// remove the default network as the all network values are assigned
// to this network
if NetName != "" {

View File

@@ -25,12 +25,13 @@ var (
SetForce bool
NetName string
ProfileConf node.NodeConf
Converters []func()
)
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
ProfileConf = node.NewConf()
ProfileConf.CreateFlags(baseCmd,
Converters = ProfileConf.CreateFlags(baseCmd,
[]string{"ipaddr", "ipaddr6", "ipmiaddr", "profile"})
baseCmd.PersistentFlags().StringVar(&NetName, "netname", "", "Set network name for network options")
// register the command line completions

View File

@@ -15,7 +15,7 @@ var (
Use: "set [OPTIONS] [PROFILE ...]",
Short: "Configure node profile properties",
Long: "This command sets configuration properties for the node PROFILE(s).\n\n" +
"Note: use the string 'UNSET' to remove a configuration",
"Note: use the string 'UNSET'/'0.0.0.0' to remove a configuration",
Args: cobra.MinimumNArgs(0),
RunE: CobraRunE,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
@@ -38,11 +38,12 @@ var (
SetForce bool
NetName string
ProfileConf node.NodeConf
Converters []func()
)
func init() {
ProfileConf = node.NewConf()
ProfileConf.CreateFlags(baseCmd,
Converters = ProfileConf.CreateFlags(baseCmd,
[]string{"ipaddr", "ipaddr6", "ipmiaddr", "profile"})
baseCmd.PersistentFlags().StringVar(&NetName, "netname", "default", "Set network name for network options")
baseCmd.PersistentFlags().StringVarP(&SetNetDevDel, "netdel", "D", "", "Delete the node's network device")

View File

@@ -44,7 +44,7 @@ type NodeConf struct {
Init string `yaml:"init,omitempty" lopt:"init" sopt:"i" comment:"Define the init process to boot the container"`
Root string `yaml:"root,omitempty" lopt:"root" comment:"Define the rootfs" `
AssetKey string `yaml:"asset key,omitempty" lopt:"asset" comment:"Set the node's Asset tag (key)"`
Discoverable string `yaml:"discoverable,omitempty" lopt:"discoverable" comment:"Make discoverable in given network (yes/no)"`
Discoverable string `yaml:"discoverable,omitempty" lopt:"discoverable" sopt:"e" comment:"Make discoverable in given network (true/false)" type:"bool"`
Profiles []string `yaml:"profiles,omitempty" lopt:"profile" sopt:"P" comment:"Set the node's profile members (comma separated)"`
NetDevs map[string]*NetDevs `yaml:"network devices,omitempty"`
Tags map[string]string `yaml:"tags,omitempty" lopt:"tagadd" comment:"base key"`
@@ -56,12 +56,12 @@ type NodeConf struct {
type IpmiConf struct {
UserName string `yaml:"username,omitempty" lopt:"ipmiuser" comment:"Set the IPMI username"`
Password string `yaml:"password,omitempty" lopt:"ipmipass" comment:"Set the IPMI password"`
Ipaddr string `yaml:"ipaddr,omitempty" lopt:"ipmiaddr" comment:"Set the IPMI IP address"`
Netmask string `yaml:"netmask,omitempty" lopt:"ipminetmask" comment:"Set the IPMI netmask"`
Ipaddr string `yaml:"ipaddr,omitempty" lopt:"ipmiaddr" comment:"Set the IPMI IP address" type:"IP"`
Netmask string `yaml:"netmask,omitempty" lopt:"ipminetmask" comment:"Set the IPMI netmask" type:"IP"`
Port string `yaml:"port,omitempty" lopt:"ipmiport" comment:"Set the IPMI port"`
Gateway string `yaml:"gateway,omitempty" lopt:"ipmigateway" comment:"Set the IPMI gateway"`
Gateway string `yaml:"gateway,omitempty" lopt:"ipmigateway" comment:"Set the IPMI gateway" type:"IP"`
Interface string `yaml:"interface,omitempty" lopt:"ipmiinterface" comment:"Set the node's IPMI interface (defaults: 'lan')"`
Write string `yaml:"write,omitempty" lopt:"ipmiwrite" comment:"Enable the write of impi configuration (yes/no)"`
Write string `yaml:"write,omitempty" lopt:"ipmiwrite" comment:"Enable the write of impi configuration (true/false)" type:"bool"`
Tags map[string]string `yaml:"tags,omitempty" lopt:"ipmitagadd" comment:"add ipmitags"`
TagsDel []string `yaml:"tagsdel,omitempty" lopt:"ipmitagdel" comment:"remove ipmitags"` // should not go to disk only to wire
}
@@ -73,16 +73,18 @@ type KernelConf struct {
type NetDevs struct {
Type string `yaml:"type,omitempty" lopt:"type" sopt:"T" comment:"Set device type of given network"`
OnBoot string `yaml:"onboot,omitempty" lopt:"onboot" comment:"Enable/disable network device (yes/no)"`
OnBoot string `yaml:"onboot,omitempty" lopt:"onboot" comment:"Enable/disable network device (true/false)" type:"bool"`
Device string `yaml:"device,omitempty" lopt:"netdev" sopt:"N" comment:"Set the device for given network"`
Hwaddr string `yaml:"hwaddr,omitempty" lopt:"hwaddr" sopt:"H" comment:"Set the device's HW address for given network"`
Ipaddr string `yaml:"ipaddr,omitempty" comment:"IPv4 address in given network" sopt:"I" lopt:"ipaddr"`
Ipaddr string `yaml:"ipaddr,omitempty" comment:"IPv4 address in given network" sopt:"I" lopt:"ipaddr" type:"IP"`
IpCIDR string `yaml:"ipcidr,omitempty"`
Ipaddr6 string `yaml:"ip6addr,omitempty" lopt:"ipaddr6" comment:"IPv6 address"`
Ipaddr6 string `yaml:"ip6addr,omitempty" lopt:"ipaddr6" comment:"IPv6 address" type:"IP"`
Prefix string `yaml:"prefix,omitempty"`
Netmask string `yaml:"netmask,omitempty" lopt:"netmask" sopt:"M" comment:"Set the networks netmask"`
Gateway string `yaml:"gateway,omitempty" lopt:"gateway" sopt:"G" comment:"Set the node's network device gateway"`
Netmask string `yaml:"netmask,omitempty" lopt:"netmask" sopt:"M" comment:"Set the networks netmask" type:"IP"`
Gateway string `yaml:"gateway,omitempty" lopt:"gateway" sopt:"G" comment:"Set the node's network device gateway" type:"IP"`
MTU string `yaml:"mtu,omitempty" lopt:"mtu" comment:"Set the mtu"`
Primary string `yaml:"primary,omitempty" lopt:"primary" comment:"Enable/disable network device as primary (true/false)" type:"bool"`
Default string `yaml:"default,omitempty"` /* backward compatibility */
Tags map[string]string `yaml:"tags,omitempty" lopt:"nettagadd" comment:"network tags"`
TagsDel []string `yaml:"tagsdel,omitempty" lopt:"nettagdel" comment:"delete network tags"` // should not go to disk only to wire
}

200
internal/pkg/node/flags.go Normal file
View File

@@ -0,0 +1,200 @@
package node
import (
"net"
"os"
"reflect"
"strconv"
"strings"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
)
/*
Create cmd line flags from the NodeConf fields
*/
func (nodeConf *NodeConf) CreateFlags(baseCmd *cobra.Command, excludeList []string) (converters []func()) {
nodeInfoType := reflect.TypeOf(nodeConf)
nodeInfoVal := reflect.ValueOf(nodeConf)
// now iterate of every field
for i := 0; i < nodeInfoVal.Elem().NumField(); i++ {
if nodeInfoType.Elem().Field(i).Tag.Get("comment") != "" &&
!util.InSlice(excludeList, nodeInfoType.Elem().Field(i).Tag.Get("lopt")) {
field := nodeInfoVal.Elem().Field(i)
converters = append(converters, createFlags(baseCmd, excludeList, nodeInfoType.Elem().Field(i), &field)...)
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr {
nestType := reflect.TypeOf(nodeInfoVal.Elem().Field(i).Interface())
nestVal := reflect.ValueOf(nodeInfoVal.Elem().Field(i).Interface())
for j := 0; j < nestType.Elem().NumField(); j++ {
field := nestVal.Elem().Field(j)
converters = append(converters, createFlags(baseCmd, excludeList, nestType.Elem().Field(j), &field)...)
}
} else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string]*NetDevs(nil)) {
netMap := nodeInfoVal.Elem().Field(i).Interface().(map[string]*NetDevs)
// add a default network so that it can hold values
key := "default"
if len(netMap) == 0 {
netMap[key] = new(NetDevs)
} else {
for keyIt := range netMap {
key = keyIt
break
}
}
netType := reflect.TypeOf(netMap[key])
netVal := reflect.ValueOf(netMap[key])
for j := 0; j < netType.Elem().NumField(); j++ {
field := netVal.Elem().Field(j)
converters = append(converters, createFlags(baseCmd, excludeList, netType.Elem().Field(j), &field)...)
}
}
}
return converters
}
/*
Helper function to create the different PerisitantFlags() for different types.
*/
func createFlags(baseCmd *cobra.Command, excludeList []string,
myType reflect.StructField, myVal *reflect.Value) (converters []func()) {
if myType.Tag.Get("lopt") != "" {
if myType.Type.Kind() == reflect.String {
ptr := myVal.Addr().Interface().(*string)
switch myType.Tag.Get("type") {
case "uint":
defaultConv, _ := strconv.ParseUint(myType.Tag.Get("default"), 10, 32)
var valueRaw uint
converters = append(converters, func() { *ptr = strconv.FormatUint(uint64(valueRaw), 10) })
if myType.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().UintVarP(&valueRaw,
myType.Tag.Get("lopt"),
myType.Tag.Get("sopt"),
uint(defaultConv),
myType.Tag.Get("comment"))
} else {
baseCmd.PersistentFlags().UintVar(&valueRaw,
myType.Tag.Get("lopt"),
uint(defaultConv),
myType.Tag.Get("comment"))
}
case "bool":
/*
Can't use the bool var from pflag as we need the UNSET verbs to be passwd correctly
*/
converters = append(converters, func() {
if !util.InSlice(GetUnsetVerbs(), *ptr) && *ptr != "" {
if strings.ToLower(*ptr) != "yes" {
*ptr = "true"
return
}
if strings.ToLower(*ptr) != "no" {
*ptr = "false"
return
}
val, err := strconv.ParseBool(*ptr)
if err != nil {
wwlog.Error("commandline option %s needs to be bool", myType.Tag.Get("lopt"))
os.Exit(1)
}
*ptr = strconv.FormatBool(val)
}
})
if myType.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().StringVarP(ptr,
myType.Tag.Get("lopt"),
myType.Tag.Get("sopt"),
"",
myType.Tag.Get("comment"))
} else {
baseCmd.PersistentFlags().StringVar(ptr,
myType.Tag.Get("lopt"),
"",
myType.Tag.Get("comment"))
}
baseCmd.PersistentFlags().Lookup(myType.Tag.Get("lopt")).NoOptDefVal = "true"
case "IP":
defaultConv := net.ParseIP(myType.Tag.Get("default"))
var valueRaw net.IP
converters = append(converters, func() {
if valueRaw != nil {
*ptr = valueRaw.String()
}
})
if myType.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().IPVarP(&valueRaw,
myType.Tag.Get("lopt"),
myType.Tag.Get("sopt"),
defaultConv,
myType.Tag.Get("comment"))
} else {
baseCmd.PersistentFlags().IPVar(&valueRaw,
myType.Tag.Get("lopt"),
defaultConv,
myType.Tag.Get("comment"))
}
case "IPMask":
defaultConv := net.ParseIP(myType.Tag.Get("default")).DefaultMask()
var valueRaw net.IPMask
converters = append(converters, func() { *ptr = valueRaw.String() })
if myType.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().IPMaskVarP(&valueRaw,
myType.Tag.Get("lopt"),
myType.Tag.Get("sopt"),
defaultConv,
myType.Tag.Get("comment"))
} else {
baseCmd.PersistentFlags().IPMaskVar(&valueRaw,
myType.Tag.Get("lopt"),
defaultConv,
myType.Tag.Get("comment"))
}
default:
if myType.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().StringVarP(ptr,
myType.Tag.Get("lopt"),
myType.Tag.Get("sopt"),
myType.Tag.Get("default"),
myType.Tag.Get("comment"))
} else {
baseCmd.PersistentFlags().StringVar(ptr,
myType.Tag.Get("lopt"),
myType.Tag.Get("default"),
myType.Tag.Get("comment"))
}
}
} else if myType.Type == reflect.TypeOf([]string{}) {
ptr := myVal.Addr().Interface().(*[]string)
if myType.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().StringSliceVarP(ptr,
myType.Tag.Get("lopt"),
myType.Tag.Get("sopt"),
[]string{myType.Tag.Get("default")},
myType.Tag.Get("comment"))
} else if !util.InSlice(excludeList, myType.Tag.Get("lopt")) {
baseCmd.PersistentFlags().StringSliceVar(ptr,
myType.Tag.Get("lopt"),
[]string{myType.Tag.Get("default")},
myType.Tag.Get("comment"))
}
} else if myType.Type == reflect.TypeOf(map[string]string{}) {
ptr := myVal.Addr().Interface().(*map[string]string)
if myType.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().StringToStringVarP(ptr,
myType.Tag.Get("lopt"),
myType.Tag.Get("sopt"),
map[string]string{}, // empty default!
myType.Tag.Get("comment"))
} else if !util.InSlice(excludeList, myType.Tag.Get("lopt")) {
baseCmd.PersistentFlags().StringToStringVar(ptr,
myType.Tag.Get("lopt"),
map[string]string{}, // empty default!
myType.Tag.Get("comment"))
}
}
}
return converters
}

View File

@@ -10,6 +10,10 @@ import (
"github.com/hpcng/warewulf/internal/pkg/wwlog"
)
func GetUnsetVerbs() []string {
return []string{"UNSET", "DELETE", "UNDEF", "undef", "--", "nil", "0.0.0.0"}
}
/**********
*
* Filters
@@ -77,7 +81,7 @@ func (ent *Entry) Set(val string) {
return
}
if val == "UNDEF" || val == "DELETE" || val == "UNSET" || val == "--" || val == "nil" {
if util.InSlice(GetUnsetVerbs(), val) {
wwlog.Debug("Removing value for %v", *ent)
ent.value = []string{""}
} else {
@@ -102,7 +106,7 @@ func (ent *Entry) SetSlice(val []string) {
} else if len(val) == 1 && val[0] == "" { // check also for an "empty" slice
return
}
if val[0] == "UNDEF" || val[0] == "DELETE" || val[0] == "UNSET" || val[0] == "--" {
if util.InSlice(GetUnsetVerbs(), val[0]) {
ent.value = []string{}
} else {
ent.value = val

View File

@@ -6,7 +6,6 @@ import (
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
)
/*
@@ -194,102 +193,6 @@ func (nodeConf *NodeConf) getterFrom(nodeInfo NodeInfo,
}
}
/*
Create cmd line flags from the NodeConf fields
*/
func (nodeConf *NodeConf) CreateFlags(baseCmd *cobra.Command, excludeList []string) {
nodeInfoType := reflect.TypeOf(nodeConf)
nodeInfoVal := reflect.ValueOf(nodeConf)
// now iterate of every field
for i := 0; i < nodeInfoVal.Elem().NumField(); i++ {
if nodeInfoType.Elem().Field(i).Tag.Get("comment") != "" &&
!util.InSlice(excludeList, nodeInfoType.Elem().Field(i).Tag.Get("lopt")) {
field := nodeInfoVal.Elem().Field(i)
createFlags(baseCmd, excludeList, nodeInfoType.Elem().Field(i), &field)
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr {
nestType := reflect.TypeOf(nodeInfoVal.Elem().Field(i).Interface())
nestVal := reflect.ValueOf(nodeInfoVal.Elem().Field(i).Interface())
for j := 0; j < nestType.Elem().NumField(); j++ {
field := nestVal.Elem().Field(j)
createFlags(baseCmd, excludeList, nestType.Elem().Field(j), &field)
}
} else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string]*NetDevs(nil)) {
netMap := nodeInfoVal.Elem().Field(i).Interface().(map[string]*NetDevs)
// add a default network so that it can hold values
key := "default"
if len(netMap) == 0 {
netMap[key] = new(NetDevs)
} else {
for keyIt := range netMap {
key = keyIt
break
}
}
netType := reflect.TypeOf(netMap[key])
netVal := reflect.ValueOf(netMap[key])
for j := 0; j < netType.Elem().NumField(); j++ {
field := netVal.Elem().Field(j)
createFlags(baseCmd, excludeList, netType.Elem().Field(j), &field)
}
}
}
}
/*
Helper function to create the different PerisitantFlags() for different types.
*/
func createFlags(baseCmd *cobra.Command, excludeList []string,
myType reflect.StructField, myVal *reflect.Value) {
if myType.Tag.Get("lopt") != "" {
if myType.Type.Kind() == reflect.String {
ptr := myVal.Addr().Interface().(*string)
if myType.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().StringVarP(ptr,
myType.Tag.Get("lopt"),
myType.Tag.Get("sopt"),
myType.Tag.Get("default"),
myType.Tag.Get("comment"))
} else if !util.InSlice(excludeList, myType.Tag.Get("lopt")) {
baseCmd.PersistentFlags().StringVar(ptr,
myType.Tag.Get("lopt"),
myType.Tag.Get("default"),
myType.Tag.Get("comment"))
}
} else if myType.Type == reflect.TypeOf([]string{}) {
ptr := myVal.Addr().Interface().(*[]string)
if myType.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().StringSliceVarP(ptr,
myType.Tag.Get("lopt"),
myType.Tag.Get("sopt"),
[]string{myType.Tag.Get("default")},
myType.Tag.Get("comment"))
} else if !util.InSlice(excludeList, myType.Tag.Get("lopt")) {
baseCmd.PersistentFlags().StringSliceVar(ptr,
myType.Tag.Get("lopt"),
[]string{myType.Tag.Get("default")},
myType.Tag.Get("comment"))
}
} else if myType.Type == reflect.TypeOf(map[string]string{}) {
ptr := myVal.Addr().Interface().(*map[string]string)
if myType.Tag.Get("sopt") != "" {
baseCmd.PersistentFlags().StringToStringVarP(ptr,
myType.Tag.Get("lopt"),
myType.Tag.Get("sopt"),
map[string]string{}, // empty default!
myType.Tag.Get("comment"))
} else if !util.InSlice(excludeList, myType.Tag.Get("lopt")) {
baseCmd.PersistentFlags().StringToStringVar(ptr,
myType.Tag.Get("lopt"),
map[string]string{}, // empty default!
myType.Tag.Get("comment"))
}
}
}
}
/*
Populates all fields of NodeInfo with Set from the
values of NodeConf.