From 5ff27061a31a9e1c4b37a31931ff48ec949dab65 Mon Sep 17 00:00:00 2001 From: Christian Goll Date: Thu, 21 Nov 2024 17:36:10 +0100 Subject: [PATCH] added remote resources Signed-off-by: Christian Goll --- internal/app/wwctl/node/add/main.go | 3 + internal/app/wwctl/node/set/main.go | 1 + internal/app/wwctl/profile/add/main.go | 21 +++--- internal/app/wwctl/profile/add/root.go | 4 +- internal/app/wwctl/profile/set/main.go | 1 + internal/app/wwctl/resource/add/main.go | 31 ++++++++ internal/app/wwctl/resource/add/main_test.go | 58 ++++++++++++++ internal/app/wwctl/resource/add/root.go | 25 +++++++ internal/app/wwctl/resource/delete/main.go | 25 +++++++ .../app/wwctl/resource/delete/main_test.go | 63 ++++++++++++++++ internal/app/wwctl/resource/delete/root.go | 31 ++++++++ internal/app/wwctl/resource/list/main.go | 44 +++++++++++ internal/app/wwctl/resource/list/main_test.go | 74 ++++++++++++++++++ internal/app/wwctl/resource/list/root.go | 33 ++++++++ internal/app/wwctl/resource/root.go | 30 ++++++++ internal/app/wwctl/resource/set/main.go | 31 ++++++++ internal/app/wwctl/resource/set/main_test.go | 75 +++++++++++++++++++ internal/app/wwctl/resource/set/root.go | 33 ++++++++ internal/pkg/node/constructors.go | 22 ++++++ internal/pkg/node/datastructure.go | 14 ++++ internal/pkg/node/flags.go | 3 + internal/pkg/node/methods.go | 1 + 22 files changed, 611 insertions(+), 12 deletions(-) create mode 100644 internal/app/wwctl/resource/add/main.go create mode 100644 internal/app/wwctl/resource/add/main_test.go create mode 100644 internal/app/wwctl/resource/add/root.go create mode 100644 internal/app/wwctl/resource/delete/main.go create mode 100644 internal/app/wwctl/resource/delete/main_test.go create mode 100644 internal/app/wwctl/resource/delete/root.go create mode 100644 internal/app/wwctl/resource/list/main.go create mode 100644 internal/app/wwctl/resource/list/main_test.go create mode 100644 internal/app/wwctl/resource/list/root.go create mode 100644 internal/app/wwctl/resource/root.go create mode 100644 internal/app/wwctl/resource/set/main.go create mode 100644 internal/app/wwctl/resource/set/main_test.go create mode 100644 internal/app/wwctl/resource/set/root.go diff --git a/internal/app/wwctl/node/add/main.go b/internal/app/wwctl/node/add/main.go index 2c91e8b2..3a472513 100644 --- a/internal/app/wwctl/node/add/main.go +++ b/internal/app/wwctl/node/add/main.go @@ -25,6 +25,8 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) error { if !node.ObjectIsEmpty(vars.nodeConf.NetDevs["UNDEF"]) { netDev := *vars.nodeConf.NetDevs["UNDEF"] vars.nodeConf.NetDevs[vars.nodeAdd.Net] = &netDev + vars.nodeConf.NetDevs[vars.nodeAdd.Net].Tags = vars.nodeAdd.NetTagsAdd + } delete(vars.nodeConf.NetDevs, "UNDEF") if vars.nodeAdd.FsName != "" { @@ -50,6 +52,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) error { return fmt.Errorf("partition and disk must be specified") } delete(vars.nodeConf.Disks, "UNDEF") + vars.nodeConf.Ipmi.Tags = vars.nodeAdd.IpmiTagsAdd if len(vars.nodeConf.Profiles) == 0 { vars.nodeConf.Profiles = []string{"default"} } diff --git a/internal/app/wwctl/node/set/main.go b/internal/app/wwctl/node/set/main.go index 228152fc..94b208d7 100644 --- a/internal/app/wwctl/node/set/main.go +++ b/internal/app/wwctl/node/set/main.go @@ -47,6 +47,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err return fmt.Errorf("partition and disk must be specified") } delete(vars.nodeConf.Disks, "UNDEF") + vars.nodeConf.Ipmi.Tags = vars.nodeAdd.IpmiTagsAdd buffer, err := yaml.Marshal(vars.nodeConf) if err != nil { return fmt.Errorf("can not marshall nodeInfo: %s", err) diff --git a/internal/app/wwctl/profile/add/main.go b/internal/app/wwctl/profile/add/main.go index 6b20fa15..a02648d9 100644 --- a/internal/app/wwctl/profile/add/main.go +++ b/internal/app/wwctl/profile/add/main.go @@ -18,32 +18,33 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err // to this network if !node.ObjectIsEmpty(vars.profileConf.NetDevs["UNDEF"]) { netDev := *vars.profileConf.NetDevs["UNDEF"] - vars.profileConf.NetDevs[vars.nodeAdd.Net] = &netDev + vars.profileConf.NetDevs[vars.profileAdd.Net] = &netDev } delete(vars.profileConf.NetDevs, "UNDEF") - if vars.nodeAdd.FsName != "" { - if !strings.HasPrefix(vars.nodeAdd.FsName, "/dev") { - if vars.nodeAdd.FsName == vars.nodeAdd.PartName { - vars.nodeAdd.FsName = "/dev/disk/by-partlabel/" + vars.nodeAdd.PartName + if vars.profileAdd.FsName != "" { + if !strings.HasPrefix(vars.profileAdd.FsName, "/dev") { + if vars.profileAdd.FsName == vars.profileAdd.PartName { + vars.profileAdd.FsName = "/dev/disk/by-partlabel/" + vars.profileAdd.PartName } else { return fmt.Errorf("filesystems need to have a underlying blockdev") } } fs := *vars.profileConf.FileSystems["UNDEF"] - vars.profileConf.FileSystems[vars.nodeAdd.FsName] = &fs + vars.profileConf.FileSystems[vars.profileAdd.FsName] = &fs } delete(vars.profileConf.FileSystems, "UNDEF") - if vars.nodeAdd.DiskName != "" && vars.nodeAdd.PartName != "" { + if vars.profileAdd.DiskName != "" && vars.profileAdd.PartName != "" { prt := *vars.profileConf.Disks["UNDEF"].Partitions["UNDEF"] - vars.profileConf.Disks["UNDEF"].Partitions[vars.nodeAdd.PartName] = &prt + vars.profileConf.Disks["UNDEF"].Partitions[vars.profileAdd.PartName] = &prt delete(vars.profileConf.Disks["UNDEF"].Partitions, "UNDEF") dsk := *vars.profileConf.Disks["UNDEF"] - vars.profileConf.Disks[vars.nodeAdd.DiskName] = &dsk + vars.profileConf.Disks[vars.profileAdd.DiskName] = &dsk } - if (vars.nodeAdd.DiskName != "") != (vars.nodeAdd.PartName != "") { + if (vars.profileAdd.DiskName != "") != (vars.profileAdd.PartName != "") { return fmt.Errorf("partition and disk must be specified") } delete(vars.profileConf.Disks, "UNDEF") + vars.profileConf.Ipmi.Tags = vars.profileAdd.IpmiTagsAdd buffer, err := yaml.Marshal(vars.profileConf) if err != nil { return fmt.Errorf("can not marshall nodeInfo: %w", err) diff --git a/internal/app/wwctl/profile/add/root.go b/internal/app/wwctl/profile/add/root.go index 795905b3..1e9b577b 100644 --- a/internal/app/wwctl/profile/add/root.go +++ b/internal/app/wwctl/profile/add/root.go @@ -12,7 +12,7 @@ import ( type variables struct { profileConf node.Profile - nodeAdd node.NodeConfAdd + profileAdd node.NodeConfAdd } // GetRootCommand returns the root cobra.Command for the application. @@ -29,7 +29,7 @@ func GetCommand() *cobra.Command { Args: cobra.ExactArgs(1), } vars.profileConf.CreateFlags(baseCmd) - vars.nodeAdd.CreateAddFlags(baseCmd) + vars.profileAdd.CreateAddFlags(baseCmd) // register the command line completions if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { list, _ := container.ListSources() diff --git a/internal/app/wwctl/profile/set/main.go b/internal/app/wwctl/profile/set/main.go index c07085eb..91410442 100644 --- a/internal/app/wwctl/profile/set/main.go +++ b/internal/app/wwctl/profile/set/main.go @@ -47,6 +47,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err return fmt.Errorf("partition and disk must be specified") } delete(vars.profileConf.Disks, "UNDEF") + vars.profileConf.Ipmi.Tags = vars.profileAdd.IpmiTagsAdd buffer, err := yaml.Marshal(vars.profileConf) if err != nil { return fmt.Errorf("can not marshall nodeInfo: %s", err) diff --git a/internal/app/wwctl/resource/add/main.go b/internal/app/wwctl/resource/add/main.go new file mode 100644 index 00000000..f99ca8a6 --- /dev/null +++ b/internal/app/wwctl/resource/add/main.go @@ -0,0 +1,31 @@ +package add + +import ( + "fmt" + + "github.com/warewulf/warewulf/internal/pkg/node" + + "github.com/spf13/cobra" +) + +func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { + return func(cmd *cobra.Command, args []string) (err error) { + nodeYml, err := node.New() + if err != nil { + return fmt.Errorf("failed to load node configuration: %s", err) + } + if ok := nodeYml.Resource[args[0]] != nil; ok { + return fmt.Errorf("resource %s already exists", args[0]) + } + if nodeYml.Resource == nil { + nodeYml.Resource = make(map[string]*node.RemoteRes) + } + res := node.RemoteRes{} + for key, val := range vars.tags { + res[key] = val + } + nodeYml.Resource[args[0]] = &res + err = nodeYml.Persist() + return err + } +} diff --git a/internal/app/wwctl/resource/add/main_test.go b/internal/app/wwctl/resource/add/main_test.go new file mode 100644 index 00000000..c81760cb --- /dev/null +++ b/internal/app/wwctl/resource/add/main_test.go @@ -0,0 +1,58 @@ +package add + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/warewulfd" +) + +func Test_resource_add(t *testing.T) { + tests := []struct { + name string + args []string + wantErr bool + stdout string + inDB string + outDb string + }{{ + name: "add resource", + args: []string{"--restagadd", "foo=baar", "test"}, + wantErr: false, + stdout: "", + inDB: `nodeprofiles: {} +nodes: {} +`, + outDb: `nodeprofiles: {} +nodes: {} +resource: + test: + foo: baar +`}} + env := testenv.New(t) + defer env.RemoveAll(t) + warewulfd.SetNoDaemon() + for _, tt := range tests { + env.WriteFile(t, "etc/warewulf/nodes.conf", tt.inDB) + t.Run(tt.name, func(t *testing.T) { + baseCmd := GetCommand() + baseCmd.SetArgs(tt.args) + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + err := baseCmd.Execute() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, buf.String(), tt.stdout) + content := env.ReadFile(t, "etc/warewulf/nodes.conf") + assert.YAMLEq(t, tt.outDb, content) + } + }) + + } +} diff --git a/internal/app/wwctl/resource/add/root.go b/internal/app/wwctl/resource/add/root.go new file mode 100644 index 00000000..d003a992 --- /dev/null +++ b/internal/app/wwctl/resource/add/root.go @@ -0,0 +1,25 @@ +package add + +import ( + "github.com/spf13/cobra" +) + +type variables struct { + tags map[string]string +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + vars := variables{} + baseCmd := &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "add PROFILE", + Short: "Add a new node profile", + Long: "This command adds a new named PROFILE.", + Aliases: []string{"new", "create"}, + RunE: CobraRunE(&vars), + Args: cobra.ExactArgs(1), + } + baseCmd.PersistentFlags().StringToStringVar(&vars.tags, "restagadd", map[string]string{}, "add cluster wide resource tags") + return baseCmd +} diff --git a/internal/app/wwctl/resource/delete/main.go b/internal/app/wwctl/resource/delete/main.go new file mode 100644 index 00000000..d4608fbb --- /dev/null +++ b/internal/app/wwctl/resource/delete/main.go @@ -0,0 +1,25 @@ +package delete + +import ( + "fmt" + + "github.com/warewulf/warewulf/internal/pkg/node" + + "github.com/spf13/cobra" +) + +func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { + return func(cmd *cobra.Command, args []string) (err error) { + nodeYml, err := node.New() + if err != nil { + return fmt.Errorf("failed to load node configuration: %s", err) + } + for _, res := range args { + if _, ok := nodeYml.Resource[res]; !ok { + return fmt.Errorf("resource %s does not exist", res) + } + delete(nodeYml.Resource, res) + } + return nodeYml.Persist() + } +} diff --git a/internal/app/wwctl/resource/delete/main_test.go b/internal/app/wwctl/resource/delete/main_test.go new file mode 100644 index 00000000..5e9a45dc --- /dev/null +++ b/internal/app/wwctl/resource/delete/main_test.go @@ -0,0 +1,63 @@ +package delete + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/warewulfd" +) + +func Test_resource_set(t *testing.T) { + tests := []struct { + name string + wantErr bool + args []string + stdout string + inDB string + outDb string + }{ + { + name: "delete resource", + wantErr: false, + stdout: "", + args: []string{"test1"}, + inDB: `nodeprofiles: {} +nodes: {} +resource: + test1: {} + test2: {} +`, + + outDb: `nodeprofiles: {} +nodes: {} +resource: + test2: {} +`}, + } + env := testenv.New(t) + defer env.RemoveAll(t) + warewulfd.SetNoDaemon() + for _, tt := range tests { + env.WriteFile(t, "etc/warewulf/nodes.conf", tt.inDB) + t.Run(tt.name, func(t *testing.T) { + baseCmd := GetCommand() + baseCmd.SetArgs(tt.args) + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + err := baseCmd.Execute() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, buf.String(), tt.stdout) + content := env.ReadFile(t, "etc/warewulf/nodes.conf") + assert.YAMLEq(t, tt.outDb, content) + } + }) + + } +} diff --git a/internal/app/wwctl/resource/delete/root.go b/internal/app/wwctl/resource/delete/root.go new file mode 100644 index 00000000..90da6866 --- /dev/null +++ b/internal/app/wwctl/resource/delete/root.go @@ -0,0 +1,31 @@ +package delete + +import ( + "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/pkg/node" +) + +type variables struct { +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + vars := variables{} + baseCmd := &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "list [RESOURCE]", + Short: "list resource", + Long: "This list all values the named RESOURCE. If no resource is named a list of the resources is printed.", + Aliases: []string{"show", "ls"}, + RunE: CobraRunE(&vars), + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + nodeDB, _ := node.New() + return nodeDB.ListAllResources(), cobra.ShellCompDirectiveNoFileComp + }, + } + return baseCmd +} diff --git a/internal/app/wwctl/resource/list/main.go b/internal/app/wwctl/resource/list/main.go new file mode 100644 index 00000000..71265db3 --- /dev/null +++ b/internal/app/wwctl/resource/list/main.go @@ -0,0 +1,44 @@ +package list + +import ( + "fmt" + + "github.com/warewulf/warewulf/internal/app/wwctl/table" + "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/wwlog" + + "github.com/spf13/cobra" +) + +func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { + return func(cmd *cobra.Command, args []string) (err error) { + nodeYml, err := node.New() + if err != nil { + return fmt.Errorf("failed to load node configuration: %s", err) + } + var resList []string + if len(args) == 1 { + resList = []string{args[0]} + } else { + resList = nodeYml.ListAllResources() + } + for _, resname := range resList { + res, err := nodeYml.GetResource(resname) + if err != nil { + return err + } + if !vars.all { + wwlog.Info("%s", resname) + } else { + t := table.New(cmd.OutOrStdout()) + t.AddHeader(resname, "key", "value") + for key, val := range res { + t.AddLine(resname, key, val) + } + t.Print() + + } + } + return nil + } +} diff --git a/internal/app/wwctl/resource/list/main_test.go b/internal/app/wwctl/resource/list/main_test.go new file mode 100644 index 00000000..ffffc291 --- /dev/null +++ b/internal/app/wwctl/resource/list/main_test.go @@ -0,0 +1,74 @@ +package list + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/warewulfd" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Test_resource_set(t *testing.T) { + tests := []struct { + name string + args []string + wantErr bool + stdout string + inDB string + }{ + { + name: "list resource", + args: []string{}, + wantErr: false, + stdout: `test1 +test2 +`, + inDB: `nodeprofiles: {} +nodes: {} +resource: + test1: {} + test2: {} +`, + }, + { + name: "list a existing resource", + args: []string{"-a"}, + wantErr: false, + stdout: `test key value +---- --- ----- +test foo baar +`, + inDB: `nodeprofiles: {} +nodes: {} +resource: + test: + foo: baar +`, + }, + } + env := testenv.New(t) + defer env.RemoveAll(t) + warewulfd.SetNoDaemon() + for _, tt := range tests { + env.WriteFile(t, "etc/warewulf/nodes.conf", tt.inDB) + t.Run(tt.name, func(t *testing.T) { + baseCmd := GetCommand() + baseCmd.SetArgs(tt.args) + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + wwlog.SetLogWriter(buf) + err := baseCmd.Execute() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, buf.String(), tt.stdout) + } + }) + + } +} diff --git a/internal/app/wwctl/resource/list/root.go b/internal/app/wwctl/resource/list/root.go new file mode 100644 index 00000000..550d4aec --- /dev/null +++ b/internal/app/wwctl/resource/list/root.go @@ -0,0 +1,33 @@ +package list + +import ( + "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/pkg/node" +) + +type variables struct { + all bool +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + vars := variables{} + baseCmd := &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "list [RESOURCE]", + Short: "list resource", + Long: "This list all values the named RESOURCE. If no resource is named a list of the resources is printed.", + Aliases: []string{"show", "ls"}, + RunE: CobraRunE(&vars), + Args: cobra.MaximumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + nodeDB, _ := node.New() + return nodeDB.ListAllResources(), cobra.ShellCompDirectiveNoFileComp + }, + } + baseCmd.PersistentFlags().BoolVarP(&vars.all, "all", "a", false, "Show all resources") + return baseCmd +} diff --git a/internal/app/wwctl/resource/root.go b/internal/app/wwctl/resource/root.go new file mode 100644 index 00000000..d74a61f6 --- /dev/null +++ b/internal/app/wwctl/resource/root.go @@ -0,0 +1,30 @@ +package resource + +import ( + "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/app/wwctl/resource/add" + "github.com/warewulf/warewulf/internal/app/wwctl/resource/delete" + "github.com/warewulf/warewulf/internal/app/wwctl/resource/list" + "github.com/warewulf/warewulf/internal/app/wwctl/resource/set" +) + +var ( + baseCmd = &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "resource COMMAND [OPTIONS]", + Short: "manage resources", + Long: "Management of global resources", + } +) + +func init() { + baseCmd.AddCommand(list.GetCommand()) + baseCmd.AddCommand(set.GetCommand()) + baseCmd.AddCommand(add.GetCommand()) + baseCmd.AddCommand(delete.GetCommand()) +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/resource/set/main.go b/internal/app/wwctl/resource/set/main.go new file mode 100644 index 00000000..a38208f4 --- /dev/null +++ b/internal/app/wwctl/resource/set/main.go @@ -0,0 +1,31 @@ +package set + +import ( + "fmt" + + "github.com/warewulf/warewulf/internal/pkg/node" + + "github.com/spf13/cobra" +) + +func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { + return func(cmd *cobra.Command, args []string) (err error) { + nodeYml, err := node.New() + if err != nil { + return fmt.Errorf("failed to load node configuration: %s", err) + } + if ok := nodeYml.Resource[args[0]] != nil; ok { + if nodeYml.Resource == nil { + nodeYml.Resource = make(map[string]*node.RemoteRes) + } + res := *nodeYml.Resource[args[0]] + for key, val := range vars.tags { + res[key] = val + } + err = nodeYml.Persist() + return err + } else { + return fmt.Errorf("resource %s does not exist", args[0]) + } + } +} diff --git a/internal/app/wwctl/resource/set/main_test.go b/internal/app/wwctl/resource/set/main_test.go new file mode 100644 index 00000000..b728529d --- /dev/null +++ b/internal/app/wwctl/resource/set/main_test.go @@ -0,0 +1,75 @@ +package set + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/warewulf/warewulf/internal/pkg/testenv" + "github.com/warewulf/warewulf/internal/pkg/warewulfd" +) + +func Test_resource_set(t *testing.T) { + tests := []struct { + name string + args []string + wantErr bool + stdout string + inDB string + outDb string + }{ + { + name: "set resource", + args: []string{"--tagset", "foo=test", "test"}, + wantErr: false, + stdout: "", + inDB: `nodeprofiles: {} +nodes: {} +resource: + test: + foo: baar +`, + outDb: `nodeprofiles: {} +nodes: {} +resource: + test: + foo: test +`}, + { + name: "set non exiting resource", + args: []string{"--tagset", "foo=test", "none"}, + wantErr: true, + stdout: "", + inDB: `nodeprofiles: {} +nodes: {} +resource: + test: + foo: baar +`, + outDb: ``}, + } + env := testenv.New(t) + defer env.RemoveAll(t) + warewulfd.SetNoDaemon() + for _, tt := range tests { + env.WriteFile(t, "etc/warewulf/nodes.conf", tt.inDB) + t.Run(tt.name, func(t *testing.T) { + baseCmd := GetCommand() + baseCmd.SetArgs(tt.args) + buf := new(bytes.Buffer) + baseCmd.SetOut(buf) + baseCmd.SetErr(buf) + err := baseCmd.Execute() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, buf.String(), tt.stdout) + content := env.ReadFile(t, "etc/warewulf/nodes.conf") + assert.YAMLEq(t, tt.outDb, content) + } + }) + + } +} diff --git a/internal/app/wwctl/resource/set/root.go b/internal/app/wwctl/resource/set/root.go new file mode 100644 index 00000000..0d15928a --- /dev/null +++ b/internal/app/wwctl/resource/set/root.go @@ -0,0 +1,33 @@ +package set + +import ( + "github.com/spf13/cobra" + "github.com/warewulf/warewulf/internal/pkg/node" +) + +type variables struct { + tags map[string]string +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + vars := variables{} + baseCmd := &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "set RESOURCE", + Short: "change a resource", + Long: "This command changes the named RESOURCE. Use UNDEF to remove a tag.", + Aliases: []string{"modify", "change"}, + RunE: CobraRunE(&vars), + Args: cobra.ExactArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + nodeDB, _ := node.New() + return nodeDB.ListAllResources(), cobra.ShellCompDirectiveNoFileComp + }, + } + baseCmd.PersistentFlags().StringToStringVar(&vars.tags, "tagset", map[string]string{}, "set cluster wide resource tags") + return baseCmd +} diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go index 0de328be..0ef5325b 100644 --- a/internal/pkg/node/constructors.go +++ b/internal/pkg/node/constructors.go @@ -183,6 +183,18 @@ func (config *NodesYaml) ListAllProfiles() []string { return nodeList } +/* +Return the names of all available remote resources +*/ +func (config *NodesYaml) ListAllResources() []string { + var resList []string + for name := range config.Resource { + resList = append(resList, name) + } + sort.Strings(resList) + return resList +} + /* FindDiscoverableNode returns the first discoverable node and an interface to associate with the discovered interface. If the nodUNDEFe has @@ -211,3 +223,13 @@ func (config *NodesYaml) FindDiscoverableNode() (Node, string, error) { return EmptyNode(), "", ErrNoUnconfigured } + +/* +get the given resource +*/ +func (config *NodesYaml) GetResource(id string) (res RemoteRes, err error) { + if found, ok := config.Resource[id]; ok { + return *found, nil + } + return res, ErrNotFound +} diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index c6789203..3ecf9e98 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -17,6 +17,7 @@ Structure of which goes to disk type NodesYaml struct { NodeProfiles map[string]*Profile Nodes map[string]*Node + Resource map[string]*RemoteRes `yaml:"resource,omitempty"` } /* @@ -53,6 +54,8 @@ type Profile struct { PrimaryNetDev string `yaml:"primary network,omitempty" lopt:"primarynet" sopt:"p" comment:"Set the primary network interface"` Disks map[string]*Disk `yaml:"disks,omitempty"` FileSystems map[string]*FileSystem `yaml:"filesystems,omitempty"` + resource map[string]*RemoteRes `yaml:"omitempty"` + Resources []string `yaml:"resources,omitempty" lopt:"resources" comment:"Set the resources available to the node or profile"` } type IpmiConf struct { @@ -124,3 +127,14 @@ type FileSystem struct { Options []string `yaml:"options,omitempty" comment:"any additional options to be passed to the format-specific mkfs utility"` MountOptions string `yaml:"mount_options,omitempty" comment:"any special options to be passed to the mount command"` } + +/* +Definition of any remote. Can be a nfs share or other FS types +*/ + +type RemoteRes map[string]string + +// always return so that the resource isn't marshalled +// func (res RemoteRes) IsZero() bool { +// return true +// } diff --git a/internal/pkg/node/flags.go b/internal/pkg/node/flags.go index 62dad5e1..1146f3ed 100644 --- a/internal/pkg/node/flags.go +++ b/internal/pkg/node/flags.go @@ -53,6 +53,9 @@ func recursiveCreateFlags(obj interface{}, baseCmd *cobra.Command) { nodeInfoType := reflect.TypeOf(obj) nodeInfoVal := reflect.ValueOf(obj) for i := 0; i < nodeInfoVal.Elem().NumField(); i++ { + if !nodeInfoType.Elem().Field(i).IsExported() { + continue + } if nodeInfoType.Elem().Field(i).Tag.Get("comment") != "" { field := nodeInfoVal.Elem().Field(i) createFlags(baseCmd, nodeInfoType.Elem().Field(i), &field) diff --git a/internal/pkg/node/methods.go b/internal/pkg/node/methods.go index 9d1f54ac..f22d07aa 100644 --- a/internal/pkg/node/methods.go +++ b/internal/pkg/node/methods.go @@ -103,6 +103,7 @@ func EmptyNode() (nodeconf Node) { nodeconf.Ipmi.Tags = map[string]string{} nodeconf.Kernel = new(KernelConf) nodeconf.NetDevs = make(map[string]*NetDev) + nodeconf.resource = make(map[string]*RemoteRes) nodeconf.Tags = map[string]string{} return nodeconf }