diff --git a/internal/app/wwctl/image/delete/main.go b/internal/app/wwctl/image/delete/main.go index e869ac31..f7f266ac 100644 --- a/internal/app/wwctl/image/delete/main.go +++ b/internal/app/wwctl/image/delete/main.go @@ -5,7 +5,7 @@ import ( "github.com/warewulf/warewulf/internal/pkg/api/image" "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - apiutil "github.com/warewulf/warewulf/internal/pkg/api/util" + "github.com/warewulf/warewulf/internal/pkg/util" "github.com/spf13/cobra" ) @@ -16,7 +16,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { } if !SetYes { - yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete image %s", args)) + yes := util.Confirm(fmt.Sprintf("Are you sure you want to delete image %s", args)) if !yes { return } diff --git a/internal/app/wwctl/node/delete/main.go b/internal/app/wwctl/node/delete/main.go index 7da7c19f..e8f22b37 100644 --- a/internal/app/wwctl/node/delete/main.go +++ b/internal/app/wwctl/node/delete/main.go @@ -6,8 +6,8 @@ import ( "github.com/spf13/cobra" apiNode "github.com/warewulf/warewulf/internal/pkg/api/node" "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - "github.com/warewulf/warewulf/internal/pkg/api/util" "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/util" ) func CobraRunE(cmd *cobra.Command, args []string) (err error) { @@ -28,7 +28,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { if len(nodeList) == 0 { return } - yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete %d nodes(s)", len(nodeList))) + yes := util.Confirm(fmt.Sprintf("Are you sure you want to delete %d nodes(s)", len(nodeList))) if !yes { return } diff --git a/internal/app/wwctl/node/edit/main.go b/internal/app/wwctl/node/edit/main.go index 82a81f43..a1d6b599 100644 --- a/internal/app/wwctl/node/edit/main.go +++ b/internal/app/wwctl/node/edit/main.go @@ -1,17 +1,12 @@ package edit import ( - "crypto/sha256" - "encoding/hex" "fmt" "io" "os" "strings" "github.com/spf13/cobra" - apinode "github.com/warewulf/warewulf/internal/pkg/api/node" - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - apiutil "github.com/warewulf/warewulf/internal/pkg/api/util" "github.com/warewulf/warewulf/internal/pkg/node" "github.com/warewulf/warewulf/internal/pkg/util" "github.com/warewulf/warewulf/internal/pkg/warewulfd" @@ -20,120 +15,125 @@ import ( ) func CobraRunE(cmd *cobra.Command, args []string) error { - canWrite, err := apiutil.CanWriteConfig() - if err != nil { - return fmt.Errorf("while checking whether can write config, err: %w", err) - } - if !canWrite.CanWriteConfig { - return fmt.Errorf("can not write to config exiting") + if !node.CanWriteConfig() { + return fmt.Errorf("can not write to config: exiting") } + editor := os.Getenv("EDITOR") if editor == "" { editor = "/bin/vi" } + wwlog.Debug("using editor: %s", editor) + + registry, regErr := node.New() + if regErr != nil { + return regErr + } + if len(args) == 0 { - args = append(args, ".*") + for nodeID := range registry.Nodes { + args = append(args, nodeID) + } } - filterList := wwapiv1.NodeList{ - Output: args, + wwlog.Debug("node list: %v", args) + + tempFile, tempErr := os.CreateTemp(os.TempDir(), "ww4NodeEdit*.yaml") + if tempErr != nil { + return fmt.Errorf("could not create temp file: %s", tempErr) } - nodeListMsg := apinode.FilteredNodes(&filterList) - nodeMap := make(map[string]*node.Node) - // got proper yaml back - _ = yaml.Unmarshal([]byte(nodeListMsg.NodeConfMapYaml), nodeMap) - file, err := os.CreateTemp(os.TempDir(), "ww4NodeEdit*.yaml") - if err != nil { - return fmt.Errorf("could not create temp file: %s", err) + defer os.Remove(tempFile.Name()) + + if !NoHeader { + yamlTemplate := node.UnmarshalConf(node.Node{}, nil) + if _, err := tempFile.WriteString("#nodename:\n# " + strings.Join(yamlTemplate, "\n# ") + "\n"); err != nil { + return err + } } - defer os.Remove(file.Name()) + + origNodes := make(map[string]*node.Node) + for _, nodeID := range args { + if n, ok := registry.Nodes[nodeID]; ok { + origNodes[nodeID] = n + } + } + + if origYaml, err := util.EncodeYaml(origNodes); err != nil { + return err + } else if _, err := tempFile.Write(origYaml); err != nil { + return err + } + + sum1, sumErr := util.HashFile(tempFile) + if sumErr != nil { + return sumErr + } + wwlog.Debug("original hash: %s", sum1) + for { - _ = file.Truncate(0) - _, _ = file.Seek(0, 0) - if !NoHeader { - yamlTemplate := node.UnmarshalConf(node.Node{}, []string{"tagsdel"}) - _, _ = file.WriteString("#nodename:\n# " + strings.Join(yamlTemplate, "\n# ") + "\n") + if err := util.ExecInteractive(editor, tempFile.Name()); err != nil { + return fmt.Errorf("editor process exited with non-zero code: %w", err) } - _, _ = 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) + + sum2, sumErr := util.HashFile(tempFile) + if sumErr != nil { + return sumErr } - sum1 := hex.EncodeToString(hasher.Sum(nil)) - err = util.ExecInteractive(editor, file.Name()) - if err != nil { - return fmt.Errorf("editor process existed with non-zero") - } - _, _ = 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) + wwlog.Debug("edited hash: %s", sum2) + if sum1 != sum2 { - wwlog.Debug("Nodes were modified") - modifiedNodeMap := make(map[string]*node.Node) - _, _ = file.Seek(0, 0) - // ignore error as only may occurs under strange circumstances - buffer, _ := io.ReadAll(file) - err = yaml.Unmarshal(buffer, modifiedNodeMap) + wwlog.Debug("modified") + + editYaml, err := io.ReadAll(tempFile) if err != nil { - yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", err)) - if yes { - continue - } else { - break - } + return err } - var checkErrors []error - for nodeName, node := range modifiedNodeMap { - err = node.Check() - if err != nil { - checkErrors = append(checkErrors, fmt.Errorf("node: %s parse error: %s", nodeName, err)) - } - } - if len(checkErrors) != 0 { - yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", checkErrors)) - if yes { + editNodes := make(map[string]*node.Node) + if err := yaml.Unmarshal(editYaml, &editNodes); err != nil { + wwlog.Error("%v\n", err) + if util.Confirm("Parse error: retry") { continue } else { break } } - 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 { - err = apinode.NodeDelete(&wwapiv1.NodeDeleteParameter{NodeNames: nodeList, Force: true}) - if err != nil { - wwlog.Verbose("Problem deleting nodes before modification %s", err) + var added, deleted, updated int + for nodeID := range origNodes { + if editNode, ok := editNodes[nodeID]; !ok || editNode == nil { + wwlog.Verbose("delete node: %s", nodeID) + delete(registry.Nodes, nodeID) + deleted += 1 } - buffer, _ = yaml.Marshal(modifiedNodeMap) - newHash := apinode.Hash() - err = apinode.NodeAddFromYaml(&wwapiv1.NodeYaml{ - NodeConfMapYaml: string(buffer), - Hash: newHash.Hash, - }) - if err != nil { - return fmt.Errorf("got following problem when writing back yaml: %s", err) - } - break } + for nodeID := range editNodes { + if _, ok := origNodes[nodeID]; !ok { + wwlog.Verbose("add node: %s", nodeID) + added += 1 + registry.Nodes[nodeID] = editNodes[nodeID] + } else if equalYaml, err := util.EqualYaml(origNodes[nodeID], editNodes[nodeID]); err != nil { + return err + } else if !equalYaml { + wwlog.Verbose("update node: %s", nodeID) + updated += 1 + registry.Nodes[nodeID] = editNodes[nodeID] + } + } + + if util.Confirm(fmt.Sprintf("Are you sure you want to add %d, delete %d, and update %d nodes", added, deleted, updated)) { + if err := registry.Persist(); err != nil { + return err + } + + if err := warewulfd.DaemonReload(); err != nil { + return fmt.Errorf("failed to reload warewulf daemon: %w", err) + } + } + break } else { + wwlog.Verbose("No changes") break } } - err = warewulfd.DaemonReload() - if err != nil { - return fmt.Errorf("failed to reload warewulf daemon: %w", err) - } - return nil } diff --git a/internal/app/wwctl/node/imprt/main.go b/internal/app/wwctl/node/imprt/main.go index 5bc1e81c..f83fabd1 100644 --- a/internal/app/wwctl/node/imprt/main.go +++ b/internal/app/wwctl/node/imprt/main.go @@ -10,8 +10,8 @@ import ( "github.com/spf13/cobra" apinode "github.com/warewulf/warewulf/internal/pkg/api/node" "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - apiutil "github.com/warewulf/warewulf/internal/pkg/api/util" "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/util" "gopkg.in/yaml.v3" ) @@ -30,7 +30,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { 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))) + yes := util.Confirm(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 { @@ -68,7 +68,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } } } - yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to import %d nodes", len(importMap))) + yes := util.Confirm(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) diff --git a/internal/app/wwctl/node/set/main.go b/internal/app/wwctl/node/set/main.go index 94b208d7..8972a535 100644 --- a/internal/app/wwctl/node/set/main.go +++ b/internal/app/wwctl/node/set/main.go @@ -7,9 +7,9 @@ import ( "github.com/spf13/cobra" apinode "github.com/warewulf/warewulf/internal/pkg/api/node" "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - "github.com/warewulf/warewulf/internal/pkg/api/util" "github.com/warewulf/warewulf/internal/pkg/hostlist" "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/util" "github.com/warewulf/warewulf/internal/pkg/wwlog" "gopkg.in/yaml.v3" ) @@ -81,7 +81,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err if err != nil { return nil } - yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes(s)", nodeCount)) + yes := util.Confirm(fmt.Sprintf("Are you sure you want to modify %d nodes(s)", nodeCount)) if !yes { return nil } diff --git a/internal/app/wwctl/profile/edit/main.go b/internal/app/wwctl/profile/edit/main.go index f981ada4..f9f795e6 100644 --- a/internal/app/wwctl/profile/edit/main.go +++ b/internal/app/wwctl/profile/edit/main.go @@ -1,132 +1,136 @@ package edit import ( - "crypto/sha256" - "encoding/hex" "fmt" "io" "os" "strings" - apinode "github.com/warewulf/warewulf/internal/pkg/api/node" - "github.com/spf13/cobra" - apiprofile "github.com/warewulf/warewulf/internal/pkg/api/profile" - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - apiutil "github.com/warewulf/warewulf/internal/pkg/api/util" "github.com/warewulf/warewulf/internal/pkg/node" "github.com/warewulf/warewulf/internal/pkg/util" + "github.com/warewulf/warewulf/internal/pkg/warewulfd" "github.com/warewulf/warewulf/internal/pkg/wwlog" "gopkg.in/yaml.v3" ) func CobraRunE(cmd *cobra.Command, args []string) error { - canWrite, err := apiutil.CanWriteConfig() - if err != nil { - return fmt.Errorf("while checking whether can write config, err: %s", err) - } - if !canWrite.CanWriteConfig { - return fmt.Errorf("can not write to config exiting") + if !node.CanWriteConfig() { + return fmt.Errorf("can not write to config: exiting") } + 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.Profile) - // got proper yaml back - _ = yaml.Unmarshal([]byte(profileListMsg.NodeConfMapYaml), profileMap) - file, err := os.CreateTemp(os.TempDir(), "ww4ProfileEdit*.yaml") - if err != nil { - wwlog.Error("Could not create temp file:%s \n", err) - } - defer os.Remove(file.Name()) - for { - _ = file.Truncate(0) - _, _ = file.Seek(0, 0) - if !NoHeader { - yamlTemplate := node.UnmarshalConf(node.Profile{}, []string{"tagsdel"}) - _, _ = 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 { - return fmt.Errorf("editor process existed with non-zero: %s", err) - } - _, _ = 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("Profiles were modified") - modifiedProfileMap := make(map[string]*node.Profile) - _, _ = 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 { - yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", err)) - if yes { - continue - } else { - break - } - } - var checkErrors []error - for nodeName, node := range modifiedProfileMap { - err = node.Check() - if err != nil { - checkErrors = append(checkErrors, fmt.Errorf("profile: %s parse error: %s", nodeName, err)) - } - } - if len(checkErrors) != 0 { - yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Got following error on parsing: %s, Retry", checkErrors)) - if yes { - continue - } else { - break - } - } - pList := make([]string, len(profileMap)) - i := 0 - for key := range profileMap { - pList[i] = key - i++ - } - yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes", len(modifiedProfileMap))) - if yes { - err = apiprofile.ProfileDelete(&wwapiv1.NodeDeleteParameter{NodeNames: pList, Force: true}) + wwlog.Debug("using editor: %s", editor) - if err != nil { - wwlog.Verbose("Problem deleting nodes before modification %s", err) - } - buffer, _ = yaml.Marshal(modifiedProfileMap) - newHash := apinode.Hash() - err = apiprofile.ProfileAddFromYaml(&wwapiv1.NodeAddParameter{ - NodeConfYaml: string(buffer), - Hash: newHash.Hash, - }) - if err != nil { - return fmt.Errorf("got following problem when writing back yaml: %s", err) - } - break + registry, regErr := node.New() + if regErr != nil { + return regErr + } + + if len(args) == 0 { + for profileID := range registry.NodeProfiles { + args = append(args, profileID) + } + } + wwlog.Debug("profile list: %v", args) + + tempFile, tempErr := os.CreateTemp(os.TempDir(), "ww4ProfileEdit*.yaml") + if tempErr != nil { + return fmt.Errorf("could not create temp file: %s", tempErr) + } + defer os.Remove(tempFile.Name()) + + if !NoHeader { + yamlTemplate := node.UnmarshalConf(node.Profile{}, nil) + if _, err := tempFile.WriteString("#profilename:\n# " + strings.Join(yamlTemplate, "\n# ") + "\n"); err != nil { + return err + } + } + + origProfiles := make(map[string]*node.Profile) + for _, profileID := range args { + if n, ok := registry.NodeProfiles[profileID]; ok { + origProfiles[profileID] = n + } + } + + if origYaml, err := util.EncodeYaml(origProfiles); err != nil { + return err + } else if _, err := tempFile.Write(origYaml); err != nil { + return err + } + + sum1, sumErr := util.HashFile(tempFile) + if sumErr != nil { + return sumErr + } + wwlog.Debug("original hash: %s", sum1) + + for { + if err := util.ExecInteractive(editor, tempFile.Name()); err != nil { + return fmt.Errorf("editor process exited with non-zero code: %w", err) + } + + sum2, sumErr := util.HashFile(tempFile) + if sumErr != nil { + return sumErr + } + wwlog.Debug("edited hash: %s", sum2) + + if sum1 != sum2 { + wwlog.Debug("modified") + + editYaml, err := io.ReadAll(tempFile) + if err != nil { + return err } + editProfiles := make(map[string]*node.Profile) + if err := yaml.Unmarshal(editYaml, &editProfiles); err != nil { + wwlog.Error("%v\n", err) + if util.Confirm("Parse error: retry") { + continue + } else { + break + } + } + + var added, deleted, updated int + for profileID := range origProfiles { + if editProfile, ok := editProfiles[profileID]; !ok || editProfile == nil { + wwlog.Verbose("delete profile: %s", profileID) + delete(registry.NodeProfiles, profileID) + deleted += 1 + } + } + for profileID := range editProfiles { + if _, ok := origProfiles[profileID]; !ok { + wwlog.Verbose("add profile: %s", profileID) + added += 1 + registry.NodeProfiles[profileID] = editProfiles[profileID] + } else if equalYaml, err := util.EqualYaml(origProfiles[profileID], editProfiles[profileID]); err != nil { + return err + } else if !equalYaml { + wwlog.Verbose("update profile: %s", profileID) + updated += 1 + registry.NodeProfiles[profileID] = editProfiles[profileID] + } + } + + if util.Confirm(fmt.Sprintf("Are you sure you want to add %d, delete %d, and update %d profiles", added, deleted, updated)) { + if err := registry.Persist(); err != nil { + return err + } + + if err := warewulfd.DaemonReload(); err != nil { + return fmt.Errorf("failed to reload warewulf daemon: %w", err) + } + } + break } else { + wwlog.Verbose("No changes") break } } diff --git a/internal/app/wwctl/profile/set/main.go b/internal/app/wwctl/profile/set/main.go index 91410442..ab2698a8 100644 --- a/internal/app/wwctl/profile/set/main.go +++ b/internal/app/wwctl/profile/set/main.go @@ -7,8 +7,8 @@ import ( "github.com/spf13/cobra" apiprofile "github.com/warewulf/warewulf/internal/pkg/api/profile" "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - "github.com/warewulf/warewulf/internal/pkg/api/util" "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/util" "github.com/warewulf/warewulf/internal/pkg/wwlog" "gopkg.in/yaml.v3" ) @@ -79,7 +79,7 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err if err != nil { return err } - yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d profile(s)", profileCount)) + yes := util.Confirm(fmt.Sprintf("Are you sure you want to modify %d profile(s)", profileCount)) if !yes { return err } diff --git a/internal/pkg/api/node/hash.go b/internal/pkg/api/node/hash.go deleted file mode 100644 index 71037f1a..00000000 --- a/internal/pkg/api/node/hash.go +++ /dev/null @@ -1,20 +0,0 @@ -package apinode - -import ( - "encoding/hex" - - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/wwlog" -) - -func Hash() *wwapiv1.NodeDBHash { - config, err := node.New() - if err != nil { - wwlog.Warn("couldb't read config") - } - hash := config.Hash() - return &wwapiv1.NodeDBHash{ - Hash: hex.EncodeToString(hash[:]), - } -} diff --git a/internal/pkg/api/profile/delete.go b/internal/pkg/api/profile/delete.go deleted file mode 100644 index b219da33..00000000 --- a/internal/pkg/api/profile/delete.go +++ /dev/null @@ -1,75 +0,0 @@ -package apiprofile - -import ( - "fmt" - - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - "github.com/warewulf/warewulf/internal/pkg/hostlist" - "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/warewulfd" - "github.com/warewulf/warewulf/internal/pkg/wwlog" -) - -// ProfileDelete adds profile deletion for management by Warewulf. -func ProfileDelete(ndp *wwapiv1.NodeDeleteParameter) (err error) { - nodeDB, profileList, err := ProfileDeleteParameterCheck(ndp, false) - if err != nil { - return - } - if nodeDB.StringHash() != ndp.Hash && !ndp.Force { - return fmt.Errorf("got wrong hash, not modifying profile database") - } - for _, p := range profileList { - err = nodeDB.DelProfile(p) - if err != nil { - return - } - } - - err = nodeDB.Persist() - if err != nil { - return fmt.Errorf("failed to persist nodedb: %w", err) - } - err = warewulfd.DaemonReload() - if err != nil { - return fmt.Errorf("failed to reload warewulf daemon: %w", err) - } - 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) (nodeDB node.NodesYaml, profileList []string, 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 - } - profileList = nodeDB.ListAllProfiles() - profileArgs := hostlist.Expand(ndp.NodeNames) - for _, r := range profileArgs { - match := false - for _, p := range profileList { - if p == r { - profileList = append(profileList, p) - match = true - } - } - - if !match { - wwlog.Error("no match for profile: %s", r) - } - } - - if len(profileList) == 0 { - wwlog.Warn("no profiles found\n") - } - return -} diff --git a/internal/pkg/api/profile/edit.go b/internal/pkg/api/profile/edit.go deleted file mode 100644 index c8a384a0..00000000 --- a/internal/pkg/api/profile/edit.go +++ /dev/null @@ -1,59 +0,0 @@ -package apiprofile - -import ( - "fmt" - "os" - - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - "github.com/warewulf/warewulf/internal/pkg/node" - "github.com/warewulf/warewulf/internal/pkg/wwlog" - "gopkg.in/yaml.v3" -) - -/* -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) - } - profiles, _ := nodeDB.FindAllProfiles() - profiles = node.FilterProfileListByName(profiles, profileList.Output) - buffer, _ := yaml.Marshal(profiles) - retVal := wwapiv1.NodeYaml{ - NodeConfMapYaml: string(buffer), - } - return &retVal -} - -/* -Add profiles from yaml -*/ -func ProfileAddFromYaml(nodeList *wwapiv1.NodeAddParameter) (err error) { - nodeDB, err := node.New() - if err != nil { - return fmt.Errorf("couldn't open NodeDB: %w", err) - } - if nodeDB.StringHash() != nodeList.Hash && !nodeList.Force { - return fmt.Errorf("got wrong hash, not modifying profile database") - } - - profileMap := make(map[string]*node.Profile) - err = yaml.Unmarshal([]byte(nodeList.NodeConfYaml), profileMap) - if err != nil { - return fmt.Errorf("couldn't unmarshall Yaml: %w", err) - } - for profileName, profile := range profileMap { - err = nodeDB.SetProfile(profileName, *profile) - if err != nil { - return fmt.Errorf("couldn't set profile: %w", err) - } - } - err = nodeDB.Persist() - if err != nil { - return fmt.Errorf("failed to persist nodedb: %w", err) - } - return nil -} diff --git a/internal/pkg/api/util/util.go b/internal/pkg/api/util/util.go deleted file mode 100644 index f68bd9b0..00000000 --- a/internal/pkg/api/util/util.go +++ /dev/null @@ -1,42 +0,0 @@ -package util - -import ( - "syscall" - - "github.com/manifoldco/promptui" - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" - "github.com/warewulf/warewulf/internal/pkg/config" - "github.com/warewulf/warewulf/internal/pkg/wwlog" -) - -// ConfirmationPrompt prompt is a blocking confirmation prompt. -// Returns true on y or yes user input. -func ConfirmationPrompt(label string) (yes bool) { - - prompt := promptui.Prompt{ - Label: label, - IsConfirm: true, - } - - result, _ := prompt.Run() - if result == "y" || result == "yes" { - yes = true - } - return -} - -/* -Simple check if the config can be written in case wwctl isn't run as root -*/ -func CanWriteConfig() (canwrite *wwapiv1.CanWriteConfig, err error) { - canwrite = new(wwapiv1.CanWriteConfig) - nodesConf := config.Get().Paths.NodesConf() - err = syscall.Access(nodesConf, syscall.O_RDWR) - if err != nil { - wwlog.Warn("Couldn't open %s:%s", nodesConf, err) - canwrite.CanWriteConfig = false - } else { - canwrite.CanWriteConfig = true - } - return canwrite, err -} diff --git a/internal/pkg/node/checkconf.go b/internal/pkg/node/checkconf.go index e80ecd01..f053ed4f 100644 --- a/internal/pkg/node/checkconf.go +++ b/internal/pkg/node/checkconf.go @@ -27,10 +27,15 @@ func (profileConf *Profile) Check() (err error) { } func check(infoType reflect.Type, infoVal reflect.Value) (err error) { + if !infoVal.Elem().IsValid() { + return nil + } // now iterate of every field for i := 0; i < infoVal.Elem().NumField(); i++ { - if infoType.Elem().Field(i).Type.Kind() == reflect.String { - newFmt, err := checker(infoVal.Elem().Field(i).Interface().(string), infoType.Elem().Field(i).Tag.Get("type")) + if !infoType.Elem().Field(i).IsExported() { + continue + } else if infoType.Elem().Field(i).Type.Kind() == reflect.String { + newFmt, err := checker(infoVal.Elem().Field(i).String(), infoType.Elem().Field(i).Tag.Get("type")) if err != nil { return fmt.Errorf("field: %s value:%s err: %s", infoType.Elem().Field(i).Name, infoVal.Elem().Field(i).String(), err) } else if newFmt != "" { diff --git a/internal/pkg/node/constructors.go b/internal/pkg/node/constructors.go index 0de328be..e819ad2a 100644 --- a/internal/pkg/node/constructors.go +++ b/internal/pkg/node/constructors.go @@ -3,6 +3,7 @@ package node import ( "os" "sort" + "syscall" warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" "github.com/warewulf/warewulf/internal/pkg/wwlog" @@ -10,6 +11,10 @@ import ( "gopkg.in/yaml.v3" ) +func CanWriteConfig() bool { + return syscall.Access(warewulfconf.Get().Paths.NodesConf(), syscall.O_RDWR) == nil +} + /* Creates a new nodeDb object from the on-disk configuration */ diff --git a/internal/pkg/node/modifiers.go b/internal/pkg/node/modifiers.go index be9554aa..e8967c97 100644 --- a/internal/pkg/node/modifiers.go +++ b/internal/pkg/node/modifiers.go @@ -6,9 +6,9 @@ import ( "os" "github.com/pkg/errors" - "gopkg.in/yaml.v3" warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" + "github.com/warewulf/warewulf/internal/pkg/util" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -136,10 +136,8 @@ func (config *NodesYaml) PersistToFile(configFile string) error { return nil } -/* -Dump returns a YAML document representing the nodeDb -instance. Passes through any errors generated by yaml.Marshal. -*/ +// Dump returns a YAML document representing the nodeDb instance. Passes through any errors +// generated by yaml encoding. func (config *NodesYaml) Dump() ([]byte, error) { // flatten out profiles and nodes for _, val := range config.NodeProfiles { @@ -148,10 +146,5 @@ func (config *NodesYaml) Dump() ([]byte, error) { for _, val := range config.Nodes { val.Flatten() } - var buf bytes.Buffer - // Run through encoder - yamlEncoder := yaml.NewEncoder(&buf) - yamlEncoder.SetIndent(2) - err := yamlEncoder.Encode(config) - return buf.Bytes(), err //yaml.Marshal(config) + return util.EncodeYaml(config) } diff --git a/internal/pkg/util/confirm.go b/internal/pkg/util/confirm.go new file mode 100644 index 00000000..39f61cc2 --- /dev/null +++ b/internal/pkg/util/confirm.go @@ -0,0 +1,21 @@ +package util + +import ( + "github.com/manifoldco/promptui" +) + +// ConfirmationPrompt prompt is a blocking confirmation prompt. +// Returns true on y or yes user input. +func Confirm(label string) (yes bool) { + + prompt := promptui.Prompt{ + Label: label, + IsConfirm: true, + } + + result, _ := prompt.Run() + if result == "y" || result == "yes" { + yes = true + } + return +} diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index e037c343..f00dfa2f 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -2,7 +2,11 @@ package util import ( "bufio" + "bytes" + "crypto/sha256" + "encoding/hex" "fmt" + "gopkg.in/yaml.v3" "io" "io/fs" "net" @@ -575,3 +579,40 @@ func ByteToString(b int64) string { } return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp]) } + +func HashFile(file *os.File) (string, error) { + if prevOffset, err := file.Seek(0, 0); err != nil { + return "", err + } else { + hasher := sha256.New() + if _, err := io.Copy(hasher, file); err != nil { + return "", err + } + if _, err := file.Seek(prevOffset, 0); err != nil { + return "", err + } + return hex.EncodeToString(hasher.Sum(nil)), nil + } +} + +func EncodeYaml(data interface{}) ([]byte, error) { + buf := new(bytes.Buffer) + encoder := yaml.NewEncoder(buf) + encoder.SetIndent(2) + err := encoder.Encode(data) + return buf.Bytes(), err +} + +func EqualYaml(a interface{}, b interface{}) (bool, error) { + aYaml, err := EncodeYaml(a) + if err != nil { + return false, err + } + + bYaml, err := EncodeYaml(b) + if err != nil { + return false, err + } + + return bytes.Equal(aYaml, bYaml), nil +}