diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd7d4b4..09ae5c9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 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 diff --git a/internal/app/wwctl/node/set/root.go b/internal/app/wwctl/node/set/root.go index a5ab4df4..ac4c1f14 100644 --- a/internal/app/wwctl/node/set/root.go +++ b/internal/app/wwctl/node/set/root.go @@ -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'/'0.0.0.0' to remove a configuration", + Long: "This command sets configuration properties for nodes matching PATTERN.\n\nNote: use the string 'UNSET' to remove a configuration", Args: cobra.MinimumNArgs(0), RunE: CobraRunE, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/internal/app/wwctl/profile/set/root.go b/internal/app/wwctl/profile/set/root.go index 9fc70f86..0c1fd56b 100644 --- a/internal/app/wwctl/profile/set/root.go +++ b/internal/app/wwctl/profile/set/root.go @@ -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'/'0.0.0.0' to remove a configuration", + "Note: use the string 'UNSET' to remove a configuration", Args: cobra.MinimumNArgs(0), RunE: CobraRunE, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/internal/pkg/node/checkconf.go b/internal/pkg/node/checkconf.go index a595a1c5..a60acdf0 100644 --- a/internal/pkg/node/checkconf.go +++ b/internal/pkg/node/checkconf.go @@ -6,6 +6,8 @@ import ( "reflect" "strconv" "strings" + + "github.com/hpcng/warewulf/internal/pkg/util" ) /* @@ -58,7 +60,7 @@ func (nodeConf *NodeConf) Check() (err error) { } func checker(value string, valType string) (niceValue string, err error) { - if valType == "" || value == "" { + if valType == "" || value == "" || util.InSlice(GetUnsetVerbs(), value) { return "", nil } //wwlog.Debug("checker: %s is %s", value, valType) diff --git a/internal/pkg/node/flags.go b/internal/pkg/node/flags.go index 4804f7c9..5196135f 100644 --- a/internal/pkg/node/flags.go +++ b/internal/pkg/node/flags.go @@ -122,25 +122,26 @@ func createFlags(baseCmd *cobra.Command, excludeList []string, } 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() error { - if valueRaw != nil { - // will always get a IP, not a string - *ptr = valueRaw.String() + 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().IPVarP(&valueRaw, + baseCmd.PersistentFlags().StringVarP(ptr, myType.Tag.Get("lopt"), myType.Tag.Get("sopt"), - defaultConv, + myType.Tag.Get("default"), myType.Tag.Get("comment")) } else { - baseCmd.PersistentFlags().IPVar(&valueRaw, + baseCmd.PersistentFlags().StringVar(ptr, myType.Tag.Get("lopt"), - defaultConv, + myType.Tag.Get("default"), myType.Tag.Get("comment")) } case "IPMask": @@ -168,11 +169,13 @@ func createFlags(baseCmd *cobra.Command, excludeList []string, } case "MAC": converters = append(converters, func() error { - myMac, err := net.ParseMAC(*ptr) - if err != nil { - return err + if !util.InSlice(GetUnsetVerbs(), *ptr) && *ptr != "" { + myMac, err := net.ParseMAC(*ptr) + if err != nil { + return err + } + *ptr = myMac.String() } - *ptr = myMac.String() return nil }) if myType.Tag.Get("sopt") != "" { diff --git a/internal/pkg/node/methods.go b/internal/pkg/node/methods.go index f9deb492..c3ed61e2 100644 --- a/internal/pkg/node/methods.go +++ b/internal/pkg/node/methods.go @@ -356,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 } @@ -366,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 */ diff --git a/internal/pkg/node/transformer_test.go b/internal/pkg/node/transformer_test.go index c09c2843..204b44a4 100644 --- a/internal/pkg/node/transformer_test.go +++ b/internal/pkg/node/transformer_test.go @@ -1,7 +1,6 @@ package node import ( - "fmt" "reflect" "strconv" "testing" @@ -69,6 +68,7 @@ func Test_nodeYaml_SetFrom(t *testing.T) { 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 @@ -213,11 +213,140 @@ func Test_nodeYaml_SetFrom(t *testing.T) { } }) t.Run("Get() ipmitag foo from profile, node does not have this tag", func(t *testing.T) { - fmt.Println("ipmi tags", test_node3.Ipmi.Tags) - fmt.Println(c.Nodes["test_node3"].Ipmi) 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)) + } + }) }