diff --git a/internal/app/wwctl/node/edit/main.go b/internal/app/wwctl/node/edit/main.go new file mode 100644 index 00000000..9307edef --- /dev/null +++ b/internal/app/wwctl/node/edit/main.go @@ -0,0 +1,115 @@ +package edit + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + apinode "github.com/hpcng/warewulf/internal/pkg/api/node" + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + apiutil "github.com/hpcng/warewulf/internal/pkg/api/util" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + canWrite := apiutil.CanWriteConfig() + if !canWrite.CanWriteConfig { + wwlog.Error("Can't write to config exiting") + os.Exit(1) + } + editor := os.Getenv("EDITOR") + if editor == "" { + editor = "/bin/vi" + } + if len(args) == 0 { + args = append(args, ".*") + } + filterList := wwapiv1.NodeList{ + Output: args, + } + nodeListMsg := apinode.FilteredNodes(&filterList) + nodeMap := make(map[string]*node.NodeConf) + // got proper yaml back + _ = yaml.Unmarshal([]byte(nodeListMsg.NodeConfMapYaml), nodeMap) + file, err := ioutil.TempFile("/tmp", "ww4NodeEdit*.yaml") + if err != nil { + wwlog.Error("Could not create temp file:%s \n", err) + } + defer os.Remove(file.Name()) + nodeConf := node.NewConf() + yamlTemplate := nodeConf.UnmarshalConf([]string{"tagsdel", "default", "profiles"}) + for { + _ = file.Truncate(0) + _, _ = file.Seek(0, 0) + if !NoHeader { + _, _ = file.WriteString("#nodename:\n# " + strings.Join(yamlTemplate, "\n# ") + "\n") + } + _, _ = file.WriteString(nodeListMsg.NodeConfMapYaml) + _, _ = file.Seek(0, 0) + hasher := sha256.New() + if _, err := io.Copy(hasher, file); err != nil { + wwlog.Error("Problems getting checksum of file %s\n", err) + } + sum1 := hex.EncodeToString(hasher.Sum(nil)) + err = util.ExecInteractive(editor, file.Name()) + if err != nil { + wwlog.Error("Editor process existed with non-zero\n") + os.Exit(1) + } + _, _ = file.Seek(0, 0) + hasher.Reset() + if _, err := io.Copy(hasher, file); err != nil { + wwlog.Error("Problems getting checksum of file %s\n", err) + } + 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") + modifiedNodeMap := 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, modifiedNodeMap) + if err == nil { + nodeList := make([]string, len(nodeMap)) + i := 0 + for key := range nodeMap { + nodeList[i] = key + i++ + } + yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes", len(modifiedNodeMap))) + if !yes { + break + } + err = apinode.NodeDelete(&wwapiv1.NodeDeleteParameter{NodeNames: nodeList, Force: true}) + if err != nil { + wwlog.Verbose("Problem deleting nodes before modification %s") + } + buffer, _ = yaml.Marshal(modifiedNodeMap) + err = apinode.NodeAddFromYaml(&wwapiv1.NodeYaml{NodeConfMapYaml: string(buffer)}) + if err != nil { + wwlog.Error("Got following problem when writing back yaml: %s", err) + os.Exit(1) + } + break + } else { + yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", err)) + if !yes { + break + } + } + } else { + break + } + } + + return nil +} diff --git a/internal/app/wwctl/node/edit/root.go b/internal/app/wwctl/node/edit/root.go new file mode 100644 index 00000000..387b7cc2 --- /dev/null +++ b/internal/app/wwctl/node/edit/root.go @@ -0,0 +1,39 @@ +package edit + +import ( + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/spf13/cobra" +) + +var ( + baseCmd = &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "edit [OPTIONS] NODENAME", + Short: "Edit node(s) with editor", + Long: "This command opens an editor for the given nodes.", + RunE: CobraRunE, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + nodeDB, _ := node.New() + nodes, _ := nodeDB.FindAllNodes() + var node_names []string + for _, node := range nodes { + node_names = append(node_names, node.Id.Get()) + } + return node_names, cobra.ShellCompDirectiveNoFileComp + }, + } + NoHeader bool +) + +func init() { + baseCmd.PersistentFlags().BoolVar(&NoHeader, "noheader", false, "Do not print header") +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/node/export/main.go b/internal/app/wwctl/node/export/main.go new file mode 100644 index 00000000..1645683c --- /dev/null +++ b/internal/app/wwctl/node/export/main.go @@ -0,0 +1,26 @@ +package export + +import ( + "fmt" + + apinode "github.com/hpcng/warewulf/internal/pkg/api/node" + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/spf13/cobra" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + args = append(args, ".*") + } + filterList := wwapiv1.NodeList{ + Output: args, + } + nodeListMsg := apinode.FilteredNodes(&filterList) + /* + nodeMap := make(map[string]*node.NodeConf) + // got proper yaml back + _ = yaml.Unmarshal([]byte(nodeListMsg.NodeConfMapYaml), nodeMap) + */ + fmt.Println(nodeListMsg.NodeConfMapYaml) + return nil +} diff --git a/internal/app/wwctl/node/export/root.go b/internal/app/wwctl/node/export/root.go new file mode 100644 index 00000000..ce423830 --- /dev/null +++ b/internal/app/wwctl/node/export/root.go @@ -0,0 +1,38 @@ +package export + +import ( + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/spf13/cobra" +) + +var ( + baseCmd = &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "export NODENAME", + Short: "Export nodes as yaml to stdout", + Long: "This command exports the given nodes as yaml to stdout.", + RunE: CobraRunE, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + nodeDB, _ := node.New() + nodes, _ := nodeDB.FindAllNodes() + var node_names []string + for _, node := range nodes { + node_names = append(node_names, node.Id.Get()) + } + return node_names, cobra.ShellCompDirectiveNoFileComp + }, + } + NoHeader bool +) + +func init() { +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/node/imprt/main.go b/internal/app/wwctl/node/imprt/main.go new file mode 100644 index 00000000..647d84cb --- /dev/null +++ b/internal/app/wwctl/node/imprt/main.go @@ -0,0 +1,98 @@ +package imprt + +import ( + "bytes" + "encoding/csv" + "fmt" + "io" + "os" + + apinode "github.com/hpcng/warewulf/internal/pkg/api/node" + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + apiutil "github.com/hpcng/warewulf/internal/pkg/api/util" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + file, err := os.Open(args[0]) + if err != nil { + wwlog.Error("Could not open file:%s \n", err) + os.Exit(1) + } + defer file.Close() + + importMap := make(map[string]*node.NodeConf) + buffer, err := io.ReadAll(file) + if err != nil { + wwlog.Error("Could not read:%s\n", err) + os.Exit(1) + } + if !ImportCVS { + err = yaml.Unmarshal(buffer, importMap) + if err == nil { + yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes", len(importMap))) + if yes { + err = apinode.NodeAddFromYaml(&wwapiv1.NodeYaml{NodeConfMapYaml: string(buffer)}) + if err != nil { + wwlog.Error("Got following problem when writing back yaml: %s", err) + os.Exit(1) + } + } + } else { + wwlog.Error("Could not parse import file") + } + } else { + // reading from buffer is a bit overshot + csvReader := csv.NewReader(bytes.NewReader(buffer)) + records, err := csvReader.ReadAll() + if err != nil { + wwlog.Error("Could not parse %s: %s\n", args[0], err) + os.Exit(1) + } + if len(records) < 1 || len(records[0]) < 1 { + wwlog.Error("Did not find any data in %s\n", args[0]) + os.Exit(1) + } + if !(records[0][0] == "node" || records[0][0] == "nodename") { + Usage() + os.Exit(1) + } + argsLen := len(records[0]) + for i, line := range records[1:] { + if len(line) != argsLen { + wwlog.Error("Wrong number of fields in lube %u\n", i+1) + os.Exit(1) + } + for j := range line { + if j == 0 { + continue + } + if importMap[line[0]] == nil { + importMap[line[0]] = new(node.NodeConf) + } + ok := importMap[line[0]].SetLopt(records[0][j], line[j]) + if !(ok) { + wwlog.Debug("Could not import %s\n", line[j]) + } + } + } + yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to import %d nodes", len(importMap))) + if yes { + // create second buffer an marshall nodeMap to it + buffer, err = yaml.Marshal(importMap) + if err != nil { + wwlog.Error("Got following problem when creating yaml: %s", err) + } + err = apinode.NodeAddFromYaml(&wwapiv1.NodeYaml{NodeConfMapYaml: string(buffer)}) + if err != nil { + wwlog.Error("Got following problem when writing back yaml: %s", err) + os.Exit(1) + } + } + } + + return nil +} diff --git a/internal/app/wwctl/node/imprt/root.go b/internal/app/wwctl/node/imprt/root.go new file mode 100644 index 00000000..2332e8fd --- /dev/null +++ b/internal/app/wwctl/node/imprt/root.go @@ -0,0 +1,35 @@ +package imprt + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var ( + baseCmd = &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "import [OPTIONS] NODENAME", + Short: "Import node(s) from yaml file", + Long: "This command imports all the nodes defined in a file. It will overwrite nodes with same name.", + RunE: CobraRunE, + Args: cobra.MinimumNArgs(1), + Aliases: []string{"import"}, + } + ImportCVS bool +) + +func init() { + baseCmd.PersistentFlags().BoolVarP(&ImportCVS, "cvs", "c", false, "Import CVS file") +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} + +func Usage() { + fmt.Println(`The csv file must be structured in following way: +node,option1,option2,net.netname1.netopt +node01,value1,value2,net.netname1,netvalue`) +} diff --git a/internal/app/wwctl/node/root.go b/internal/app/wwctl/node/root.go index 804c6320..ffc1f80b 100644 --- a/internal/app/wwctl/node/root.go +++ b/internal/app/wwctl/node/root.go @@ -4,6 +4,9 @@ import ( "github.com/hpcng/warewulf/internal/app/wwctl/node/add" "github.com/hpcng/warewulf/internal/app/wwctl/node/console" "github.com/hpcng/warewulf/internal/app/wwctl/node/delete" + "github.com/hpcng/warewulf/internal/app/wwctl/node/edit" + "github.com/hpcng/warewulf/internal/app/wwctl/node/export" + "github.com/hpcng/warewulf/internal/app/wwctl/node/imprt" "github.com/hpcng/warewulf/internal/app/wwctl/node/list" "github.com/hpcng/warewulf/internal/app/wwctl/node/sensors" "github.com/hpcng/warewulf/internal/app/wwctl/node/set" @@ -30,6 +33,9 @@ func init() { baseCmd.AddCommand(delete.GetCommand()) baseCmd.AddCommand(console.GetCommand()) baseCmd.AddCommand(nodestatus.GetCommand()) + baseCmd.AddCommand(edit.GetCommand()) + baseCmd.AddCommand(imprt.GetCommand()) + baseCmd.AddCommand(export.GetCommand()) } // GetRootCommand returns the root cobra.Command for the application. diff --git a/internal/app/wwctl/profile/delete/root.go b/internal/app/wwctl/profile/delete/root.go index a5cea9e1..806a8d68 100644 --- a/internal/app/wwctl/profile/delete/root.go +++ b/internal/app/wwctl/profile/delete/root.go @@ -1,15 +1,30 @@ package delete -import "github.com/spf13/cobra" +import ( + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/spf13/cobra" +) var ( baseCmd = &cobra.Command{ DisableFlagsInUseLine: true, - Use: "delete [OPTIONS] PROFILE", - Short: "Delete a node profile", - Long: "This command deletes the node PROFILE. You may use a pattern for PROFILE.", - RunE: CobraRunE, - Args: cobra.MinimumNArgs(1), + Use: "delete [OPTIONS] PROFILE", + Short: "Delete a node profile", + Long: "This command deletes the node PROFILE. You may use a pattern for PROFILE.", + RunE: CobraRunE, + Args: cobra.MinimumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + nodeDB, _ := node.New() + profiles, _ := nodeDB.FindAllProfiles() + var p_names []string + for _, profile := range profiles { + p_names = append(p_names, profile.Id.Get()) + } + return p_names, cobra.ShellCompDirectiveNoFileComp + }, } SetYes bool ) diff --git a/internal/app/wwctl/profile/edit/main.go b/internal/app/wwctl/profile/edit/main.go new file mode 100644 index 00000000..a5694e5d --- /dev/null +++ b/internal/app/wwctl/profile/edit/main.go @@ -0,0 +1,115 @@ +package edit + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + apiprofile "github.com/hpcng/warewulf/internal/pkg/api/profile" + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + apiutil "github.com/hpcng/warewulf/internal/pkg/api/util" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" +) + +func CobraRunE(cmd *cobra.Command, args []string) error { + canWrite := apiutil.CanWriteConfig() + if !canWrite.CanWriteConfig { + wwlog.Error("Can't write to config exiting") + os.Exit(1) + } + editor := os.Getenv("EDITOR") + if editor == "" { + editor = "/bin/vi" + } + if len(args) == 0 { + args = append(args, ".*") + } + filterList := wwapiv1.NodeList{ + Output: args, + } + profileListMsg := apiprofile.FilteredProfiles(&filterList) + profileMap := make(map[string]*node.NodeConf) + // got proper yaml back + _ = yaml.Unmarshal([]byte(profileListMsg.NodeConfMapYaml), profileMap) + file, err := ioutil.TempFile("/tmp", "ww4ProfileEdit*.yaml") + if err != nil { + wwlog.Error("Could not create temp file:%s \n", err) + } + defer os.Remove(file.Name()) + nodeConf := node.NewConf() + yamlTemplate := nodeConf.UnmarshalConf([]string{"tagsdel"}) + for { + _ = file.Truncate(0) + _, _ = file.Seek(0, 0) + if !NoHeader { + _, _ = file.WriteString("#profilename:\n# " + strings.Join(yamlTemplate, "\n# ") + "\n") + } + _, _ = file.WriteString(profileListMsg.NodeConfMapYaml) + _, _ = file.Seek(0, 0) + hasher := sha256.New() + if _, err := io.Copy(hasher, file); err != nil { + wwlog.Error("Problems getting checksum of file %s\n", err) + } + sum1 := hex.EncodeToString(hasher.Sum(nil)) + err = util.ExecInteractive(editor, file.Name()) + if err != nil { + wwlog.Error("Editor process existed with non-zero\n") + os.Exit(1) + } + _, _ = file.Seek(0, 0) + hasher.Reset() + if _, err := io.Copy(hasher, file); err != nil { + wwlog.Error("Problems getting checksum of file %s\n", err) + } + 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") + 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)) + i := 0 + for key := range profileMap { + nodeList[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 err != nil { + wwlog.Verbose("Problem deleting nodes before modification %s") + } + buffer, _ = yaml.Marshal(modifiedProfileMap) + err = apiprofile.ProfileAddFromYaml(&wwapiv1.NodeYaml{NodeConfMapYaml: string(buffer)}) + if err != nil { + wwlog.Error("Got following problem when writing back yaml: %s", err) + os.Exit(1) + } + break + } else { + yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", err)) + if !yes { + break + } + } + } else { + break + } + } + + return nil +} diff --git a/internal/app/wwctl/profile/edit/root.go b/internal/app/wwctl/profile/edit/root.go new file mode 100644 index 00000000..6a073532 --- /dev/null +++ b/internal/app/wwctl/profile/edit/root.go @@ -0,0 +1,38 @@ +package edit + +import ( + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/spf13/cobra" +) + +var ( + baseCmd = &cobra.Command{ + DisableFlagsInUseLine: true, + Use: "edit [OPTIONS] NODENAME", + Short: "Edit node(s) with editor", + Long: "This command opens an editor for the given profiles.", + RunE: CobraRunE, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + nodeDB, _ := node.New() + profiles, _ := nodeDB.FindAllProfiles() + var p_names []string + for _, profile := range profiles { + p_names = append(p_names, profile.Id.Get()) + } + return p_names, cobra.ShellCompDirectiveNoFileComp + }, + } + NoHeader bool +) + +func init() { + baseCmd.PersistentFlags().BoolVar(&NoHeader, "noheader", false, "Do not print header") +} + +// GetRootCommand returns the root cobra.Command for the application. +func GetCommand() *cobra.Command { + return baseCmd +} diff --git a/internal/app/wwctl/profile/root.go b/internal/app/wwctl/profile/root.go index 9f10a5db..4f7b1341 100644 --- a/internal/app/wwctl/profile/root.go +++ b/internal/app/wwctl/profile/root.go @@ -3,6 +3,7 @@ package profile import ( "github.com/hpcng/warewulf/internal/app/wwctl/profile/add" "github.com/hpcng/warewulf/internal/app/wwctl/profile/delete" + "github.com/hpcng/warewulf/internal/app/wwctl/profile/edit" "github.com/hpcng/warewulf/internal/app/wwctl/profile/list" "github.com/hpcng/warewulf/internal/app/wwctl/profile/set" "github.com/spf13/cobra" @@ -23,6 +24,7 @@ func init() { baseCmd.AddCommand(set.GetCommand()) baseCmd.AddCommand(add.GetCommand()) baseCmd.AddCommand(delete.GetCommand()) + baseCmd.AddCommand(edit.GetCommand()) } // GetRootCommand returns the root cobra.Command for the application. diff --git a/internal/pkg/api/node/edit.go b/internal/pkg/api/node/edit.go new file mode 100644 index 00000000..6cfa8768 --- /dev/null +++ b/internal/pkg/api/node/edit.go @@ -0,0 +1,70 @@ +package apinode + +import ( + "os" + + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +/* +Returns the nodes as a yaml string +*/ +func FindAllNodeConfs() *wwapiv1.NodeYaml { + nodeDB, err := node.New() + if err != nil { + wwlog.Error("Could not open nodeDB: %s\n", err) + os.Exit(1) + } + nodeMap := nodeDB.Nodes + // ignore err as nodeDB should always be correct + buffer, _ := yaml.Marshal(nodeMap) + retVal := wwapiv1.NodeYaml{ + NodeConfMapYaml: string(buffer), + } + return &retVal +} + +/* +Returns filtered list of nodes +*/ +func FilteredNodes(nodeList *wwapiv1.NodeList) *wwapiv1.NodeYaml { + nodeDB, err := node.New() + if err != nil { + wwlog.Error("Could not open nodeDB: %s\n", err) + os.Exit(1) + } + nodeMap := nodeDB.Nodes + nodeMap = node.FilterMapByName(nodeMap, nodeList.Output) + buffer, _ := yaml.Marshal(nodeMap) + retVal := wwapiv1.NodeYaml{ + NodeConfMapYaml: string(buffer), + } + return &retVal +} + +/* +Add nodes from yaml +*/ +func NodeAddFromYaml(nodeList *wwapiv1.NodeYaml) (err error) { + nodeDB, err := node.New() + if err != nil { + return errors.Wrap(err, "Could not open NodeDB: %s\n") + } + nodeMap := make(map[string]*node.NodeConf) + err = yaml.Unmarshal([]byte(nodeList.NodeConfMapYaml), nodeMap) + if err != nil { + return errors.Wrap(err, "Could not unmarshall Yaml: %s\n") + } + for nodeName, node := range nodeMap { + nodeDB.Nodes[nodeName] = node + } + err = nodeDB.Persist() + if err != nil { + return errors.Wrap(err, "failed to persist nodedb") + } + return nil +} diff --git a/internal/pkg/api/node/node.go b/internal/pkg/api/node/node.go index 6a9cc551..71165626 100644 --- a/internal/pkg/api/node/node.go +++ b/internal/pkg/api/node/node.go @@ -102,7 +102,7 @@ func NodeDelete(ndp *wwapiv1.NodeDeleteParameter) (err error) { wwlog.Error("%s\n", err) } else { //count++ - fmt.Printf("Deleting node: %s\n", n.Id.Print()) + wwlog.Verbose("Deleting node: %s\n", n.Id.Print()) } } diff --git a/internal/pkg/api/profile/delete.go b/internal/pkg/api/profile/delete.go new file mode 100644 index 00000000..e3c13185 --- /dev/null +++ b/internal/pkg/api/profile/delete.go @@ -0,0 +1,94 @@ +package apiprofile + +import ( + "fmt" + "os" + + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/warewulfd" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/hpcng/warewulf/pkg/hostlist" + "github.com/pkg/errors" +) + +// ProfileDelete adds profile deletion for management by Warewulf. +func ProfileDelete(ndp *wwapiv1.NodeDeleteParameter) (err error) { + + var profileList []node.NodeInfo + profileList, err = ProfileDeleteParameterCheck(ndp, false) + if err != nil { + return + } + + nodeDB, err := node.New() + if err != nil { + wwlog.Error("Failed to open node database: %s\n", err) + return + } + + for _, p := range profileList { + err := nodeDB.DelProfile(p.Id.Get()) + if err != nil { + wwlog.Error("%s\n", err) + } else { + //count++ + wwlog.Verbose("Deleting node: %s\n", p.Id.Print()) + } + } + + err = nodeDB.Persist() + if err != nil { + return errors.Wrap(err, "failed to persist nodedb") + } + + err = warewulfd.DaemonReload() + if err != nil { + return errors.Wrap(err, "failed to reload warewulf daemon") + } + return +} + +// ProfileDeleteParameterCheck does error checking on ProfileDeleteParameter. +// Output to the console if console is true. +// Returns the profiles to delete. +func ProfileDeleteParameterCheck(ndp *wwapiv1.NodeDeleteParameter, console bool) (profileList []node.NodeInfo, err error) { + + if ndp == nil { + err = fmt.Errorf("ProfileDeleteParameter is nil") + return + } + + nodeDB, err := node.New() + if err != nil { + wwlog.Error("Failed to open node database: %s\n", err) + return + } + + profiles, err := nodeDB.FindAllProfiles() + if err != nil { + wwlog.Error("Could not get node list: %s\n", err) + return + } + + node_args := hostlist.Expand(ndp.NodeNames) + + for _, r := range node_args { + var match bool + for _, p := range profiles { + if p.Id.Get() == r { + profileList = append(profileList, p) + match = true + } + } + + if !match { + fmt.Fprintf(os.Stderr, "ERROR: No match for node: %s\n", r) + } + } + + if len(profileList) == 0 { + fmt.Printf("No s found\n") + } + return +} diff --git a/internal/pkg/api/profile/edit.go b/internal/pkg/api/profile/edit.go new file mode 100644 index 00000000..5361f36c --- /dev/null +++ b/internal/pkg/api/profile/edit.go @@ -0,0 +1,70 @@ +package apiprofile + +import ( + "os" + + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/wwlog" + "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +/* +Returns the nodes as a yaml string +*/ +func FindAllProfileConfs() *wwapiv1.NodeYaml { + nodeDB, err := node.New() + if err != nil { + wwlog.Error("Could not open nodeDB: %s\n", err) + os.Exit(1) + } + profileMap := nodeDB.NodeProfiles + // ignore err as nodeDB should always be correct + buffer, _ := yaml.Marshal(profileMap) + retVal := wwapiv1.NodeYaml{ + NodeConfMapYaml: string(buffer), + } + return &retVal +} + +/* +Returns filtered list of nodes +*/ +func FilteredProfiles(profileList *wwapiv1.NodeList) *wwapiv1.NodeYaml { + nodeDB, err := node.New() + if err != nil { + wwlog.Error("Could not open nodeDB: %s\n", err) + os.Exit(1) + } + profileMap := nodeDB.NodeProfiles + profileMap = node.FilterMapByName(profileMap, profileList.Output) + buffer, _ := yaml.Marshal(profileMap) + retVal := wwapiv1.NodeYaml{ + NodeConfMapYaml: string(buffer), + } + return &retVal +} + +/* +Add profiles from yaml +*/ +func ProfileAddFromYaml(nodeList *wwapiv1.NodeYaml) (err error) { + nodeDB, err := node.New() + if err != nil { + return errors.Wrap(err, "Could not open NodeDB: %s\n") + } + profileMap := make(map[string]*node.NodeConf) + err = yaml.Unmarshal([]byte(nodeList.NodeConfMapYaml), profileMap) + if err != nil { + return errors.Wrap(err, "Could not unmarshall Yaml: %s\n") + } + for profileName, profile := range profileMap { + nodeDB.NodeProfiles[profileName] = profile + } + err = nodeDB.Persist() + if err != nil { + return errors.Wrap(err, "failed to persist nodedb") + } + return nil +} diff --git a/internal/pkg/api/routes/v1/routes.proto b/internal/pkg/api/routes/v1/routes.proto index eb2fcb44..23dec63f 100644 --- a/internal/pkg/api/routes/v1/routes.proto +++ b/internal/pkg/api/routes/v1/routes.proto @@ -91,7 +91,6 @@ message NetDev { // NodeInfo contains details about a node managed by Warewulf/ message NodeInfo { map Fields = 1; - map NetDevs = 23; map Tags = 24; map Keys = 25; // TODO: We may not need this. Tags may be it. Ask Greg. @@ -126,6 +125,12 @@ message NodeAddParameter { repeated string nodeNames = 10; } +// NodeYaml is just the updated YAML config which will be added +// to nodes.conf (is resused for profile edit) +message NodeYaml { + string nodeConfMapYaml = 1; +} + // NodeDeleteParameter contains input for removing nodes from Warewulf // management. message NodeDeleteParameter { @@ -167,6 +172,11 @@ message VersionResponse { string warewulfVersion = 3; } +// Check if config is writeable +message CanWriteConfig { + bool canWriteConfig = 1; +} + // WWApi defines the wwapid service web interface. service WWApi { diff --git a/internal/pkg/api/routes/wwapiv1/routes.pb.go b/internal/pkg/api/routes/wwapiv1/routes.pb.go index 5d38b99e..8b878fa7 100644 --- a/internal/pkg/api/routes/wwapiv1/routes.pb.go +++ b/internal/pkg/api/routes/wwapiv1/routes.pb.go @@ -1026,6 +1026,55 @@ func (x *NodeAddParameter) GetNodeNames() []string { return nil } +// NodeYaml is just the updated YAML config which will be added +// to nodes.conf (is resused for profile edit) +type NodeYaml struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeConfMapYaml string `protobuf:"bytes,1,opt,name=nodeConfMapYaml,proto3" json:"nodeConfMapYaml,omitempty"` +} + +func (x *NodeYaml) Reset() { + *x = NodeYaml{} + if protoimpl.UnsafeEnabled { + mi := &file_routes_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NodeYaml) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeYaml) ProtoMessage() {} + +func (x *NodeYaml) ProtoReflect() protoreflect.Message { + mi := &file_routes_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodeYaml.ProtoReflect.Descriptor instead. +func (*NodeYaml) Descriptor() ([]byte, []int) { + return file_routes_proto_rawDescGZIP(), []int{16} +} + +func (x *NodeYaml) GetNodeConfMapYaml() string { + if x != nil { + return x.NodeConfMapYaml + } + return "" +} + // NodeDeleteParameter contains input for removing nodes from Warewulf // management. type NodeDeleteParameter struct { @@ -1040,7 +1089,7 @@ type NodeDeleteParameter struct { func (x *NodeDeleteParameter) Reset() { *x = NodeDeleteParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[16] + mi := &file_routes_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1053,7 +1102,7 @@ func (x *NodeDeleteParameter) String() string { func (*NodeDeleteParameter) ProtoMessage() {} func (x *NodeDeleteParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[16] + mi := &file_routes_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1066,7 +1115,7 @@ func (x *NodeDeleteParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeDeleteParameter.ProtoReflect.Descriptor instead. func (*NodeDeleteParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{16} + return file_routes_proto_rawDescGZIP(), []int{17} } func (x *NodeDeleteParameter) GetForce() bool { @@ -1101,7 +1150,7 @@ type NodeSetParameter struct { func (x *NodeSetParameter) Reset() { *x = NodeSetParameter{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[17] + mi := &file_routes_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1114,7 +1163,7 @@ func (x *NodeSetParameter) String() string { func (*NodeSetParameter) ProtoMessage() {} func (x *NodeSetParameter) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[17] + mi := &file_routes_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1127,7 +1176,7 @@ func (x *NodeSetParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeSetParameter.ProtoReflect.Descriptor instead. func (*NodeSetParameter) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{17} + return file_routes_proto_rawDescGZIP(), []int{18} } func (x *NodeSetParameter) GetNodeConfYaml() string { @@ -1188,7 +1237,7 @@ type NodeStatus struct { func (x *NodeStatus) Reset() { *x = NodeStatus{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[18] + mi := &file_routes_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1201,7 +1250,7 @@ func (x *NodeStatus) String() string { func (*NodeStatus) ProtoMessage() {} func (x *NodeStatus) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[18] + mi := &file_routes_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1214,7 +1263,7 @@ func (x *NodeStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeStatus.ProtoReflect.Descriptor instead. func (*NodeStatus) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{18} + return file_routes_proto_rawDescGZIP(), []int{19} } func (x *NodeStatus) GetNodeName() string { @@ -1264,7 +1313,7 @@ type NodeStatusResponse struct { func (x *NodeStatusResponse) Reset() { *x = NodeStatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[19] + mi := &file_routes_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1277,7 +1326,7 @@ func (x *NodeStatusResponse) String() string { func (*NodeStatusResponse) ProtoMessage() {} func (x *NodeStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[19] + mi := &file_routes_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1290,7 +1339,7 @@ func (x *NodeStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeStatusResponse.ProtoReflect.Descriptor instead. func (*NodeStatusResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{19} + return file_routes_proto_rawDescGZIP(), []int{20} } func (x *NodeStatusResponse) GetNodeStatus() []*NodeStatus { @@ -1314,7 +1363,7 @@ type VersionResponse struct { func (x *VersionResponse) Reset() { *x = VersionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_routes_proto_msgTypes[20] + mi := &file_routes_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1327,7 +1376,7 @@ func (x *VersionResponse) String() string { func (*VersionResponse) ProtoMessage() {} func (x *VersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_routes_proto_msgTypes[20] + mi := &file_routes_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1340,7 +1389,7 @@ func (x *VersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionResponse.ProtoReflect.Descriptor instead. func (*VersionResponse) Descriptor() ([]byte, []int) { - return file_routes_proto_rawDescGZIP(), []int{20} + return file_routes_proto_rawDescGZIP(), []int{21} } func (x *VersionResponse) GetApiPrefix() string { @@ -1364,6 +1413,54 @@ func (x *VersionResponse) GetWarewulfVersion() string { return "" } +// Check if config is writeable +type CanWriteConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CanWriteConfig bool `protobuf:"varint,1,opt,name=canWriteConfig,proto3" json:"canWriteConfig,omitempty"` +} + +func (x *CanWriteConfig) Reset() { + *x = CanWriteConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_routes_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CanWriteConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CanWriteConfig) ProtoMessage() {} + +func (x *CanWriteConfig) ProtoReflect() protoreflect.Message { + mi := &file_routes_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CanWriteConfig.ProtoReflect.Descriptor instead. +func (*CanWriteConfig) Descriptor() ([]byte, []int) { + return file_routes_proto_rawDescGZIP(), []int{22} +} + +func (x *CanWriteConfig) GetCanWriteConfig() bool { + if x != nil { + return x.CanWriteConfig + } + return false +} + var File_routes_proto protoreflect.FileDescriptor var file_routes_proto_rawDesc = []byte{ @@ -1504,45 +1601,52 @@ var file_routes_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, - 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x49, 0x0a, 0x13, 0x4e, 0x6f, 0x64, - 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x10, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, - 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x1c, 0x0a, - 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, - 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, - 0x1a, 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x1b, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, - 0x6f, 0x72, 0x63, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, - 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x27, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, - 0x86, 0x01, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, - 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, - 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x73, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x70, 0x61, 0x64, 0x64, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, 0x61, 0x64, 0x64, 0x72, 0x12, 0x1a, 0x0a, 0x08, - 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, - 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, 0x22, 0x4a, 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, - 0x0a, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, - 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x50, 0x72, - 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x70, 0x69, 0x50, - 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, - 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x32, + 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x08, 0x4e, 0x6f, 0x64, + 0x65, 0x59, 0x61, 0x6d, 0x6c, 0x12, 0x28, 0x0a, 0x0f, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, + 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, + 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x70, 0x59, 0x61, 0x6d, 0x6c, 0x22, + 0x49, 0x0a, 0x13, 0x4e, 0x6f, 0x64, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xc8, 0x01, 0x0a, 0x10, 0x4e, + 0x6f, 0x64, 0x65, 0x53, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, + 0x22, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, 0x61, 0x6d, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x59, + 0x61, 0x6d, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x74, 0x64, 0x65, 0x76, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, + 0x73, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x6c, 0x6c, 0x4e, 0x6f, 0x64, 0x65, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x73, 0x18, 0x27, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x6f, 0x64, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x86, 0x01, 0x0a, 0x0a, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x70, + 0x61, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, 0x61, 0x64, + 0x64, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x73, 0x65, 0x65, 0x6e, 0x22, 0x4a, + 0x0a, 0x12, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, + 0x6e, 0x6f, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, 0x0f, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x61, 0x70, 0x69, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x61, + 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x77, + 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x72, 0x65, 0x77, 0x75, 0x6c, 0x66, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x38, 0x0a, 0x0e, 0x43, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x61, 0x6e, 0x57, 0x72, + 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0e, 0x63, 0x61, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xa6, 0x08, 0x0a, 0x05, 0x57, 0x57, 0x41, 0x70, 0x69, 0x12, 0x73, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x21, 0x2e, 0x77, 0x77, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, @@ -1628,7 +1732,7 @@ func file_routes_proto_rawDescGZIP() []byte { } var file_routes_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_routes_proto_msgTypes = make([]protoimpl.MessageInfo, 27) +var file_routes_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_routes_proto_goTypes = []interface{}{ (GetNodeList_ListType)(0), // 0: wwapi.v1.GetNodeList.ListType (*ContainerBuildParameter)(nil), // 1: wwapi.v1.ContainerBuildParameter @@ -1647,30 +1751,32 @@ var file_routes_proto_goTypes = []interface{}{ (*GetNodeList)(nil), // 14: wwapi.v1.GetNodeList (*NodeList)(nil), // 15: wwapi.v1.NodeList (*NodeAddParameter)(nil), // 16: wwapi.v1.NodeAddParameter - (*NodeDeleteParameter)(nil), // 17: wwapi.v1.NodeDeleteParameter - (*NodeSetParameter)(nil), // 18: wwapi.v1.NodeSetParameter - (*NodeStatus)(nil), // 19: wwapi.v1.NodeStatus - (*NodeStatusResponse)(nil), // 20: wwapi.v1.NodeStatusResponse - (*VersionResponse)(nil), // 21: wwapi.v1.VersionResponse - nil, // 22: wwapi.v1.NetDev.FieldEntry - nil, // 23: wwapi.v1.NetDev.TagsEntry - nil, // 24: wwapi.v1.NodeInfo.FieldsEntry - nil, // 25: wwapi.v1.NodeInfo.NetDevsEntry - nil, // 26: wwapi.v1.NodeInfo.TagsEntry - nil, // 27: wwapi.v1.NodeInfo.KeysEntry - (*empty.Empty)(nil), // 28: google.protobuf.Empty + (*NodeYaml)(nil), // 17: wwapi.v1.NodeYaml + (*NodeDeleteParameter)(nil), // 18: wwapi.v1.NodeDeleteParameter + (*NodeSetParameter)(nil), // 19: wwapi.v1.NodeSetParameter + (*NodeStatus)(nil), // 20: wwapi.v1.NodeStatus + (*NodeStatusResponse)(nil), // 21: wwapi.v1.NodeStatusResponse + (*VersionResponse)(nil), // 22: wwapi.v1.VersionResponse + (*CanWriteConfig)(nil), // 23: wwapi.v1.CanWriteConfig + nil, // 24: wwapi.v1.NetDev.FieldEntry + nil, // 25: wwapi.v1.NetDev.TagsEntry + nil, // 26: wwapi.v1.NodeInfo.FieldsEntry + nil, // 27: wwapi.v1.NodeInfo.NetDevsEntry + nil, // 28: wwapi.v1.NodeInfo.TagsEntry + nil, // 29: wwapi.v1.NodeInfo.KeysEntry + (*empty.Empty)(nil), // 30: google.protobuf.Empty } var file_routes_proto_depIdxs = []int32{ 4, // 0: wwapi.v1.ContainerListResponse.containers:type_name -> wwapi.v1.ContainerInfo - 22, // 1: wwapi.v1.NetDev.Field:type_name -> wwapi.v1.NetDev.FieldEntry - 23, // 2: wwapi.v1.NetDev.Tags:type_name -> wwapi.v1.NetDev.TagsEntry - 24, // 3: wwapi.v1.NodeInfo.Fields:type_name -> wwapi.v1.NodeInfo.FieldsEntry - 25, // 4: wwapi.v1.NodeInfo.NetDevs:type_name -> wwapi.v1.NodeInfo.NetDevsEntry - 26, // 5: wwapi.v1.NodeInfo.Tags:type_name -> wwapi.v1.NodeInfo.TagsEntry - 27, // 6: wwapi.v1.NodeInfo.Keys:type_name -> wwapi.v1.NodeInfo.KeysEntry + 24, // 1: wwapi.v1.NetDev.Field:type_name -> wwapi.v1.NetDev.FieldEntry + 25, // 2: wwapi.v1.NetDev.Tags:type_name -> wwapi.v1.NetDev.TagsEntry + 26, // 3: wwapi.v1.NodeInfo.Fields:type_name -> wwapi.v1.NodeInfo.FieldsEntry + 27, // 4: wwapi.v1.NodeInfo.NetDevs:type_name -> wwapi.v1.NodeInfo.NetDevsEntry + 28, // 5: wwapi.v1.NodeInfo.Tags:type_name -> wwapi.v1.NodeInfo.TagsEntry + 29, // 6: wwapi.v1.NodeInfo.Keys:type_name -> wwapi.v1.NodeInfo.KeysEntry 12, // 7: wwapi.v1.NodeListResponse.nodes:type_name -> wwapi.v1.NodeInfo 0, // 8: wwapi.v1.GetNodeList.type:type_name -> wwapi.v1.GetNodeList.ListType - 19, // 9: wwapi.v1.NodeStatusResponse.nodeStatus:type_name -> wwapi.v1.NodeStatus + 20, // 9: wwapi.v1.NodeStatusResponse.nodeStatus:type_name -> wwapi.v1.NodeStatus 10, // 10: wwapi.v1.NetDev.FieldEntry.value:type_name -> wwapi.v1.NodeField 10, // 11: wwapi.v1.NetDev.TagsEntry.value:type_name -> wwapi.v1.NodeField 10, // 12: wwapi.v1.NodeInfo.FieldsEntry.value:type_name -> wwapi.v1.NodeField @@ -1680,25 +1786,25 @@ var file_routes_proto_depIdxs = []int32{ 1, // 16: wwapi.v1.WWApi.ContainerBuild:input_type -> wwapi.v1.ContainerBuildParameter 2, // 17: wwapi.v1.WWApi.ContainerDelete:input_type -> wwapi.v1.ContainerDeleteParameter 3, // 18: wwapi.v1.WWApi.ContainerImport:input_type -> wwapi.v1.ContainerImportParameter - 28, // 19: wwapi.v1.WWApi.ContainerList:input_type -> google.protobuf.Empty + 30, // 19: wwapi.v1.WWApi.ContainerList:input_type -> google.protobuf.Empty 6, // 20: wwapi.v1.WWApi.ContainerShow:input_type -> wwapi.v1.ContainerShowParameter 16, // 21: wwapi.v1.WWApi.NodeAdd:input_type -> wwapi.v1.NodeAddParameter - 17, // 22: wwapi.v1.WWApi.NodeDelete:input_type -> wwapi.v1.NodeDeleteParameter + 18, // 22: wwapi.v1.WWApi.NodeDelete:input_type -> wwapi.v1.NodeDeleteParameter 9, // 23: wwapi.v1.WWApi.NodeList:input_type -> wwapi.v1.NodeNames - 18, // 24: wwapi.v1.WWApi.NodeSet:input_type -> wwapi.v1.NodeSetParameter + 19, // 24: wwapi.v1.WWApi.NodeSet:input_type -> wwapi.v1.NodeSetParameter 9, // 25: wwapi.v1.WWApi.NodeStatus:input_type -> wwapi.v1.NodeNames - 28, // 26: wwapi.v1.WWApi.Version:input_type -> google.protobuf.Empty + 30, // 26: wwapi.v1.WWApi.Version:input_type -> google.protobuf.Empty 5, // 27: wwapi.v1.WWApi.ContainerBuild:output_type -> wwapi.v1.ContainerListResponse - 28, // 28: wwapi.v1.WWApi.ContainerDelete:output_type -> google.protobuf.Empty + 30, // 28: wwapi.v1.WWApi.ContainerDelete:output_type -> google.protobuf.Empty 5, // 29: wwapi.v1.WWApi.ContainerImport:output_type -> wwapi.v1.ContainerListResponse 5, // 30: wwapi.v1.WWApi.ContainerList:output_type -> wwapi.v1.ContainerListResponse 7, // 31: wwapi.v1.WWApi.ContainerShow:output_type -> wwapi.v1.ContainerShowResponse 13, // 32: wwapi.v1.WWApi.NodeAdd:output_type -> wwapi.v1.NodeListResponse - 28, // 33: wwapi.v1.WWApi.NodeDelete:output_type -> google.protobuf.Empty + 30, // 33: wwapi.v1.WWApi.NodeDelete:output_type -> google.protobuf.Empty 13, // 34: wwapi.v1.WWApi.NodeList:output_type -> wwapi.v1.NodeListResponse 13, // 35: wwapi.v1.WWApi.NodeSet:output_type -> wwapi.v1.NodeListResponse - 20, // 36: wwapi.v1.WWApi.NodeStatus:output_type -> wwapi.v1.NodeStatusResponse - 21, // 37: wwapi.v1.WWApi.Version:output_type -> wwapi.v1.VersionResponse + 21, // 36: wwapi.v1.WWApi.NodeStatus:output_type -> wwapi.v1.NodeStatusResponse + 22, // 37: wwapi.v1.WWApi.Version:output_type -> wwapi.v1.VersionResponse 27, // [27:38] is the sub-list for method output_type 16, // [16:27] is the sub-list for method input_type 16, // [16:16] is the sub-list for extension type_name @@ -1905,7 +2011,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeDeleteParameter); i { + switch v := v.(*NodeYaml); i { case 0: return &v.state case 1: @@ -1917,7 +2023,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeSetParameter); i { + switch v := v.(*NodeDeleteParameter); i { case 0: return &v.state case 1: @@ -1929,7 +2035,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeStatus); i { + switch v := v.(*NodeSetParameter); i { case 0: return &v.state case 1: @@ -1941,7 +2047,7 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NodeStatusResponse); i { + switch v := v.(*NodeStatus); i { case 0: return &v.state case 1: @@ -1953,6 +2059,18 @@ func file_routes_proto_init() { } } file_routes_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeStatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_routes_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*VersionResponse); i { case 0: return &v.state @@ -1964,6 +2082,18 @@ func file_routes_proto_init() { return nil } } + file_routes_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CanWriteConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1971,7 +2101,7 @@ func file_routes_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_routes_proto_rawDesc, NumEnums: 1, - NumMessages: 27, + NumMessages: 29, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/pkg/api/util/util.go b/internal/pkg/api/util/util.go index 273b5594..093183ed 100644 --- a/internal/pkg/api/util/util.go +++ b/internal/pkg/api/util/util.go @@ -1,6 +1,11 @@ package util import ( + "syscall" + + "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/hpcng/warewulf/internal/pkg/node" + "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/manifoldco/promptui" ) @@ -18,4 +23,19 @@ func ConfirmationPrompt(label string) (yes bool) { yes = true } return -} \ No newline at end of file +} + +/* +Simple check if the config can be written in case wwctl isn't run as root +*/ +func CanWriteConfig() (canwrite *wwapiv1.CanWriteConfig) { + canwrite = new(wwapiv1.CanWriteConfig) + err := syscall.Access(node.ConfigFile, syscall.O_RDWR) + if err != nil { + wwlog.Warn("Couldn't open %s:%s", node.ConfigFile, err) + canwrite.CanWriteConfig = false + } else { + canwrite.CanWriteConfig = true + } + return canwrite +} diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index 26bbcac4..2cd221ee 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -3,7 +3,9 @@ package node /****** * YAML data representations ******/ - +/* +Structure of which goes to disk +*/ type NodeYaml struct { WWInternal int `yaml:"WW_INTERNAL"` NodeProfiles map[string]*NodeConf @@ -11,7 +13,7 @@ type NodeYaml struct { } /* -NodeConf is the datastructure which is stored on disk. +NodeConf is the datastructure describing a node and a profile which in disk format. */ type NodeConf struct { Comment string `yaml:"comment,omitempty" lopt:"comment" comment:"Set arbitrary string comment"` @@ -19,11 +21,11 @@ type NodeConf struct { ContainerName string `yaml:"container name,omitempty" lopt:"container" sopt:"C" comment:"Set container name"` Ipxe string `yaml:"ipxe template,omitempty" lopt:"ipxe" comment:"Set the iPXE template name"` // Deprecated start - // Kernel settings here are deprecated and here for backward comptability + // Kernel settings here are deprecated and here for backward compatibility KernelVersion string `yaml:"kernel version,omitempty"` KernelOverride string `yaml:"kernel override,omitempty"` KernelArgs string `yaml:"kernel args,omitempty"` - // Ipmi settings herer are deprecated and here for backward comptability + // Ipmi settings herer are deprecated and here for backward compatibility IpmiUserName string `yaml:"ipmi username,omitempty"` IpmiPassword string `yaml:"ipmi password,omitempty"` IpmiIpaddr string `yaml:"ipmi ipaddr,omitempty"` diff --git a/internal/pkg/node/methods.go b/internal/pkg/node/methods.go index f33b7c3a..5f8f2d6f 100644 --- a/internal/pkg/node/methods.go +++ b/internal/pkg/node/methods.go @@ -26,8 +26,7 @@ func FilterByName(set []NodeInfo, searchList []string) []NodeInfo { if len(searchList) > 0 { for _, search := range searchList { for _, entry := range set { - b, _ := regexp.MatchString("^"+search+"$", entry.Id.Get()) - if b { + if match, _ := regexp.MatchString("^"+search+"$", entry.Id.Get()); match { unique[entry.Id.Get()] = entry } } @@ -42,6 +41,24 @@ func FilterByName(set []NodeInfo, searchList []string) []NodeInfo { return ret } +/* +Filter a given map of NodeConf against given regular expression. +*/ +func FilterMapByName(inputMap map[string]*NodeConf, searchList []string) (retMap map[string]*NodeConf) { + retMap = map[string]*NodeConf{} + if len(searchList) > 0 { + for _, search := range searchList { + for name, nConf := range inputMap { + if match, _ := regexp.MatchString("^"+search+"$", name); match { + retMap[name] = nConf + } + } + + } + } + return retMap +} + /********** * * Sets diff --git a/internal/pkg/node/transformers.go b/internal/pkg/node/transformers.go index 8ba52b68..bed18213 100644 --- a/internal/pkg/node/transformers.go +++ b/internal/pkg/node/transformers.go @@ -2,8 +2,10 @@ package node import ( "reflect" + "strings" "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" ) @@ -186,6 +188,10 @@ 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) @@ -520,3 +526,145 @@ func (netDev *NetDevEntry) setterFrom(netYaml *NetDevs, nameArg string, } } } + +/* +Create a string slice, where every element represents a yaml entry +*/ +func (nodeConf *NodeConf) UnmarshalConf(excludeList []string) (lines []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("lopt") != "" { + if ymlStr, ok := getYamlString(nodeInfoType.Elem().Field(i), excludeList); ok { + lines = append(lines, ymlStr...) + } + } else if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.Ptr { + nestType := reflect.TypeOf(nodeInfoVal.Elem().Field(i).Interface()) + if ymlStr, ok := getYamlString(nodeInfoType.Elem().Field(i), excludeList); ok { + lines = append(lines, ymlStr...) + } + for j := 0; j < nestType.Elem().NumField(); j++ { + if nestType.Elem().Field(j).Tag.Get("lopt") != "" && + !util.InSlice(excludeList, nestType.Elem().Field(j).Tag.Get("lopt")) { + if ymlStr, ok := getYamlString(nestType.Elem().Field(j), excludeList); ok { + for _, str := range ymlStr { + lines = append(lines, " "+str) + } + } + } + } + } 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 + } + } + if ymlStr, ok := getYamlString(nodeInfoType.Elem().Field(i), excludeList); ok { + lines = append(lines, ymlStr[0]+":", " "+key+":") + netType := reflect.TypeOf(netMap[key]) + for j := 0; j < netType.Elem().NumField(); j++ { + if ymlStr, ok := getYamlString(netType.Elem().Field(j), excludeList); ok { + for _, str := range ymlStr { + lines = append(lines, " "+str) + } + } + } // lines + } // this + } //not + } //do + return lines +} + +/* +Get the string of the yaml tag +*/ +func getYamlString(myType reflect.StructField, excludeList []string) ([]string, bool) { + ymlStr := myType.Tag.Get("yaml") + if len(strings.Split(ymlStr, ",")) > 1 { + ymlStr = strings.Split(ymlStr, ",")[0] + } + if util.InSlice(excludeList, ymlStr) { + return []string{""}, false + } else if myType.Tag.Get("lopt") == "" && myType.Type.Kind() == reflect.String { + return []string{""}, false + } + if myType.Type.Kind() == reflect.String { + ymlStr += ": string" + return []string{ymlStr}, true + } else if myType.Type == reflect.TypeOf([]string{}) { + return []string{ymlStr + ":", " - string"}, true + } else if myType.Type == reflect.TypeOf(map[string]string{}) { + return []string{ymlStr + ":", " key: value"}, true + } else if myType.Type.Kind() == reflect.Ptr { + return []string{ymlStr + ":"}, true + } + return []string{ymlStr}, true +} + +/* +Set the field of the NodeConf with the given lopt name, returns true if the +field was found. String slices must be comma separated. Network must have the form +net.$NETNAME.lopt or netname.$NETNAME.lopt +*/ +func (nodeConf *NodeConf) SetLopt(lopt string, value string) (found bool) { + found = false + nodeInfoType := reflect.TypeOf(nodeConf) + nodeInfoVal := reflect.ValueOf(nodeConf) + // try to find the normal fields, networks come later + for i := 0; i < nodeInfoVal.Elem().NumField(); i++ { + //fmt.Println(nodeInfoType.Elem().Field(i).Tag.Get("lopt"), lopt) + if nodeInfoType.Elem().Field(i).Tag.Get("lopt") == lopt { + if nodeInfoType.Elem().Field(i).Type.Kind() == reflect.String { + wwlog.Verbose("Found lopt %s mapping to %s, setting to %s\n", + lopt, nodeInfoType.Elem().Field(i).Name, value) + confVal := nodeInfoVal.Elem().Field(i).Addr().Interface().(*string) + *confVal = value + found = true + } else if nodeInfoType.Elem().Field(i).Type == reflect.TypeOf([]string{}) { + wwlog.Verbose("Found lopt %s mapping to %s, setting to %s\n", + lopt, nodeInfoType.Elem().Field(i).Name, value) + confVal := nodeInfoVal.Elem().Field(i).Addr().Interface().(*[]string) + *confVal = strings.Split(value, ",") + found = true + } + } + } + // check network + loptSlice := strings.Split(lopt, ".") + wwlog.Debug("Trying to get network out of %s\n", loptSlice) + if !found && len(loptSlice) == 3 && (loptSlice[0] == "net" || loptSlice[0] == "network" || loptSlice[0] == "netname") { + if nodeConf.NetDevs == nil { + nodeConf.NetDevs = make(map[string]*NetDevs) + } + if nodeConf.NetDevs[loptSlice[1]] == nil { + nodeConf.NetDevs[loptSlice[1]] = new(NetDevs) + } + netInfoType := reflect.TypeOf(nodeConf.NetDevs[loptSlice[1]]) + netInfoVal := reflect.ValueOf(nodeConf.NetDevs[loptSlice[1]]) + for i := 0; i < netInfoVal.Elem().NumField(); i++ { + if netInfoType.Elem().Field(i).Tag.Get("lopt") == loptSlice[2] { + if netInfoType.Elem().Field(i).Type.Kind() == reflect.String { + wwlog.Verbose("Found lopt %s for network %s mapping to %s, setting to %s\n", + lopt, loptSlice[1], netInfoType.Elem().Field(i).Name, value) + confVal := netInfoVal.Elem().Field(i).Addr().Interface().(*string) + *confVal = value + found = true + } else if netInfoType.Elem().Field(i).Type == reflect.TypeOf([]string{}) { + wwlog.Verbose("Found lopt %s for network %s mapping to %s, setting to %s\n", + lopt, loptSlice[1], netInfoType.Elem().Field(i).Name, value) + confVal := netInfoVal.Elem().Field(i).Addr().Interface().(*[]string) + *confVal = strings.Split(value, ",") + found = true + } + } + } + } + return found +} diff --git a/print_defaults b/print_defaults new file mode 100755 index 00000000..ac446bbd Binary files /dev/null and b/print_defaults differ