Files
warewulf/internal/app/wwctl/node/status/main.go
Jonathon Anderson ec6884a878 Refactor gRPC-based node functions
Signed-off-by: Jonathon Anderson <janderson@ciq.com>
2026-03-23 06:54:41 -06:00

176 lines
4.1 KiB
Go

package nodestatus
import (
"encoding/json"
"fmt"
"net/http"
"sort"
"strings"
"time"
"github.com/fatih/color"
"github.com/spf13/cobra"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/hostlist"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
"golang.org/x/term"
)
type nodeStatus struct {
NodeName string `json:"node name"`
Stage string `json:"stage"`
Sent string `json:"sent"`
Ipaddr string `json:"ipaddr"`
Lastseen int64 `json:"last seen"`
}
func displayStage(stage string) string {
switch stage {
case "efiboot":
return "EFI"
case "grub":
return "GRUB"
case "ipxe":
return "IPXE"
case "kernel":
return "KERNEL"
case "image":
return "IMAGE"
case "system":
return "SYSTEM OVERLAY"
case "runtime":
return "RUNTIME OVERLAY"
case "initramfs":
return "INITRAMFS"
default:
return strings.ToUpper(stage)
}
}
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
controller := warewulfconf.Get()
if controller.Ipaddr == "" {
return fmt.Errorf("warewulf Server IP Address is not properly configured")
}
for {
var elipsis bool
var height int
var count int
rightnow := time.Now().Unix()
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 {
return fmt.Errorf("could not connect to Warewulf server: %w", err)
}
var wwNodeStatus struct {
Nodes map[string]*nodeStatus `json:"nodes"`
}
err = json.NewDecoder(resp.Body).Decode(&wwNodeStatus)
_ = resp.Body.Close()
if err != nil {
return fmt.Errorf("could not decode JSON: %w", err)
}
if SetWatch {
fmt.Print("\033[H\033[2J")
_, height, err = term.GetSize(0)
if err != nil {
wwlog.Warn("Could not get terminal height, using 24")
height = 24
}
}
fmt.Printf("%-20s %-20s %-25s %-10s\n", "NODENAME", "STAGE", "SENT", "LASTSEEN (s)")
fmt.Printf("%s\n", strings.Repeat("=", 80))
wwlog.Verbose("Building sort index")
var statuses []*nodeStatus
if len(args) > 0 {
nodeList := hostlist.Expand(args)
for _, v := range wwNodeStatus.Nodes {
for _, name := range nodeList {
if v.NodeName == name {
statuses = append(statuses, v)
break
}
}
}
} else {
for _, v := range wwNodeStatus.Nodes {
statuses = append(statuses, v)
}
}
wwlog.Verbose("Sorting index")
if SetSortLast {
sort.Slice(statuses, func(i, j int) bool {
if statuses[i].Lastseen > statuses[j].Lastseen {
return true
} else if statuses[i].Lastseen < statuses[j].Lastseen {
return false
} else {
return statuses[i].NodeName < statuses[j].NodeName
}
})
} else if SetSortReverse {
wwlog.Verbose("Reversing sort order")
sort.Slice(statuses, func(i, j int) bool {
return statuses[i].NodeName > statuses[j].NodeName
})
} else {
sort.Slice(statuses, func(i, j int) bool {
return statuses[i].NodeName < statuses[j].NodeName
})
}
wwlog.Verbose("Printing results")
for i := 0; i < len(statuses); i++ {
o := statuses[i]
if SetTime > 0 && o.Lastseen < SetTime {
continue
}
if o.Lastseen > 0 {
if SetUnknown {
continue
}
if rightnow-o.Lastseen >= int64(controller.Warewulf.UpdateInterval*2) {
color.Red("%-20s %-20s %-25s %-10d\n", o.NodeName, displayStage(o.Stage), o.Sent, rightnow-o.Lastseen)
} else if rightnow-o.Lastseen >= int64(controller.Warewulf.UpdateInterval+5) {
color.Yellow("%-20s %-20s %-25s %-10d\n", o.NodeName, displayStage(o.Stage), o.Sent, rightnow-o.Lastseen)
} else {
fmt.Printf("%-20s %-20s %-25s %-10d\n", o.NodeName, displayStage(o.Stage), o.Sent, rightnow-o.Lastseen)
}
} else {
color.HiBlack("%-20s %-20s %-25s %-10s\n", o.NodeName, "--", "--", "--")
}
if count+4 >= height && SetWatch {
if count+1 != len(statuses) {
elipsis = true
}
break
}
count++
}
if SetWatch {
if elipsis {
fmt.Printf("... ")
}
time.Sleep(time.Duration(SetUpdate) * time.Millisecond)
} else {
break
}
}
return
}