Refactor gRPC-based node functions
Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
package apinode
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"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/util"
|
||||
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// NodeAdd adds nodes for management by Warewulf.
|
||||
func NodeAdd(nap *wwapiv1.NodeAddParameter) (err error) {
|
||||
|
||||
if nap == nil {
|
||||
return fmt.Errorf("NodeAddParameter is nil")
|
||||
}
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open node database: %w", err)
|
||||
}
|
||||
dbHash := nodeDB.Hash()
|
||||
if hex.EncodeToString(dbHash[:]) != nap.Hash && !nap.Force {
|
||||
return fmt.Errorf("got wrong hash, not modifying node database")
|
||||
}
|
||||
node_args := hostlist.Expand(nap.NodeNames)
|
||||
var ipv4, ipmiaddr net.IP
|
||||
for _, a := range node_args {
|
||||
n, err := nodeDB.AddNode(a)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add node: %w", err)
|
||||
}
|
||||
err = yaml.Unmarshal([]byte(nap.NodeConfYaml), &n)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode nodeConf: %w", err)
|
||||
}
|
||||
wwlog.Info("Added node: %s", a)
|
||||
for _, dev := range n.NetDevs {
|
||||
if !ipv4.IsUnspecified() && ipv4 != nil {
|
||||
// if more nodes are added increment IPv4 address
|
||||
ipv4 = util.IncrementIPv4(ipv4, 1)
|
||||
wwlog.Verbose("Incremented IP addr to %s", ipv4)
|
||||
dev.Ipaddr = ipv4
|
||||
|
||||
} else if !dev.Ipaddr.IsUnspecified() {
|
||||
ipv4 = dev.Ipaddr
|
||||
}
|
||||
}
|
||||
if n.Ipmi != nil {
|
||||
if !ipmiaddr.IsUnspecified() && ipmiaddr != nil {
|
||||
ipmiaddr = util.IncrementIPv4(ipmiaddr, 1)
|
||||
wwlog.Verbose("Incremented ipmi IP addr to %s", ipmiaddr)
|
||||
n.Ipmi.Ipaddr = ipmiaddr
|
||||
} else if !n.Ipmi.Ipaddr.IsUnspecified() {
|
||||
ipmiaddr = n.Ipmi.Ipaddr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = nodeDB.Persist()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to persist new node: %w", err)
|
||||
}
|
||||
|
||||
err = warewulfd.DaemonReload()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to reload warewulf daemon: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package apinode
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// NodeDelete adds nodes for management by Warewulf.
|
||||
func NodeDelete(ndp *wwapiv1.NodeDeleteParameter) (err error) {
|
||||
|
||||
var nodeList []node.Node
|
||||
nodeList, err = NodeDeleteParameterCheck(ndp, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("Failed to open node database: %s", err)
|
||||
return
|
||||
}
|
||||
dbHash := nodeDB.Hash()
|
||||
if hex.EncodeToString(dbHash[:]) != ndp.Hash && !ndp.Force {
|
||||
return fmt.Errorf("got wrong hash, not modifying node database")
|
||||
}
|
||||
|
||||
for _, n := range nodeList {
|
||||
err := nodeDB.DelNode(n.Id())
|
||||
if err != nil {
|
||||
wwlog.Error("%s", err)
|
||||
} else {
|
||||
wwlog.Verbose("Deleting node: %s\n", n.Id())
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// NodeDeleteParameterCheck does error checking on NodeDeleteParameter.
|
||||
// Output to the console if console is true.
|
||||
// Returns the nodes to delete.
|
||||
func NodeDeleteParameterCheck(ndp *wwapiv1.NodeDeleteParameter, console bool) (nodeList []node.Node, err error) {
|
||||
|
||||
if ndp == nil {
|
||||
err = fmt.Errorf("NodeDeleteParameter is nil")
|
||||
return
|
||||
}
|
||||
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("Failed to open node database: %s", err)
|
||||
return
|
||||
}
|
||||
dbHash := nodeDB.Hash()
|
||||
if hex.EncodeToString(dbHash[:]) != ndp.Hash && !ndp.Force {
|
||||
wwlog.Debug("got hash: %s", ndp.Hash)
|
||||
wwlog.Debug("actual hash: %s", hex.EncodeToString(dbHash[:]))
|
||||
err = fmt.Errorf("got wrong hash, not modifying node database")
|
||||
return
|
||||
}
|
||||
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
wwlog.Error("Could not get node list: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
node_args := hostlist.Expand(ndp.NodeNames)
|
||||
|
||||
for _, r := range node_args {
|
||||
var match bool
|
||||
for _, n := range nodes {
|
||||
if n.Id() == r {
|
||||
nodeList = append(nodeList, n)
|
||||
match = true
|
||||
}
|
||||
}
|
||||
|
||||
if !match {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: No match for node: %s\n", r)
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodeList) == 0 {
|
||||
fmt.Printf("No nodes found\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package apinode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
/*
|
||||
Add nodes from yaml
|
||||
*/
|
||||
func NodeAddFromYaml(nodeList *wwapiv1.NodeYaml) (err error) {
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open NodeDB: %w", err)
|
||||
}
|
||||
nodeMap := make(map[string]*node.Node)
|
||||
err = yaml.Unmarshal([]byte(nodeList.NodeConfMapYaml), nodeMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not unmarshal Yaml: %w", err)
|
||||
}
|
||||
for nodeName, nodeData := range nodeMap {
|
||||
if _, err = nodeDB.GetNodeOnly(nodeName); err == node.ErrNotFound {
|
||||
_, err = nodeDB.AddNode(nodeName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't add new node: %w", err)
|
||||
}
|
||||
}
|
||||
err = nodeDB.SetNode(nodeName, *nodeData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't set node: %w", err)
|
||||
}
|
||||
}
|
||||
err = nodeDB.Persist()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to persist nodedb: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package apinode
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"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/wwlog"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
/*
|
||||
NodeList lists all to none of the nodes managed by Warewulf. Returns
|
||||
a formated string slice, with each line as separate string
|
||||
*/
|
||||
func NodeList(nodeGet *wwapiv1.GetNodeList) (nodeList wwapiv1.NodeList, err error) {
|
||||
// nil is okay for nodeNames
|
||||
nodeDB, err := node.New()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nodes, err := nodeDB.FindAllNodes()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nodeGet.Nodes = hostlist.Expand(nodeGet.Nodes)
|
||||
sort.Strings(nodeGet.Nodes)
|
||||
|
||||
if nodeGet.Type == wwapiv1.GetNodeList_Simple {
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s", "NODE NAME", "PROFILES", "NETWORK"))
|
||||
for _, n := range node.FilterNodeListByName(nodes, nodeGet.Nodes) {
|
||||
var netNames []string
|
||||
for k := range n.NetDevs {
|
||||
netNames = append(netNames, k)
|
||||
}
|
||||
sort.Strings(netNames)
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s", n.Id(), strings.Join(n.Profiles, ","), strings.Join(netNames, ", ")))
|
||||
}
|
||||
} else if nodeGet.Type == wwapiv1.GetNodeList_Network {
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s:=:%s", "NODE", "NETWORK", "HWADDR", "IPADDR", "GATEWAY", "DEVICE"))
|
||||
for _, n := range node.FilterNodeListByName(nodes, nodeGet.Nodes) {
|
||||
if len(n.NetDevs) > 0 {
|
||||
for name := range n.NetDevs {
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s:=:%s", n.Id(), name,
|
||||
n.NetDevs[name].Hwaddr,
|
||||
n.NetDevs[name].Ipaddr,
|
||||
n.NetDevs[name].Gateway,
|
||||
n.NetDevs[name].Device))
|
||||
}
|
||||
} else {
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s:=:%s", n.Id(), "--", "--", "--", "--", "--"))
|
||||
}
|
||||
}
|
||||
} else if nodeGet.Type == wwapiv1.GetNodeList_Ipmi {
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s", "NODE", "IPMI IPADDR", "IPMI PORT", "IPMI USERNAME", "IPMI INTERFACE"))
|
||||
for _, n := range node.FilterNodeListByName(nodes, nodeGet.Nodes) {
|
||||
ipaddr, port, username, iface := "", "", "", ""
|
||||
if n.Ipmi != nil {
|
||||
ipaddr = n.Ipmi.Ipaddr.String()
|
||||
port = n.Ipmi.Port
|
||||
username = n.Ipmi.UserName
|
||||
iface = n.Ipmi.Interface
|
||||
}
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s:=:%s:=:%s",
|
||||
n.Id(),
|
||||
ipaddr,
|
||||
port,
|
||||
username,
|
||||
iface))
|
||||
}
|
||||
} else if nodeGet.Type == wwapiv1.GetNodeList_Long {
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s:=:%s", "NODE NAME", "KERNEL VERSION", "IMAGE", "OVERLAYS (S/R)"))
|
||||
for _, n := range node.FilterNodeListByName(nodes, nodeGet.Nodes) {
|
||||
kernelVersion := ""
|
||||
if n.Kernel != nil {
|
||||
kernelVersion = n.Kernel.Version
|
||||
}
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id(),
|
||||
kernelVersion,
|
||||
n.ImageName,
|
||||
strings.Join(n.SystemOverlay, ",")+"/"+strings.Join(n.RuntimeOverlay, ",")))
|
||||
}
|
||||
} else if nodeGet.Type == wwapiv1.GetNodeList_All {
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s:=:%s", "NODE", "FIELD", "PROFILE", "VALUE"))
|
||||
for _, n := range node.FilterNodeListByName(nodes, nodeGet.Nodes) {
|
||||
if _, fields, err := nodeDB.MergeNode(n.Id()); err != nil {
|
||||
wwlog.Error("unable to merge node %v: %v", n.Id(), err)
|
||||
continue
|
||||
} else {
|
||||
for _, f := range fields.List(n) {
|
||||
nodeList.Output = append(nodeList.Output,
|
||||
fmt.Sprintf("%s:=:%s:=:%s:=:%s", n.Id(), f.Field, f.Source, f.Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if nodeGet.Type == wwapiv1.GetNodeList_YAML || nodeGet.Type == wwapiv1.GetNodeList_JSON {
|
||||
nodeMap := make(map[string]node.Node)
|
||||
for _, n := range node.FilterNodeListByName(nodes, nodeGet.Nodes) {
|
||||
nodeMap[n.Id()] = n
|
||||
}
|
||||
var buf []byte
|
||||
if nodeGet.Type == wwapiv1.GetNodeList_JSON {
|
||||
buf, _ = json.MarshalIndent(nodeMap, "", " ")
|
||||
}
|
||||
if nodeGet.Type == wwapiv1.GetNodeList_YAML {
|
||||
buf, _ = yaml.Marshal(nodeMap)
|
||||
}
|
||||
nodeList.Output = append(nodeList.Output, string(buf))
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package apinode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"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"
|
||||
)
|
||||
|
||||
// NodeSet is the wwapiv1 implmentation for updating node fields.
|
||||
func NodeSet(set *wwapiv1.ConfSetParameter) (err error) {
|
||||
if set == nil {
|
||||
return fmt.Errorf("NodeSetParameter is nil")
|
||||
}
|
||||
var nodeDB node.NodesYaml
|
||||
nodeDB, _, err = NodeSetParameterCheck(set)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = nodeDB.Persist(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = warewulfd.DaemonReload(); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
NodeSetParameterCheck does error checking and returns a modified
|
||||
NodeYml which than can be persisted
|
||||
*/
|
||||
func NodeSetParameterCheck(set *wwapiv1.ConfSetParameter) (nodeDB node.NodesYaml, count uint, err error) {
|
||||
nodeDB, err = node.New()
|
||||
if err != nil {
|
||||
wwlog.Error("Could not open configuration: %s", err)
|
||||
return
|
||||
}
|
||||
if set == nil {
|
||||
err = fmt.Errorf("node set parameter is nil")
|
||||
return
|
||||
}
|
||||
if set.ConfList == nil {
|
||||
err = fmt.Errorf("node nodes to set")
|
||||
return
|
||||
}
|
||||
confs := nodeDB.ListAllNodes()
|
||||
// Note: This does not do expansion on the nodes.
|
||||
if set.AllConfs || (len(set.ConfList) == 0) {
|
||||
wwlog.Warn("this command will modify all nodes/profiles")
|
||||
} else if len(confs) == 0 {
|
||||
wwlog.Warn("no nodes/profiles found")
|
||||
return
|
||||
}
|
||||
for _, nId := range set.ConfList {
|
||||
if util.InSlice(set.ConfList, nId) {
|
||||
wwlog.Debug("evaluating node: %s", nId)
|
||||
var nodePtr *node.Node
|
||||
nodePtr, err = nodeDB.GetNodeOnlyPtr(nId)
|
||||
if err != nil {
|
||||
wwlog.Warn("invalid node: %s", nId)
|
||||
continue
|
||||
}
|
||||
newConf := node.EmptyNode()
|
||||
err = yaml.Unmarshal([]byte(set.NodeConfYaml), &newConf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// merge in
|
||||
err = mergo.Merge(nodePtr, &newConf, mergo.WithOverride, mergo.WithTransformers(node.Transformer{}))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if set.NetdevDelete != "" {
|
||||
if _, ok := nodePtr.NetDevs[set.NetdevDelete]; !ok {
|
||||
err = fmt.Errorf("network device name doesn't exist: %s", set.NetdevDelete)
|
||||
wwlog.Error(fmt.Sprintf("%v", err.Error()))
|
||||
return
|
||||
}
|
||||
wwlog.Verbose("Profile: %s, Deleting network device: %s", nId, set.NetdevDelete)
|
||||
delete(nodePtr.NetDevs, set.NetdevDelete)
|
||||
}
|
||||
if set.PartitionDelete != "" {
|
||||
for diskname, disk := range nodePtr.Disks {
|
||||
if _, ok := disk.Partitions[set.PartitionDelete]; ok {
|
||||
wwlog.Verbose("Node: %s, on disk %s, deleting partition: %s", nId, diskname, set.PartitionDelete)
|
||||
delete(disk.Partitions, set.PartitionDelete)
|
||||
} else {
|
||||
return nodeDB, count, fmt.Errorf("partition doesn't exist: %s", set.PartitionDelete)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if set.DiskDelete != "" {
|
||||
if _, ok := nodePtr.Disks[set.DiskDelete]; ok {
|
||||
wwlog.Verbose("Node: %s, deleting disk: %s", nId, set.DiskDelete)
|
||||
delete(nodePtr.Disks, set.DiskDelete)
|
||||
} else {
|
||||
return nodeDB, count, fmt.Errorf("disk doesn't exist: %s", set.DiskDelete)
|
||||
}
|
||||
}
|
||||
if set.FilesystemDelete != "" {
|
||||
if _, ok := nodePtr.FileSystems[set.FilesystemDelete]; ok {
|
||||
wwlog.Verbose("Node: %s, deleting filesystem: %s", nId, set.FilesystemDelete)
|
||||
delete(nodePtr.FileSystems, set.FilesystemDelete)
|
||||
} else {
|
||||
return nodeDB, count, fmt.Errorf("disk doesn't exist: %s", set.FilesystemDelete)
|
||||
}
|
||||
}
|
||||
for _, key := range set.TagDel {
|
||||
delete(nodePtr.Tags, key)
|
||||
}
|
||||
for key, val := range set.TagAdd {
|
||||
if nodePtr.Tags == nil {
|
||||
nodePtr.Tags = make(map[string]string)
|
||||
}
|
||||
nodePtr.Tags[key] = val
|
||||
}
|
||||
for key, val := range set.IpmiTagAdd {
|
||||
if nodePtr.Ipmi.Tags == nil {
|
||||
nodePtr.Ipmi.Tags = make(map[string]string)
|
||||
}
|
||||
nodePtr.Ipmi.Tags[key] = val
|
||||
}
|
||||
for _, key := range set.IpmiTagDel {
|
||||
delete(nodePtr.Ipmi.Tags, key)
|
||||
}
|
||||
if _, ok := nodePtr.NetDevs[set.Netdev]; ok {
|
||||
for _, key := range set.NetTagDel {
|
||||
delete(nodePtr.NetDevs[set.Netdev].Tags, key)
|
||||
}
|
||||
for key, val := range set.NetTagAdd {
|
||||
nodePtr.NetDevs[set.Netdev].Tags[key] = val
|
||||
}
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package apinode
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/warewulf/warewulf/internal/pkg/hostlist"
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
// NodeStatus returns the imaging state for nodes.
|
||||
// This requires warewulfd.
|
||||
func NodeStatus(nodeNames []string) (nodeStatusResponse *wwapiv1.NodeStatusResponse, err error) {
|
||||
|
||||
// Local structs for translating json from warewulfd.
|
||||
type nodeStatusInternal struct {
|
||||
NodeName string `json:"node name"`
|
||||
Stage string `json:"stage"`
|
||||
Sent string `json:"sent"`
|
||||
Ipaddr string `json:"ipaddr"`
|
||||
Lastseen int64 `json:"last seen"`
|
||||
}
|
||||
|
||||
// all status is a map with one key (nodes)
|
||||
// and maps of [nodeName]NodeStatus underneath.
|
||||
type allStatus struct {
|
||||
Nodes map[string]*nodeStatusInternal `json:"nodes"`
|
||||
}
|
||||
|
||||
controller := warewulfconf.Get()
|
||||
|
||||
if controller.Ipaddr == "" {
|
||||
err = fmt.Errorf("the Warewulf Server IP Address is not properly configured")
|
||||
wwlog.Error(fmt.Sprintf("%v", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
statusURL := fmt.Sprintf("http://%s:%d/status", controller.Ipaddr, controller.Warewulf.Port)
|
||||
wwlog.Verbose("Connecting to: %s", statusURL)
|
||||
|
||||
resp, err := http.Get(statusURL)
|
||||
if err != nil {
|
||||
wwlog.Error("Could not connect to Warewulf server: %s", err)
|
||||
return
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
var wwNodeStatus allStatus
|
||||
|
||||
err = decoder.Decode(&wwNodeStatus)
|
||||
if err != nil {
|
||||
wwlog.Error("Could not decode JSON: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Translate struct and filter.
|
||||
nodeStatusResponse = &wwapiv1.NodeStatusResponse{}
|
||||
|
||||
if len(nodeNames) == 0 {
|
||||
for _, v := range wwNodeStatus.Nodes {
|
||||
nodeStatusResponse.NodeStatus = append(nodeStatusResponse.NodeStatus,
|
||||
&wwapiv1.NodeStatus{
|
||||
NodeName: v.NodeName,
|
||||
Stage: v.Stage,
|
||||
Sent: v.Sent,
|
||||
Ipaddr: v.Ipaddr,
|
||||
Lastseen: v.Lastseen,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
nodeList := hostlist.Expand(nodeNames)
|
||||
for _, v := range wwNodeStatus.Nodes {
|
||||
for j := 0; j < len(nodeList); j++ {
|
||||
if v.NodeName == nodeList[j] {
|
||||
nodeStatusResponse.NodeStatus = append(nodeStatusResponse.NodeStatus,
|
||||
&wwapiv1.NodeStatus{
|
||||
NodeName: v.NodeName,
|
||||
Stage: v.Stage,
|
||||
Sent: v.Sent,
|
||||
Ipaddr: v.Ipaddr,
|
||||
Lastseen: v.Lastseen,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
88
internal/pkg/node/update.go
Normal file
88
internal/pkg/node/update.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// UpdateFrom copies fields from src to dst, but only those fields whose
|
||||
// corresponding cobra flag (identified by the "lopt" struct tag) reports
|
||||
// as changed. This replaces the old pattern of marshaling to YAML (to strip
|
||||
// zero values via omitempty) and then merging with mergo.
|
||||
//
|
||||
// The changed function should typically be cmd.Flags().Changed.
|
||||
func (dst *Node) UpdateFrom(src *Node, changed func(string) bool) {
|
||||
recursiveUpdateFrom(reflect.ValueOf(dst).Elem(), reflect.ValueOf(src).Elem(), changed)
|
||||
}
|
||||
|
||||
// UpdateFrom copies fields from src to dst for profiles.
|
||||
func (dst *Profile) UpdateFrom(src *Profile, changed func(string) bool) {
|
||||
recursiveUpdateFrom(reflect.ValueOf(dst).Elem(), reflect.ValueOf(src).Elem(), changed)
|
||||
}
|
||||
|
||||
func recursiveUpdateFrom(dst, src reflect.Value, changed func(string) bool) {
|
||||
srcType := src.Type()
|
||||
|
||||
for i := 0; i < src.NumField(); i++ {
|
||||
field := srcType.Field(i)
|
||||
srcField := src.Field(i)
|
||||
dstField := dst.Field(i)
|
||||
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
if lopt := field.Tag.Get("lopt"); lopt != "" && field.Tag.Get("comment") != "" {
|
||||
// Leaf field with a cobra flag — copy if the flag was changed
|
||||
if changed(lopt) {
|
||||
dstField.Set(srcField)
|
||||
}
|
||||
} else if field.Anonymous {
|
||||
// Embedded struct (e.g., Profile in Node)
|
||||
recursiveUpdateFrom(dstField, srcField, changed)
|
||||
} else if field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
|
||||
// Pointer-to-struct (e.g., *IpmiConf, *KernelConf)
|
||||
if srcField.IsNil() {
|
||||
continue
|
||||
}
|
||||
if dstField.IsNil() {
|
||||
dstField.Set(reflect.New(field.Type.Elem()))
|
||||
}
|
||||
recursiveUpdateFrom(dstField.Elem(), srcField.Elem(), changed)
|
||||
} else if field.Type.Kind() == reflect.Struct {
|
||||
// Direct struct
|
||||
recursiveUpdateFrom(dstField, srcField, changed)
|
||||
} else if field.Type.Kind() == reflect.Map {
|
||||
// Map fields (e.g., NetDevs, Disks, FileSystems)
|
||||
switch field.Type.Elem().Kind() {
|
||||
case reflect.String, reflect.Interface:
|
||||
// Tags map[string]string — handled separately by tag add/del operations
|
||||
continue
|
||||
case reflect.Pointer:
|
||||
// map[string]*Struct — update entries from src into dst
|
||||
if srcField.IsNil() {
|
||||
continue
|
||||
}
|
||||
if dstField.IsNil() {
|
||||
dstField.Set(reflect.MakeMap(field.Type))
|
||||
}
|
||||
for _, key := range srcField.MapKeys() {
|
||||
srcEntry := srcField.MapIndex(key)
|
||||
if srcEntry.IsNil() {
|
||||
continue
|
||||
}
|
||||
dstEntry := dstField.MapIndex(key)
|
||||
if !dstEntry.IsValid() || dstEntry.IsNil() {
|
||||
// Create new entry in dst
|
||||
dstEntry = reflect.New(field.Type.Elem().Elem())
|
||||
dstField.SetMapIndex(key, dstEntry)
|
||||
}
|
||||
// Map values are not addressable, so we need to work with a copy
|
||||
tmpDst := reflect.New(field.Type.Elem().Elem()).Elem()
|
||||
tmpDst.Set(dstEntry.Elem())
|
||||
recursiveUpdateFrom(tmpDst, srcEntry.Elem(), changed)
|
||||
dstField.SetMapIndex(key, tmpDst.Addr())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user