diff --git a/cmd/machine.go b/cmd/machine.go index 01ac8dd..25a8fa6 100644 --- a/cmd/machine.go +++ b/cmd/machine.go @@ -356,6 +356,15 @@ In case the machine did not register properly a direct ipmi console access is av }, ValidArgsFunction: c.comp.MachineListCompletion, } + machineIpmiChassisCmd := &cobra.Command{ + Use: "chassis-list", + Short: `display ipmi machines grouped by chassis serial`, + Long: `display ipmi machines grouped by chassis serial.` + "\n" + api.EmojiHelpText(), + RunE: func(cmd *cobra.Command, args []string) error { + return w.machineIpmiChassis(args) + }, + ValidArgsFunction: c.comp.MachineListCompletion, + } machineIssuesCmd := &cobra.Command{ Use: "issues []", Short: `display machines which are in a potential bad state`, @@ -395,6 +404,10 @@ In case the machine did not register properly a direct ipmi console access is av w.listCmdFlags(machineIpmiCmd, 1*time.Hour) genericcli.AddSortFlag(machineIpmiCmd, sorters.MachineIPMISorter()) + w.listCmdFlags(machineIpmiChassisCmd, 1*time.Hour) + genericcli.AddSortFlag(machineIpmiChassisCmd, sorters.MachineIPMISorter()) + machineIpmiCmd.AddCommand(machineIpmiChassisCmd) + w.listCmdFlags(machineIssuesCmd, 0) genericcli.AddSortFlag(machineIssuesCmd, sorters.MachineIPMISorter()) @@ -1327,6 +1340,65 @@ func (c *machineCmd) machineIpmi(args []string) error { return c.listPrinter.Print(resp.Payload) } +func (c *machineCmd) machineIpmiChassis(args []string) error { + var machines []*models.V1MachineIPMIResponse + + if len(args) > 0 { + id, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return err + } + + resp, err := c.client.Machine().FindIPMIMachine(machine.NewFindIPMIMachineParams().WithID(id), nil) + if err != nil { + return err + } + + machines = pointer.WrapInSlice(resp.Payload) + } else { + resp, err := c.client.Machine().FindIPMIMachines(machine.NewFindIPMIMachinesParams().WithBody(machineFindRequestFromCLI()), nil) + if err != nil { + return err + } + + machines = append(machines, resp.Payload...) + } + + sortKeys, err := genericcli.ParseSortFlags() + if err != nil { + return err + } + + err = sorters.MachineIPMISorter().SortBy(machines, sortKeys...) + if err != nil { + return err + } + + byChassis := tableprinters.MachineIpmiChassisTable{} + for _, m := range machines { + var ( + serial = pointer.SafeDeref(pointer.SafeDeref(m.Ipmi).Fru).ChassisPartSerial + number = pointer.SafeDeref(pointer.SafeDeref(m.Ipmi).Fru).ChassisPartNumber + + idx = slices.IndexFunc(byChassis, func(c *tableprinters.MachineIpmiChassis) bool { + return serial == c.ChassisPartSerial + }) + ) + + if idx < 0 { + byChassis = append(byChassis, &tableprinters.MachineIpmiChassis{ + ChassisPartNumber: number, + ChassisPartSerial: serial, + Machines: []*models.V1MachineIPMIResponse{m}, + }) + } else { + byChassis[idx].Machines = append(byChassis[idx].Machines, m) + } + } + + return c.listPrinter.Print(byChassis) +} + func (c *machineCmd) machineIssuesList() error { issuesResp, err := c.client.Machine().ListIssues(machine.NewListIssuesParams(), nil) if err != nil { diff --git a/cmd/tableprinters/machine.go b/cmd/tableprinters/machine.go index 5bf8f5d..0b0585b 100644 --- a/cmd/tableprinters/machine.go +++ b/cmd/tableprinters/machine.go @@ -409,3 +409,72 @@ func (t *TablePrinter) MachineIssuesTable(data *MachinesAndIssues, wide bool) ([ return header, rows, nil } + +type MachineIpmiChassisTable []*MachineIpmiChassis + +type MachineIpmiChassis struct { + ChassisPartNumber string `json:"chassis_part_number,omitempty" yaml:"chassis_part_number,omitempty"` + ChassisPartSerial string `json:"chassis_part_serial,omitempty" yaml:"chassis_part_serial,omitempty"` + Machines []*models.V1MachineIPMIResponse `json:"machines" yaml:"machines"` +} + +func (t *TablePrinter) MachineIpmiChassisTable(data MachineIpmiChassisTable, wide bool) ([]string, [][]string, error) { + var ( + rows [][]string + ) + + header := []string{"ID", "", "Partition", "Rack", "Size", "Product Serial", "Hostname"} + // if wide { + // no particular wide view yet + // } + + for _, chassis := range data { + chassisID := chassis.ChassisPartSerial + if chassisID == "" { + chassisID = "" + } + if chassis.ChassisPartNumber != "" { + chassisID = fmt.Sprintf("%s (%s)", chassisID, chassis.ChassisPartNumber) + } + + rows = append(rows, []string{chassisID}) + + for i, m := range chassis.Machines { + var ( + id = pointer.SafeDeref(m.ID) + partition = pointer.SafeDeref(pointer.SafeDeref(m.Partition).ID) + size = pointer.SafeDeref(pointer.SafeDeref(m.Size).ID) + ps = "" + ipmi = m.Ipmi + rack = m.Rackid + hostname = pointer.SafeDeref(pointer.SafeDeref(m.Allocation).Hostname) + ) + + if ipmi != nil { + fru := ipmi.Fru + + if fru != nil { + ps = fru.ProductSerial + } + } + + emojis, wideEmojis := t.getMachineStatusEmojis(m.Liveliness, m.Events, m.State, nil) + + prefix := "├" + if i == len(chassis.Machines)-1 { + prefix = "└" + } + prefix += "─╴" + + id = fmt.Sprintf("%s %s", prefix, id) + + if wide { + rows = append(rows, []string{id, wideEmojis, partition, rack, size, ps, hostname}) + } else { + rows = append(rows, []string{id, emojis, partition, rack, size, ps, hostname}) + } + } + } + + return header, rows, nil +} diff --git a/cmd/tableprinters/printer.go b/cmd/tableprinters/printer.go index 8d3ef5b..8bd0eb6 100644 --- a/cmd/tableprinters/printer.go +++ b/cmd/tableprinters/printer.go @@ -90,6 +90,8 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin return t.MachineIPMITable(d, wide) case *models.V1MachineIPMIResponse: return t.MachineIPMITable(pointer.WrapInSlice(d), wide) + case MachineIpmiChassisTable: + return t.MachineIpmiChassisTable(d, wide) case []*models.V1MachineProvisioningEvent: return t.MachineLogsTable(d, wide) case *models.V1MachineProvisioningEvent: diff --git a/docs/metalctl_machine_ipmi.md b/docs/metalctl_machine_ipmi.md index d8d176a..4184e49 100644 --- a/docs/metalctl_machine_ipmi.md +++ b/docs/metalctl_machine_ipmi.md @@ -82,5 +82,6 @@ metalctl machine ipmi [] [flags] ### SEE ALSO * [metalctl machine](metalctl_machine.md) - manage machine entities +* [metalctl machine ipmi chassis-list](metalctl_machine_ipmi_chassis-list.md) - display ipmi machines grouped by chassis serial * [metalctl machine ipmi events](metalctl_machine_ipmi_events.md) - display machine hardware events diff --git a/docs/metalctl_machine_ipmi_chassis-list.md b/docs/metalctl_machine_ipmi_chassis-list.md new file mode 100644 index 0000000..a0c09a3 --- /dev/null +++ b/docs/metalctl_machine_ipmi_chassis-list.md @@ -0,0 +1,85 @@ +## metalctl machine ipmi chassis-list + +display ipmi machines grouped by chassis serial + +### Synopsis + +display ipmi machines grouped by chassis serial + +Meaning of the emojis: + +🚧 Machine is reserved. Reserved machines are not considered for random allocation until the reservation flag is removed. +🔒 Machine is locked. Locked machines can not be deleted until the lock is removed. +💀 Machine is dead. The metal-api does not receive any events from this machine. +❗ Machine has a last event error. The machine has recently encountered an error during the provisioning lifecycle. +❓ Machine is in unknown condition. The metal-api does not receive phoned home events anymore or has never booted successfully. +⭕ Machine is in a provisioning crash loop. Flag can be reset through an API-triggered reboot or when the machine reaches the phoned home state. +🚑 Machine reclaim has failed. The machine was deleted but it is not going back into the available machine pool. +🛡 Machine is connected to our VPN, ssh access only possible via this VPN. + + +``` +metalctl machine ipmi chassis-list [flags] +``` + +### Options + +``` + --bmc-address string bmc ipmi address (needs to include port) to filter [optional] + --bmc-mac string bmc mac address to filter [optional] + --board-part-number string fru board part number to filter [optional] + -h, --help help for chassis-list + --hostname string allocation hostname to filter [optional] + --id string ID to filter [optional] + --image string allocation image to filter [optional] + --last-event-error-threshold duration the duration up to how long in the past a machine last event error will be counted as an issue [optional] (default 1h0m0s) + --mac string mac to filter [optional] + --manufacturer string fru manufacturer to filter [optional] + --name string allocation name to filter [optional] + --network-destination-prefixes string network destination prefixes to filter [optional] + --network-ids string network ids to filter [optional] + --network-ips string network ips to filter [optional] + --partition string partition to filter [optional] + --product-part-number string fru product part number to filter [optional] + --product-serial string fru product serial to filter [optional] + --project string allocation project to filter [optional] + --rack string rack to filter [optional] + --role string allocation role to filter [optional] + --size string size to filter [optional] + --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: age|bios|bmc|event|id|liveliness|partition|project|rack|size|when + --state string state to filter [optional] + --tags strings tags to filter, use it like: --tags "tag1,tag2" or --tags "tag3". +``` + +### Options inherited from parent commands + +``` + --api-token string api token to authenticate. Can be specified with METALCTL_API_TOKEN environment variable. + --api-url string api server address. Can be specified with METALCTL_API_URL environment variable. + -c, --config string alternative config file path, (default is ~/.metalctl/config.yaml). + Example config.yaml: + + --- + apitoken: "alongtoken" + ... + + + --debug debug output + --force-color force colored output even without tty + --kubeconfig string Path to the kube-config to use for authentication and authorization. Is updated by login. Uses default path if not specified. + --no-headers do not print headers of table output format (default print headers) + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. + For property names inspect the output of -o json or -o yaml for reference. + Example for machines: + + metalctl machine list -o template --template "{{ .id }}:{{ .size.id }}" + + + --yes-i-really-mean-it skips security prompts (which can be dangerous to set blindly because actions can lead to data loss or additional costs) +``` + +### SEE ALSO + +* [metalctl machine ipmi](metalctl_machine_ipmi.md) - display ipmi details of the machine, if no machine ID is given all ipmi addresses are returned. +