Merge branch 'RefactorFlags' into development

Signed-off-by: Christian Goll <cgoll@suse.de>
This commit is contained in:
Christian Goll
2023-03-23 11:24:47 +01:00
23 changed files with 940 additions and 184 deletions

View File

@@ -33,6 +33,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`wwctl --emptyconf genconfig warewulfconf print` for the available paths.
- Added experimental dnsmasq support.
- new subcommand `wwctl genconf` is available with following subcommands:
* `completions` which will create the files used for bash-completion. Also
fish an zsh completions can be generated
* `defaults` which will generate a valid `defaults.conf`
* `man` which will generate the man pages in the specified directory
* `reference` which will generate a reference documentation for the wwctl commands
* `warwulfconf print` which will print the used `warewulf.conf`. If there is no valid
`warewulf.conf` a valid configuration is provided, prefilled with default values
and an IP configuration derived from the network configuration of the host
- All paths can now be configured in `warewulf.conf`, check the paths section of of
`wwctl --emptyconf genconfig warewulfconf print` for the available paths.
- Added experimental dnsmasq support.
- new subcommand `wwctl genconf` is available with following subcommands:
* `completions` which will create the files used for bash-completion. Also
fish an zsh completions can be generated
* `defaults` which will generate a valid `defaults.conf`
* `man` which will generate the man pages in the specified directory
* `reference` which will generate a reference documentation for the wwctl commands
* `warwulfconf print` which will print the used `warewulf.conf`. If there is no valid
`warewulf.conf` a valid configuration is provided, prefilled with default values
and an IP configuration derived from the network configuration of the host
- All paths can now be configured in `warewulf.conf`, check the paths section of of
`wwctl --emptyconf genconfig warewulfconf print` for the available paths.
- Added experimental dnsmasq support.
- Check for formal correct IP and MAC addresses for command line options and
when reading in the configurations
## [4.4.0] 2023-01-18
### Added

View File

@@ -228,10 +228,11 @@ wwclient: $(WWCLIENT_DEPS)
-X 'github.com/hpcng/warewulf/internal/pkg/warewulfconf.ConfigFile=/etc/warewulf/warewulf.conf'" -o ../../wwclient
man_pages: wwctl
install -d man_pages
./wwctl --emptyconf genconfig man man_pages
cp docs/man/man5/*.5 ./man_pages/
cd man_pages; for i in wwctl*1 *.5; do echo "Compressing manpage: $$i"; gzip --force $$i; done
@install -d man_pages
@./wwctl --emptyconf genconfig man man_pages
@cp docs/man/man5/*.5 ./man_pages/
@echo -n "Compressing manpage: "
@cd man_pages; for i in wwctl*1 *.5; do gzip --force $$i; echo -n "$$i "; done; echo
update_configuration: vendor cmd/update_configuration/update_configuration.go
cd cmd/update_configuration && go build -ldflags="-X 'github.com/hpcng/warewulf/internal/pkg/warewulfconf.ConfigFile=./etc/warewulf.conf'\

View File

@@ -16,6 +16,12 @@ 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 vars.converters {
if err := c(); err != nil {
return err
}
}
// remove the default network as all network values are assigned
// to this network
if _, ok := vars.nodeConf.NetDevs["default"]; ok && vars.netName != "" {

View File

@@ -16,6 +16,7 @@ func Test_Add(t *testing.T) {
args []string
wantErr bool
stdout string
chkout bool
outDb string
}{
{name: "single node add",
@@ -39,6 +40,51 @@ nodes:
n01:
profiles:
- foo
`},
{name: "single node add, discoverable true, explicit",
args: []string{"--discoverable=true", "n01"},
wantErr: false,
stdout: "",
outDb: `WW_INTERNAL: 43
nodeprofiles: {}
nodes:
n01:
discoverable: "true"
profiles:
- default
`},
{name: "single node add, discoverable true with yes",
args: []string{"--discoverable=yes", "n01"},
wantErr: false,
stdout: "",
outDb: `WW_INTERNAL: 43
nodeprofiles: {}
nodes:
n01:
discoverable: "true"
profiles:
- default
`},
{name: "single node add, discoverable wrong argument",
args: []string{"--discoverable=maybe", "n01"},
wantErr: true,
stdout: "",
chkout: false,
outDb: `WW_INTERNAL: 43
nodeprofiles: {}
nodes: {}
`},
{name: "single node add, discoverable false",
args: []string{"--discoverable=false", "n01"},
wantErr: false,
stdout: "",
outDb: `WW_INTERNAL: 43
nodeprofiles: {}
nodes:
n01:
discoverable: "false"
profiles:
- default
`},
{name: "single node add with Kernel args",
args: []string{"--kernelargs=foo", "n01"},
@@ -94,6 +140,15 @@ nodes:
network devices:
default:
ipaddr: 10.0.0.1
`},
{name: "single node with malformed ipaddr",
args: []string{"--ipaddr=10.0.1", "n01"},
wantErr: true,
stdout: "",
chkout: false,
outDb: `WW_INTERNAL: 43
nodeprofiles: {}
nodes: {}
`},
{name: "three nodes with ipaddr",
args: []string{"--ipaddr=10.10.0.1", "n[01-02,03]"},
@@ -212,7 +267,7 @@ WW_INTERNAL: 43
t.Errorf("DB dump is wrong, got:'%s'\nwant:'%s'", dump, tt.outDb)
t.FailNow()
}
if buf.String() != tt.stdout {
if tt.chkout && buf.String() != tt.stdout {
t.Errorf("Got wrong output, got:'%s'\nwant:'%s'", buf.String(), tt.stdout)
t.FailNow()
}

View File

@@ -14,6 +14,7 @@ import (
type variables struct {
netName string
nodeConf node.NodeConf
converters []func() error
}
// 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

@@ -77,7 +77,30 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
// ignore error as only may occurs under strange circumstances
buffer, _ := io.ReadAll(file)
err = yaml.Unmarshal(buffer, modifiedNodeMap)
if err == nil {
if err != nil {
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", err))
if yes {
continue
} else {
break
}
}
var checkErrors []error
for nodeName, node := range modifiedNodeMap {
err = node.Check()
if err != nil {
checkErrors = append(checkErrors, fmt.Errorf("node: %s parse error: %s", nodeName, err))
}
}
if len(checkErrors) != 0 {
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", checkErrors))
if yes {
continue
} else {
break
}
}
nodeList := make([]string, len(nodeMap))
i := 0
for key := range nodeMap {
@@ -85,9 +108,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
i++
}
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes", len(modifiedNodeMap)))
if !yes {
break
}
if yes {
err = apinode.NodeDelete(&wwapiv1.NodeDeleteParameter{NodeNames: nodeList, Force: true})
if err != nil {
wwlog.Verbose("Problem deleting nodes before modification %s")
@@ -99,11 +120,6 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
os.Exit(1)
}
break
} else {
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", err))
if !yes {
break
}
}
} else {
break

View File

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

View File

@@ -38,11 +38,12 @@ var (
SetYes bool
SetForce bool
NodeConf node.NodeConf
Converters []func() error
)
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,12 @@ import (
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
// run converters for different types
for _, c := range Converters {
if err := c(); err != nil {
return err
}
}
// 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() error
)
// 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

@@ -71,24 +71,44 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
sum2 := hex.EncodeToString(hasher.Sum(nil))
wwlog.Debug("Hashes are before %s and after %s\n", sum1, sum2)
if sum1 != sum2 {
wwlog.Debug("Nodes were modified")
wwlog.Debug("Profiles were modified")
modifiedProfileMap := make(map[string]*node.NodeConf)
_, _ = file.Seek(0, 0)
// ignore error as only may occurs under strange circumstances
buffer, _ := io.ReadAll(file)
err = yaml.Unmarshal(buffer, modifiedProfileMap)
if err == nil {
nodeList := make([]string, len(profileMap))
if err != nil {
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", err))
if yes {
continue
} else {
break
}
}
var checkErrors []error
for nodeName, node := range modifiedProfileMap {
err = node.Check()
if err != nil {
checkErrors = append(checkErrors, fmt.Errorf("profile: %s parse error: %s", nodeName, err))
}
}
if len(checkErrors) != 0 {
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", checkErrors))
if yes {
continue
} else {
break
}
}
pList := make([]string, len(profileMap))
i := 0
for key := range profileMap {
nodeList[i] = key
pList[i] = key
i++
}
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes", len(modifiedProfileMap)))
if !yes {
break
}
err = apiprofile.ProfileDelete(&wwapiv1.NodeDeleteParameter{NodeNames: nodeList, Force: true})
if yes {
err = apiprofile.ProfileDelete(&wwapiv1.NodeDeleteParameter{NodeNames: pList, Force: true})
if err != nil {
wwlog.Verbose("Problem deleting nodes before modification %s")
}
@@ -99,11 +119,6 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
os.Exit(1)
}
break
} else {
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", err))
if !yes {
break
}
}
} else {
break

View File

@@ -38,11 +38,12 @@ var (
SetForce bool
NetName string
ProfileConf node.NodeConf
Converters []func() error
)
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

@@ -60,6 +60,10 @@ func NodeAddFromYaml(nodeList *wwapiv1.NodeYaml) (err error) {
return errors.Wrap(err, "Could not unmarshall Yaml: %s\n")
}
for nodeName, node := range nodeMap {
err = node.Check()
if err != nil {
return errors.Errorf("error on node %s: %s", nodeName, err)
}
nodeDB.Nodes[nodeName] = node
}
err = nodeDB.Persist()

View File

@@ -50,6 +50,12 @@ func NodeAdd(nap *wwapiv1.NodeAddParameter) (err error) {
// only key
}
// setting node from the received yaml
err = nodeConf.Check()
if err != nil {
err = fmt.Errorf("error on check of node %s: %s", n.Id.Get(), err)
return
}
n.SetFrom(&nodeConf)
if netName != "" && nodeConf.NetDevs[netName].Ipaddr != "" {
// if more nodes are added increment IPv4 address
@@ -236,6 +242,12 @@ func NodeSetParameterCheck(set *wwapiv1.NodeSetParameter, console bool) (nodeDB
wwlog.Error(fmt.Sprintf("%v", err.Error()))
return
}
err = nodeConf.Check()
if err != nil {
err = fmt.Errorf("error on check of node %s: %s", n.Id.Get(), err)
return
}
n.SetFrom(&nodeConf)
if set.NetdevDelete != "" {
if _, ok := n.NetDevs[set.NetdevDelete]; !ok {

View File

@@ -0,0 +1,97 @@
package node
import (
"fmt"
"net"
"reflect"
"strconv"
"strings"
"github.com/hpcng/warewulf/internal/pkg/util"
)
/*
Checks if for NodeConf all values can be parsed according to their type.
*/
func (nodeConf *NodeConf) Check() (err error) {
nodeInfoType := reflect.TypeOf(nodeConf)
nodeInfoVal := reflect.ValueOf(nodeConf)
// now iterate of every field
for i := 0; i < nodeInfoVal.Elem().NumField(); i++ {
//wwlog.Debug("checking field: %s type: %s", nodeInfoType.Elem().Field(i).Name, nodeInfoVal.Elem().Field(i).Type())
if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.String {
newFmt, err := checker(nodeInfoVal.Elem().Field(i).Interface().(string), nodeInfoType.Elem().Field(i).Tag.Get("type"))
if err != nil {
return fmt.Errorf("field: %s value:%s err: %s", nodeInfoType.Elem().Field(i).Name, nodeInfoVal.Elem().Field(i).String(), err)
} else if newFmt != "" {
nodeInfoVal.Elem().Field(i).SetString(newFmt)
}
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr && !nodeInfoVal.Elem().Field(i).IsNil() {
nestType := reflect.TypeOf(nodeInfoVal.Elem().Field(i).Interface())
nestVal := reflect.ValueOf(nodeInfoVal.Elem().Field(i).Interface())
for j := 0; j < nestType.Elem().NumField(); j++ {
if nestType.Elem().Field(j).Type.Kind() == reflect.String {
//wwlog.Debug("checking field: %s type: %s", nestType.Elem().Field(j).Name, nestType.Elem().Field(j).Tag.Get("type"))
newFmt, err := checker(nestVal.Elem().Field(j).Interface().(string), nestType.Elem().Field(j).Tag.Get("type"))
if err != nil {
return fmt.Errorf("field: %s value:%s err: %s", nestType.Elem().Field(j).Name, nestVal.Elem().Field(j).String(), err)
} else if newFmt != "" {
nestVal.Elem().Field(j).SetString(newFmt)
}
}
}
} else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string]*NetDevs(nil)) {
netMap := nodeInfoVal.Elem().Field(i).Interface().(map[string]*NetDevs)
for _, val := range netMap {
netType := reflect.TypeOf(val)
netVal := reflect.ValueOf(val)
for j := 0; j < netType.Elem().NumField(); j++ {
newFmt, err := checker(netVal.Elem().Field(j).String(), netType.Elem().Field(j).Tag.Get("type"))
if err != nil {
return fmt.Errorf("field: %s value:%s err: %s", netType.Elem().Field(j).Name, netVal.Elem().Field(j).String(), err)
} else if newFmt != "" {
netVal.Elem().Field(j).SetString(newFmt)
}
}
}
}
}
return nil
}
func checker(value string, valType string) (niceValue string, err error) {
if valType == "" || value == "" || util.InSlice(GetUnsetVerbs(), value) {
return "", nil
}
//wwlog.Debug("checker: %s is %s", value, valType)
switch valType {
case "":
return "", nil
case "bool":
if strings.ToLower(value) == "yes" {
return "true", nil
}
if strings.ToLower(value) == "no" {
return "false", nil
}
myBool, err := strconv.ParseBool(value)
return strconv.FormatBool(myBool), err
case "IP":
if addr := net.ParseIP(value); addr == nil {
return "", fmt.Errorf("%s can't be parsed to ip address", value)
} else {
return addr.String(), nil
}
case "MAC":
if mac, err := net.ParseMAC(value); err != nil {
return "", fmt.Errorf("%s can't be parsed to MAC address: %s", value, err)
} else {
return mac.String(), nil
}
case "uint":
if _, err := strconv.ParseUint(value, 10, 64); err != nil {
return "", fmt.Errorf("%s is not a uint: %s", value, err)
}
}
return "", nil
}

View File

@@ -2,6 +2,7 @@ package node
import (
"errors"
"fmt"
"os"
"path"
"sort"
@@ -71,6 +72,21 @@ func New() (NodeYaml, error) {
if err != nil {
return ret, err
}
wwlog.Debug("Checking nodes for types")
for nodeName, node := range ret.Nodes {
err = node.Check()
if err != nil {
wwlog.Warn("node: %s parsing error: %s", nodeName, err)
return ret, err
}
}
for profileName, profile := range ret.NodeProfiles {
err = profile.Check()
if err != nil {
wwlog.Warn("node: %s parsing error: %s", profileName, err)
return ret, err
}
}
wwlog.Debug("Returning node object")
cachedDB = ret
@@ -119,12 +135,13 @@ func (config *NodeYaml) FindAllNodes() ([]NodeInfo, error) {
defData, err := os.ReadFile(DefaultConfig)
if err != nil {
wwlog.Verbose("Couldn't read DefaultConfig :%s\n", err)
wwlog.Verbose("Using building defaults")
defData = []byte(FallBackConf)
}
wwlog.Debug("Unmarshalling default config\n")
err = yaml.Unmarshal(defData, &defConf)
if err != nil {
wwlog.Verbose("Couldn't unmarshall defaults from file :%s\n", err)
wwlog.Verbose("Using building defaults")
err = yaml.Unmarshal([]byte(FallBackConf), &defConf)
if err != nil {
wwlog.Warn("Could not get any defaults")
@@ -165,6 +182,10 @@ func (config *NodeYaml) FindAllNodes() ([]NodeInfo, error) {
node.Tags[keyname] = key
delete(node.Keys, keyname)
}
err = node.Check()
if err != nil {
return nil, fmt.Errorf("node: %s check error: %s", nodename, err)
}
n.SetFrom(node)
// only now the netdevs start to exist so that default values can be set
for _, netdev := range n.NetDevs {

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"`
Hwaddr string `yaml:"hwaddr,omitempty" lopt:"hwaddr" sopt:"H" comment:"Set the device's HW address for given network" type:"MAC"`
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"`
MTU string `yaml:"mtu,omitempty" lopt:"mtu" comment:"Set the mtu"`
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" type:"uint"`
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
}

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

@@ -0,0 +1,240 @@
package node
import (
"fmt"
"net"
"reflect"
"strconv"
"strings"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/spf13/cobra"
)
/*
Create cmd line flags from the NodeConf fields. Returns a []func() where every function
must be called, as the commandline parser returns e.g. netip.IP objects which must be parsedf
back to strings.
*/
func (nodeConf *NodeConf) CreateFlags(baseCmd *cobra.Command, excludeList []string) (converters []func() error) {
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() error) {
if myType.Tag.Get("lopt") != "" {
if myType.Type.Kind() == reflect.String {
ptr := myVal.Addr().Interface().(*string)
switch myType.Tag.Get("type") {
case "uint":
converters = append(converters, func() error {
if !util.InSlice(GetUnsetVerbs(), *ptr) && *ptr != "" {
_, err := strconv.ParseUint(myType.Tag.Get(*ptr), 10, 32)
if err != nil {
return err
}
}
return nil
})
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"))
}
case "bool":
/*
Can't use the bool var from pflag as we need the UNSET verbs to be passwd correctly
*/
converters = append(converters, func() error {
if !util.InSlice(GetUnsetVerbs(), *ptr) && *ptr != "" {
if strings.ToLower(*ptr) == "yes" {
*ptr = "true"
return nil
}
if strings.ToLower(*ptr) == "no" {
*ptr = "false"
return nil
}
val, err := strconv.ParseBool(*ptr)
if err != nil {
return fmt.Errorf("commandline option %s needs to be bool", myType.Tag.Get("lopt"))
}
*ptr = strconv.FormatBool(val)
}
return nil
})
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":
converters = append(converters, func() error {
if !util.InSlice(GetUnsetVerbs(), *ptr) && *ptr != "" {
ipval := net.ParseIP(*ptr)
if ipval == nil {
return fmt.Errorf("commandline option %s needs to be an IP address", myType.Tag.Get("lopt"))
}
*ptr = ipval.String()
}
return nil
})
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"))
}
case "IPMask":
defaultConv := net.ParseIP(myType.Tag.Get("default")).DefaultMask()
var valueRaw net.IPMask
converters = append(converters, func() error {
if valueRaw != nil {
*ptr = valueRaw.String()
return nil
} else {
return fmt.Errorf("could not parse %s to IP", 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"))
}
case "MAC":
converters = append(converters, func() error {
if !util.InSlice(GetUnsetVerbs(), *ptr) && *ptr != "" {
myMac, err := net.ParseMAC(*ptr)
if err != nil {
return err
}
*ptr = myMac.String()
}
return nil
})
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"))
}
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
@@ -352,8 +356,10 @@ Create an empty node NodeConf
*/
func NewConf() (nodeconf NodeConf) {
nodeconf.Ipmi = new(IpmiConf)
nodeconf.Ipmi.Tags = map[string]string{}
nodeconf.Kernel = new(KernelConf)
nodeconf.NetDevs = make(map[string]*NetDevs)
nodeconf.Tags = map[string]string{}
return nodeconf
}
@@ -362,11 +368,18 @@ Create an empty node NodeInfo
*/
func NewInfo() (nodeInfo NodeInfo) {
nodeInfo.Ipmi = new(IpmiEntry)
nodeInfo.Ipmi.Tags = map[string]*Entry{}
nodeInfo.Kernel = new(KernelEntry)
nodeInfo.NetDevs = make(map[string]*NetDevEntry)
nodeInfo.Tags = make(map[string]*Entry)
return nodeInfo
}
func NewNetDevEntry() (netdev NetDevEntry) {
netdev.Tags = make(map[string]*Entry)
return
}
/*
Get a entry by its name
*/

View File

@@ -2,36 +2,351 @@ package node
import (
"reflect"
"strconv"
"testing"
"gopkg.in/yaml.v2"
)
func NewTransformerTestNode() NodeYaml {
var data = `
nodeprofiles:
default:
comment: This profile is automatically included for each node
ipmi:
username: greg
profile2:
tags:
foo: foo profile2
comment: Comment profile2
ipmi:
tags:
foo: foo ipmi profile
nodes:
test_node1:
comment: Node Comment
profiles:
- default
network devices:
net0:
device: eth1
discoverable: true
ipmi:
username: chris
tags:
baar: baar node1
test_node2:
primary: net0
profiles:
- default
- profile2
network devices:
net0:
netmask: 1.1.1.1
net1:
ipaddr: 1.2.3.4
tags:
baar: baar node2
test_node3:
profiles:
- profile2
tags:
foo: foo node3
foobaar: foobaar node3
ipmi:
ipaddr: 1.1.1.1
tags:
foo: foo ipmi node3
`
var ret NodeYaml
_ = yaml.Unmarshal([]byte(data), &ret)
return ret
}
func Test_nodeYaml_SetFrom(t *testing.T) {
c, _ := NewTestNode()
singleNodeConf := c.Nodes["test_node"]
singleNodeInfo := NewInfo()
singleNodeInfo.SetFrom(singleNodeConf)
tests := []struct {
c := NewTransformerTestNode()
nodes, _ := c.FindAllNodes()
test_node1 := NewInfo()
test_node2 := NewInfo()
test_node3 := NewInfo()
test_node4 := NewInfo()
for _, n := range nodes {
if n.Id.Get() == "test_node1" {
test_node1 = n
}
if n.Id.Get() == "test_node2" {
test_node2 = n
}
if n.Id.Get() == "test_node3" {
test_node3 = n
}
}
getByNametests := []struct {
name string
arg string
want string
wantErr bool
}{
{"Right comment", "Comment", "Node Comment", false},
{"FieldName", "comment", "NodeComment", true},
{"GetByName: FieldValue", "Comment", "Node Comment", false},
{"GetByName: FieldName", "comment", "NodeComment", true},
}
for _, tt := range tests {
for _, tt := range getByNametests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetByName(&singleNodeInfo, tt.arg)
got, err := GetByName(&test_node1, tt.arg)
if (err != nil) != tt.wantErr {
t.Errorf("GetByName(%s,%s) error = %v, wantErr %v",
reflect.TypeOf(singleNodeConf), tt.arg, err, tt.wantErr)
reflect.TypeOf(test_node1), tt.arg, err, tt.wantErr)
return
}
if (got != tt.want) != tt.wantErr {
t.Errorf("GetByName(%s,%s) got = %v, want = %v",
reflect.TypeOf(singleNodeConf), tt.arg, got, tt.want)
reflect.TypeOf(test_node1), tt.arg, got, tt.want)
return
}
})
}
t.Run("Get() comment", func(t *testing.T) {
comment := test_node1.Comment.Get()
if comment != "Node Comment" {
t.Errorf("Get() returned wrong comment: %s", comment)
}
})
t.Run("Get() profile comment", func(t *testing.T) {
comment := test_node2.Comment.Get()
if comment != "Comment profile2" {
t.Errorf("Get() returned wrong comment: %s", comment)
}
})
t.Run("Get() default ipxe", func(t *testing.T) {
value := test_node1.Ipxe.Get()
if value != "default" {
t.Errorf("Get() returned wrong ipxe template: %s", value)
}
})
t.Run("GetSlice() default profile", func(t *testing.T) {
value := test_node1.Profiles.GetSlice()[0]
if value != "default" {
t.Errorf("GetSlice() returned wrong profile: %s", value)
}
})
t.Run("Get() default kernel args", func(t *testing.T) {
value := test_node1.Kernel.Args.Get()
if value != "quiet crashkernel=no vga=791 net.naming-scheme=v238" {
t.Errorf("Get() returned wrong kernel args: %s", value)
}
})
t.Run("Get() default network mask", func(t *testing.T) {
value := test_node1.NetDevs["net0"].Netmask.Get()
if value != "255.255.255.0" {
t.Errorf("Get() returned wrong default netmask: %s", value)
}
})
t.Run("Get() default network mask", func(t *testing.T) {
value := test_node2.NetDevs["net0"].Netmask.Get()
if value != "1.1.1.1" {
t.Errorf("Get() returned wrong default netmask: %s", value)
}
})
t.Run("GetB() primary for single network", func(t *testing.T) {
value := test_node1.NetDevs["net0"].Primary.GetB()
if !value {
t.Errorf("GetB() returned wrong: %s", strconv.FormatBool(value))
}
})
t.Run("GetB() for primary with two networks", func(t *testing.T) {
value := test_node2.NetDevs["net0"].Primary.GetB()
if !value {
t.Errorf("GetB() returned wrong: %s", strconv.FormatBool(value))
}
})
t.Run("GetB() for primary with two networks, get secondary network", func(t *testing.T) {
value := test_node2.NetDevs["net1"].Primary.GetB()
if value {
t.Errorf("GetB() returned wrong: %s", strconv.FormatBool(value))
}
})
t.Run("GetB() default discoverable", func(t *testing.T) {
value := test_node1.Discoverable.GetB()
if !value {
t.Errorf("GetB() returned wrong: %s", strconv.FormatBool(value))
}
})
t.Run("GetB() default discoverable", func(t *testing.T) {
value := test_node2.Discoverable.GetB()
if value {
t.Errorf("GetB() returned wrong: %s", strconv.FormatBool(value))
}
})
t.Run("Get() ipmi user from profile", func(t *testing.T) {
value := test_node2.Ipmi.UserName.Get()
if value != "greg" {
t.Errorf("Get() returned wrong ipmi username: %s", value)
}
})
t.Run("Get() ipmi user from node", func(t *testing.T) {
value := test_node1.Ipmi.UserName.Get()
if value != "chris" {
t.Errorf("Get() returned wrong ipmi username: %s", value)
}
})
t.Run("Get() tag foo from profile, node does not have this tag", func(t *testing.T) {
value := test_node2.Tags["foo"].Get()
if value != "foo profile2" {
t.Errorf("Get() returned wrong tag for foo: %s", value)
}
})
t.Run("Get() tag baar from node, node tag map is not overwritten", func(t *testing.T) {
value := test_node2.Tags["baar"].Get()
if value != "baar node2" {
t.Errorf("Get() returned wrong tag for foo: %s", value)
}
})
t.Run("Get() tag foo from node, tag present in profile", func(t *testing.T) {
value := test_node3.Tags["foo"].Get()
if value != "foo node3" {
t.Errorf("Get() returned wrong tag for foo: %s", value)
}
})
t.Run("Get() tag foobaar from node", func(t *testing.T) {
value := test_node3.Tags["foobaar"].Get()
if value != "foobaar node3" {
t.Errorf("Get() returned wrong tag for foo: %s", value)
}
})
t.Run("Get() ipmitag foo from profile, node does not have this tag", func(t *testing.T) {
value := test_node3.Ipmi.Tags["foo"].Get()
if value != "foo ipmi node3" {
t.Errorf("Get() returned wrong tag for foo: %s", value)
}
})
t.Run("Set() comment foo for empty node", func(t *testing.T) {
test_node4.Comment.Set("foo")
nodeConf := NewConf()
nodeConf.GetFrom(test_node4)
ymlByte, _ := yaml.Marshal(nodeConf)
wanted := `comment: foo
kernel: {}
ipmi: {}
`
if !(wanted == string(ymlByte)) {
t.Errorf("Got wrong yml, wanted:\n'%s'\nGot:\n'%s'", wanted, string(ymlByte))
}
// have to remove the comment for further tests, as vscode
// can test single functions
test_node4.Comment.Set("UNDEF")
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
ymlByte, _ = yaml.Marshal(nodeConf)
wanted = `{}
`
if string(ymlByte) != wanted {
t.Errorf("Couldn't unset comment:\n'%s'\nwanted:\n'%s'", string(ymlByte), wanted)
}
})
t.Run("Set() ipmiuser foo for flattened empty node", func(t *testing.T) {
test_node4.Ipmi.UserName.Set("foo")
nodeConf := NewConf()
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
ymlByte, _ := yaml.Marshal(nodeConf)
wanted := `ipmi:
username: foo
`
if !(wanted == string(ymlByte)) {
t.Errorf("Got wrong yml, wanted:\n'%s'\nGot:\n'%s'", wanted, string(ymlByte))
}
test_node4.Ipmi.Tags["foo"] = &Entry{}
test_node4.Ipmi.Tags["foo"].Set("baar")
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
ymlByte, _ = yaml.Marshal(nodeConf)
wanted = `ipmi:
username: foo
tags:
foo: baar
`
if !(wanted == string(ymlByte)) {
t.Errorf("Got wrong yml, wanted:\n'%s'\nGot:\n'%s'", wanted, string(ymlByte))
}
test_node4.Ipmi.UserName.Set("UNSET")
delete(test_node4.Ipmi.Tags, "foo")
})
t.Run("Set() kernelargs foo for flattened empty node", func(t *testing.T) {
test_node4.Kernel.Args.Set("foo")
nodeConf := NewConf()
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
ymlByte, _ := yaml.Marshal(nodeConf)
wanted := `kernel:
args: foo
`
if !(wanted == string(ymlByte)) {
t.Errorf("Got wrong yml, wanted:\n'%s'\nGot:\n'%s'", wanted, string(ymlByte))
}
test_node4.Kernel.Args.Set("--")
})
t.Run("Set() tag foo to bar for flattened empty node", func(t *testing.T) {
test_node4.Tags["foo"] = &Entry{}
test_node4.Tags["foo"].Set("baar")
nodeConf := NewConf()
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
ymlByte, _ := yaml.Marshal(nodeConf)
wanted := `tags:
foo: baar
`
if !(wanted == string(ymlByte)) {
t.Errorf("Got wrong yml, wanted:\n'%s'\nGot:\n'%s'", wanted, string(ymlByte))
}
delete(test_node4.Tags, "foo")
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
ymlByte, _ = yaml.Marshal(nodeConf)
wanted = `{}
`
if string(ymlByte) != wanted {
t.Errorf("Couldn't remove tag'%s'", string(ymlByte))
}
})
t.Run("Set() netdev foo with device name baar for flattened empty node", func(t *testing.T) {
netdev := NewNetDevEntry()
test_node4.NetDevs["foo"] = &netdev
test_node4.NetDevs["foo"].Device.Set("baar")
nodeConf := NewConf()
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
ymlByte, _ := yaml.Marshal(nodeConf)
wanted := `network devices:
foo:
device: baar
`
if !(wanted == string(ymlByte)) {
t.Errorf("Got wrong yml, wanted:\n'%s'\nGot:\n'%s'", wanted, string(ymlByte))
}
test_node4.NetDevs["foo"].Tags["netfoo"] = &Entry{}
test_node4.NetDevs["foo"].Tags["netfoo"].Set("netbaar")
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
wanted = `network devices:
foo:
device: baar
tags:
netfoo: netbaar
`
ymlByte, _ = yaml.Marshal(nodeConf)
if string(ymlByte) != wanted {
t.Errorf("Couldn set nettag: '%s' got: '%s'", wanted, string(ymlByte))
}
delete(test_node4.NetDevs, "foo")
nodeConf.GetFrom(test_node4)
nodeConf.Flatten()
ymlByte, _ = yaml.Marshal(nodeConf)
wanted = `{}
`
if string(ymlByte) != wanted {
t.Errorf("Couldn't remove tag'%s'", string(ymlByte))
}
})
}

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.
@@ -363,24 +266,28 @@ func (node *NodeInfo) setterFrom(n *NodeConf, nameArg string,
}
} else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr && !valField.IsZero() {
nestedInfoType := reflect.TypeOf(nodeInfoVal.Elem().Field(i).Interface())
netstedInfoVal := reflect.ValueOf(nodeInfoVal.Elem().Field(i).Interface())
nestedInfoVal := reflect.ValueOf(nodeInfoVal.Elem().Field(i).Interface())
nestedConfVal := reflect.ValueOf(valField.Interface())
for j := 0; j < nestedInfoType.Elem().NumField(); j++ {
nestedVal := nestedConfVal.Elem().FieldByName(nestedInfoType.Elem().Field(j).Name)
if nestedVal.IsValid() {
if netstedInfoVal.Elem().Field(j).Type() == reflect.TypeOf(Entry{}) {
setter(netstedInfoVal.Elem().Field(j).Addr().Interface().(*Entry), nestedVal.String(), nameArg)
} else {
if nestedInfoVal.Elem().Field(j).Type() == reflect.TypeOf(Entry{}) {
setter(nestedInfoVal.Elem().Field(j).Addr().Interface().(*Entry), nestedVal.String(), nameArg)
} else if nestedInfoVal.Elem().Field(j).Type() == reflect.TypeOf(map[string](*Entry){}) {
confMap := nestedVal.Interface().(map[string]string)
if netstedInfoVal.Elem().Field(j).IsNil() {
newMap := make(map[string]*Entry)
mapPtr := (netstedInfoVal.Elem().Field(j).Addr().Interface()).(*map[string](*Entry))
*mapPtr = newMap
if nestedInfoVal.Elem().Field(j).IsNil() {
ptr := nestedInfoVal.Elem().Field(j).Addr().Interface().(*map[string](*Entry))
*ptr = make(map[string]*Entry)
}
tagMap := nestedInfoVal.Elem().Field(j).Interface().(map[string](*Entry))
for key, val := range confMap {
entr := new(Entry)
if entr, ok := tagMap[key]; ok {
setter(entr, val, nameArg)
(netstedInfoVal.Elem().Field(j).Interface()).(map[string](*Entry))[key] = entr
} else {
entr := new(Entry)
tagMap[key] = entr
setter(entr, val, nameArg)
}
}
}
}
@@ -388,9 +295,17 @@ func (node *NodeInfo) setterFrom(n *NodeConf, nameArg string,
} else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string](*Entry)(nil)) {
confMap := valField.Interface().(map[string]string)
for key, val := range confMap {
entr := new(Entry)
tagMap := nodeInfoVal.Elem().Field(i).Interface().(map[string](*Entry))
if nodeInfoVal.Elem().Field(i).IsNil() {
tagMap = make(map[string]*Entry)
}
if entr, ok := tagMap[key]; ok {
setter(entr, val, nameArg)
(nodeInfoVal.Elem().Field(i).Interface()).(map[string](*Entry))[key] = entr
} else {
entr := new(Entry)
tagMap[key] = entr
setter(entr, val, nameArg)
}
}
} else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf(map[string](*NetDevEntry)(nil)) {
netValMap := valField.Interface().(map[string](*NetDevs))

View File

@@ -27,10 +27,11 @@ func (config *NodeYaml) FindByHwaddr(hwa string) (NodeInfo, error) {
}
func (config *NodeYaml) FindByIpaddr(ipaddr string) (NodeInfo, error) {
if net.ParseIP(ipaddr) == nil {
if addr := net.ParseIP(ipaddr); addr == nil {
return NodeInfo{}, errors.New("invalid IP:" + ipaddr)
} else {
ipaddr = addr.String()
}
var ret NodeInfo
n, _ := config.FindAllNodes()

View File

@@ -6,7 +6,7 @@ import (
"gopkg.in/yaml.v2"
)
func NewTestNode() (NodeYaml, error) {
func NewUtilTestNode() (NodeYaml, error) {
var data = `
nodeprofiles:
default:
@@ -49,7 +49,7 @@ nodes:
}
func Test_nodeYaml_FindByHwaddr(t *testing.T) {
c, _ := NewTestNode()
c, _ := NewUtilTestNode()
//type fields struct {
// NodeProfiles map[string]*NodeConf
// Nodes map[string]*NodeConf
@@ -90,7 +90,7 @@ func Test_nodeYaml_FindByHwaddr(t *testing.T) {
}
func Test_nodeYaml_FindByIpaddr(t *testing.T) {
c, _ := NewTestNode()
c, _ := NewUtilTestNode()
type args struct {
ipaddr string
}