Merge pull request #1633 from anderbubble/fix-node-edit
Refactor and fix wwctl <node|profile> edit
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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[:]),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
21
internal/pkg/util/confirm.go
Normal file
21
internal/pkg/util/confirm.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user