diff --git a/cmd/completion/machine.go b/cmd/completion/machine.go index 24a24240..6f8b53f9 100644 --- a/cmd/completion/machine.go +++ b/cmd/completion/machine.go @@ -2,6 +2,8 @@ package completion import ( "github.com/metal-stack/metal-go/api/client/machine" + "github.com/metal-stack/metal-go/api/models" + "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/spf13/cobra" ) @@ -20,3 +22,116 @@ func (c *Completion) MachineListCompletion(cmd *cobra.Command, args []string, to } return names, cobra.ShellCompDirectiveNoFileComp } + +func (c *Completion) MachineManufacturerCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.client.Machine().FindIPMIMachines(machine.NewFindIPMIMachinesParams().WithBody(&models.V1MachineFindRequest{}), nil) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, m := range resp.Payload { + if m == nil || m.Ipmi == nil || m.Ipmi.Fru == nil { + continue + } + + names = append(names, m.Ipmi.Fru.ProductManufacturer) + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) MachineProductPartNumberCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.client.Machine().FindIPMIMachines(machine.NewFindIPMIMachinesParams().WithBody(&models.V1MachineFindRequest{}), nil) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, m := range resp.Payload { + if m == nil || m.Ipmi == nil || m.Ipmi.Fru == nil { + continue + } + + names = append(names, m.Ipmi.Fru.ProductPartNumber) + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) MachineProductSerialCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.client.Machine().FindIPMIMachines(machine.NewFindIPMIMachinesParams().WithBody(&models.V1MachineFindRequest{}), nil) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, m := range resp.Payload { + if m == nil || m.Ipmi == nil || m.Ipmi.Fru == nil { + continue + } + + names = append(names, m.Ipmi.Fru.ProductSerial) + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) MachineBoardPartNumberCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.client.Machine().FindIPMIMachines(machine.NewFindIPMIMachinesParams().WithBody(&models.V1MachineFindRequest{}), nil) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, m := range resp.Payload { + if m == nil || m.Ipmi == nil || m.Ipmi.Fru == nil { + continue + } + + names = append(names, m.Ipmi.Fru.BoardPartNumber) + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) IssueTypeCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.client.Machine().ListIssues(machine.NewListIssuesParams(), nil) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var names []string + for _, issue := range resp.Payload { + issue := issue + + if issue.ID == nil { + continue + } + + name := *issue.ID + description := pointer.SafeDeref(issue.Description) + if description != "" { + name = name + "\t" + description + } + + names = append(names, name) + } + return names, cobra.ShellCompDirectiveNoFileComp +} + +func (c *Completion) IssueSeverityCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.client.Machine().ListIssues(machine.NewListIssuesParams(), nil) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + + severities := map[string]bool{} + for _, issue := range resp.Payload { + issue := issue + + if issue.Severity == nil { + continue + } + + severities[*issue.Severity] = true + } + + var names []string + for s := range severities { + names = append(names, s) + } + + return names, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/completion/network.go b/cmd/completion/network.go index 05ef26e6..8c0d1c50 100644 --- a/cmd/completion/network.go +++ b/cmd/completion/network.go @@ -16,3 +16,17 @@ func (c *Completion) NetworkListCompletion(cmd *cobra.Command, args []string, to } return names, cobra.ShellCompDirectiveNoFileComp } + +func (c *Completion) NetworkDestinationPrefixesCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + resp, err := c.client.Network().ListNetworks(network.NewListNetworksParams(), nil) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var prefixes []string + for _, n := range resp.Payload { + for _, prefix := range n.Destinationprefixes { + prefixes = append(prefixes, prefix) + } + } + return prefixes, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/firewall.go b/cmd/firewall.go index 3b267564..531c775c 100644 --- a/cmd/firewall.go +++ b/cmd/firewall.go @@ -53,7 +53,7 @@ func newFirewallCmd(c *config) *cobra.Command { cmd.Flags().String("project", "", "allocation project to filter [optional]") cmd.Flags().String("image", "", "allocation image to filter [optional]") cmd.Flags().String("hostname", "", "allocation hostname to filter [optional]") - cmd.Flags().StringSlice("mac", []string{}, "mac to filter [optional]") + cmd.Flags().String("mac", "", "mac to filter [optional]") cmd.Flags().StringSlice("tags", []string{}, "tags to filter, use it like: --tags \"tag1,tag2\" or --tags \"tag3\".") must(cmd.RegisterFlagCompletionFunc("partition", c.comp.PartitionListCompletion)) must(cmd.RegisterFlagCompletionFunc("size", c.comp.SizeListCompletion)) @@ -86,6 +86,11 @@ func (c firewallCmd) Get(id string) (*models.V1FirewallResponse, error) { } func (c firewallCmd) List() ([]*models.V1FirewallResponse, error) { + var macs []string + if viper.IsSet("mac") { + macs = pointer.WrapInSlice(viper.GetString("mac")) + } + resp, err := c.client.Firewall().FindFirewalls(firewall.NewFindFirewallsParams().WithBody(&models.V1FirewallFindRequest{ ID: viper.GetString("id"), PartitionID: viper.GetString("partition"), @@ -94,7 +99,7 @@ func (c firewallCmd) List() ([]*models.V1FirewallResponse, error) { AllocationProject: viper.GetString("project"), AllocationImageID: viper.GetString("image"), AllocationHostname: viper.GetString("hostname"), - NicsMacAddresses: viper.GetStringSlice("mac"), + NicsMacAddresses: macs, Tags: viper.GetStringSlice("tags"), }), nil) if err != nil { diff --git a/cmd/firewall_test.go b/cmd/firewall_test.go index b23f48ba..9d79a1f3 100644 --- a/cmd/firewall_test.go +++ b/cmd/firewall_test.go @@ -197,7 +197,7 @@ func Test_FirewallCmd_MultiResult(t *testing.T) { mocks: &client.MetalMockFns{ Firewall: func(mock *mock.Mock) { mock.On("FindFirewalls", testcommon.MatchIgnoreContext(t, firewall.NewFindFirewallsParams().WithBody(&models.V1FirewallFindRequest{ - NicsMacAddresses: []string{}, + NicsMacAddresses: nil, Tags: []string{}, })), nil).Return(&firewall.FindFirewallsOK{ Payload: []*models.V1FirewallResponse{ diff --git a/cmd/machine.go b/cmd/machine.go index c906e2d6..4d74085e 100644 --- a/cmd/machine.go +++ b/cmd/machine.go @@ -21,6 +21,7 @@ import ( "github.com/metal-stack/metal-lib/pkg/genericcli/printers" "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/metal-stack/metalctl/cmd/sorters" + "github.com/metal-stack/metalctl/cmd/tableprinters" "github.com/metal-stack/metalctl/pkg/api" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -52,6 +53,16 @@ func (c *machineCmd) listCmdFlags(cmd *cobra.Command, lastEventErrorThresholdDef models.V1FirewallFindRequestStateValueLOCKED, models.V1MachineFindRequestStateValueRESERVED, }, cobra.ShellCompDirectiveDefault)}, + {flagName: "role", f: cobra.FixedCompletions([]string{ + models.V1MachineAllocationRoleFirewall, + models.V1MachineAllocationRoleMachine, + }, cobra.ShellCompDirectiveDefault)}, + {flagName: "manufacturer", f: c.comp.MachineManufacturerCompletion}, + {flagName: "product-part-number", f: c.comp.MachineProductPartNumberCompletion}, + {flagName: "product-serial", f: c.comp.MachineProductSerialCompletion}, + {flagName: "board-part-number", f: c.comp.MachineBoardPartNumberCompletion}, + {flagName: "network-destination-prefixes", f: c.comp.NetworkDestinationPrefixesCompletion}, + {flagName: "network-ids", f: c.comp.NetworkListCompletion}, } cmd.Flags().String("id", "", "ID to filter [optional]") @@ -66,6 +77,16 @@ func (c *machineCmd) listCmdFlags(cmd *cobra.Command, lastEventErrorThresholdDef cmd.Flags().String("mac", "", "mac to filter [optional]") cmd.Flags().StringSlice("tags", []string{}, "tags to filter, use it like: --tags \"tag1,tag2\" or --tags \"tag3\".") cmd.Flags().Duration("last-event-error-threshold", lastEventErrorThresholdDefault, "the duration up to how long in the past a machine last event error will be counted as an issue [optional]") + cmd.Flags().String("role", "", "allocation role to filter [optional]") + cmd.Flags().String("board-part-number", "", "fru board part number to filter [optional]") + cmd.Flags().String("manufacturer", "", "fru manufacturer to filter [optional]") + cmd.Flags().String("product-part-number", "", "fru product part number to filter [optional]") + cmd.Flags().String("product-serial", "", "fru product serial to filter [optional]") + cmd.Flags().String("bmc-address", "", "bmc ipmi address (needs to include port) to filter [optional]") + cmd.Flags().String("bmc-mac", "", "bmc mac address to filter [optional]") + cmd.Flags().String("network-destination-prefixes", "", "network destination prefixes to filter [optional]") + cmd.Flags().String("network-ids", "", "network ids to filter [optional]") + cmd.Flags().String("network-ips", "", "network ips to filter [optional]") for _, c := range listFlagCompletions { c := c @@ -340,10 +361,18 @@ In case the machine did not register properly a direct ipmi console access is av Short: `display machines which are in a potential bad state`, Long: `display machines which are in a potential bad state` + "\n" + api.EmojiHelpText(), RunE: func(cmd *cobra.Command, args []string) error { - return w.machineIssues(args) + return w.machineIssuesEvaluate(args) }, ValidArgsFunction: c.comp.MachineListCompletion, } + machineIssuesListCmd := &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: `list all machine issues that the metal-api can evaluate`, + RunE: func(cmd *cobra.Command, args []string) error { + return w.machineIssuesList() + }, + } machineLogsCmd := &cobra.Command{ Use: "logs ", Aliases: []string{"log"}, @@ -366,35 +395,21 @@ 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(machineIssuesCmd, api.DefaultLastErrorThreshold()) + w.listCmdFlags(machineIssuesCmd, 0) genericcli.AddSortFlag(machineIssuesCmd, sorters.MachineIPMISorter()) - machineIssuesCmd.Flags().StringSlice("only", []string{}, "issue types to include [optional]") - machineIssuesCmd.Flags().StringSlice("omit", []string{}, "issue types to omit [optional]") - machineIssuesCmd.Flags().String("severity", "", "issue severity to include [optional]") + machineIssuesCmd.AddCommand(machineIssuesListCmd) + genericcli.AddSortFlag(machineIssuesListCmd, sorters.MachineIssueSorter()) - var severities []string - for _, s := range api.AllSevereties() { - severities = append(severities, string(s)) - } - must(machineIssuesCmd.RegisterFlagCompletionFunc("severity", cobra.FixedCompletions(severities, cobra.ShellCompDirectiveNoFileComp))) + machineIssuesCmd.Flags().StringSlice("only", nil, "issue types to include [optional]") + machineIssuesCmd.Flags().StringSlice("omit", nil, "issue types to omit [optional]") + machineIssuesCmd.Flags().String("severity", "", "issue severity to include [optional]") - must(machineIssuesCmd.RegisterFlagCompletionFunc("omit", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - var shortNames []string - for _, i := range api.AllIssues() { - shortNames = append(shortNames, string(i.Type)+"\t"+i.Description) - } - return shortNames, cobra.ShellCompDirectiveNoFileComp - })) - must(machineIssuesCmd.RegisterFlagCompletionFunc("only", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - var shortNames []string - for _, i := range api.AllIssues() { - shortNames = append(shortNames, string(i.Type)+"\t"+i.Description) - } - return shortNames, cobra.ShellCompDirectiveNoFileComp - })) + must(machineIssuesCmd.RegisterFlagCompletionFunc("severity", c.comp.IssueSeverityCompletion)) + must(machineIssuesCmd.RegisterFlagCompletionFunc("omit", c.comp.IssueTypeCompletion)) + must(machineIssuesCmd.RegisterFlagCompletionFunc("only", c.comp.IssueTypeCompletion)) - machineLogsCmd.Flags().Duration("last-event-error-threshold", api.DefaultLastErrorThreshold(), "the duration up to how long in the past a machine last event error will be counted as an issue [optional]") + machineLogsCmd.Flags().Duration("last-event-error-threshold", 7*24*time.Hour, "the duration up to how long in the past a machine last event error will be counted as an issue [optional]") machineConsolePasswordCmd.Flags().StringP("reason", "", "", "a short description why access to the consolepassword is required") @@ -538,18 +553,33 @@ func (c machineCmd) List() ([]*models.V1MachineResponse, error) { } func machineFindRequestFromCLI() *models.V1MachineFindRequest { + var macs []string + if viper.IsSet("mac") { + macs = pointer.WrapInSlice(viper.GetString("mac")) + } + return &models.V1MachineFindRequest{ - ID: viper.GetString("id"), - PartitionID: viper.GetString("partition"), - Sizeid: viper.GetString("size"), - Rackid: viper.GetString("rack"), - Name: viper.GetString("name"), - AllocationProject: viper.GetString("project"), - AllocationImageID: viper.GetString("image"), - AllocationHostname: viper.GetString("hostname"), - NicsMacAddresses: viper.GetStringSlice("mac"), - StateValue: viper.GetString("state"), - Tags: viper.GetStringSlice("tags"), + AllocationHostname: viper.GetString("hostname"), + AllocationImageID: viper.GetString("image"), + AllocationProject: viper.GetString("project"), + AllocationRole: viper.GetString("role"), + FruBoardPartNumber: viper.GetString("board-part-number"), + FruProductManufacturer: viper.GetString("manufacturer"), + FruProductPartNumber: viper.GetString("product-part-number"), + FruProductSerial: viper.GetString("product-serial"), + ID: viper.GetString("id"), + IpmiAddress: viper.GetString("bmc-address"), + IpmiMacAddress: viper.GetString("bmc-mac"), + Name: viper.GetString("name"), + NetworkDestinationPrefixes: viper.GetStringSlice("network-destination-prefixes"), + NetworkIds: viper.GetStringSlice("network-ids"), + NetworkIps: viper.GetStringSlice("network-ips"), + NicsMacAddresses: macs, + PartitionID: viper.GetString("partition"), + Rackid: viper.GetString("rack"), + Sizeid: viper.GetString("size"), + StateValue: viper.GetString("state"), + Tags: viper.GetStringSlice("tags"), } } @@ -1270,69 +1300,109 @@ func (c *machineCmd) machineIpmi(args []string) error { return c.listPrinter.Print(resp.Payload) } -func (c *machineCmd) machineIssues(args []string) error { - sortKeys, err := genericcli.ParseSortFlags() +func (c *machineCmd) machineIssuesList() error { + issuesResp, err := c.client.Machine().ListIssues(machine.NewListIssuesParams(), nil) if err != nil { return err } - resp, err := c.client.Machine().FindIPMIMachines(machine.NewFindIPMIMachinesParams().WithBody(machineFindRequestFromCLI()), nil) + sortKeys, err := genericcli.ParseSortFlags() if err != nil { return err } - err = sorters.MachineIPMISorter().SortBy(resp.Payload, sortKeys...) + err = sorters.MachineIssueSorter().SortBy(issuesResp.Payload, sortKeys...) if err != nil { return err } - var ( - severity = api.IssueSeverityMinor - only []api.IssueType - omit []api.IssueType - ) - - for _, o := range viper.GetStringSlice("only") { - only = append(only, api.IssueType(o)) - } - for _, o := range viper.GetStringSlice("omit") { - omit = append(omit, api.IssueType(o)) - } + return c.listPrinter.Print(issuesResp.Payload) +} - if viper.IsSet("severity") { - severity, err = api.SeverityFromString(viper.GetString("severity")) +func (c *machineCmd) machineIssuesEvaluate(args []string) error { + id := viper.GetString("id") + if len(args) > 0 { + var err error + id, err = genericcli.GetExactlyOneArg(args) if err != nil { return err } } - issues, err := api.FindIssues(&api.IssueConfig{ - Machines: resp.Payload, - Severity: severity, - Only: only, - Omit: omit, - LastErrorThreshold: viper.GetDuration("last-event-error-threshold"), - }) + issuesResp, err := c.client.Machine().ListIssues(machine.NewListIssuesParams(), nil) if err != nil { return err } + var macs []string + if viper.IsSet("mac") { + macs = pointer.WrapInSlice(viper.GetString("mac")) + } + + evalResp, err := c.client.Machine().Issues(machine.NewIssuesParams().WithBody(&models.V1MachineIssuesRequest{ + AllocationHostname: viper.GetString("hostname"), + AllocationImageID: viper.GetString("image"), + AllocationProject: viper.GetString("project"), + AllocationRole: viper.GetString("role"), + FruBoardPartNumber: viper.GetString("board-part-number"), + FruProductManufacturer: viper.GetString("manufacturer"), + FruProductPartNumber: viper.GetString("product-part-number"), + FruProductSerial: viper.GetString("product-serial"), + ID: id, + IpmiAddress: viper.GetString("bmc-address"), + IpmiMacAddress: viper.GetString("bmc-mac"), + Name: viper.GetString("name"), + NetworkDestinationPrefixes: viper.GetStringSlice("network-destination-prefixes"), + NetworkIds: viper.GetStringSlice("network-ids"), + NetworkIps: viper.GetStringSlice("network-ips"), + NicsMacAddresses: macs, + PartitionID: viper.GetString("partition"), + Rackid: viper.GetString("rack"), + Sizeid: viper.GetString("size"), + StateValue: viper.GetString("state"), + Tags: viper.GetStringSlice("tags"), + + LastErrorThreshold: pointer.PointerOrNil(int64(viper.GetDuration("last-event-error-threshold"))), + Omit: viper.GetStringSlice("omit"), + Only: viper.GetStringSlice("only"), + Severity: pointer.PointerOrNil(viper.GetString("severity")), + }), nil) + if err != nil { + return err + } + + var machines []*models.V1MachineIPMIResponse if len(args) > 0 { - id, err := genericcli.GetExactlyOneArg(args) + machineResp, err := c.client.Machine().FindIPMIMachine(machine.NewFindIPMIMachineParams().WithID(id), nil) if err != nil { return err } - mWithIssues := issues.Get(id) - if mWithIssues == nil { - fmt.Fprintf(c.out, "machine with id %q has no issues\n", id) - return nil + machines = append(machines, machineResp.Payload) + } else { + machinesResp, err := c.client.Machine().FindIPMIMachines(machine.NewFindIPMIMachinesParams().WithBody(machineFindRequestFromCLI()), nil) + if err != nil { + return err } - return c.describePrinter.Print(mWithIssues.Issues) + machines = machinesResp.Payload } - return c.listPrinter.Print(issues) + sortKeys, err := genericcli.ParseSortFlags() + if err != nil { + return err + } + + err = sorters.MachineIPMISorter().SortBy(machines, sortKeys...) + if err != nil { + return err + } + + return c.listPrinter.Print(&tableprinters.MachinesAndIssues{ + Machines: machines, + Issues: issuesResp.Payload, + EvaluationResult: evalResp.Payload, + }) } func (c *machineCmd) machineIpmiEvents(args []string) error { diff --git a/cmd/machine_test.go b/cmd/machine_test.go index 5a2d23ac..a062c780 100644 --- a/cmd/machine_test.go +++ b/cmd/machine_test.go @@ -12,6 +12,7 @@ import ( "github.com/metal-stack/metal-lib/pkg/net" "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/metal-stack/metal-lib/pkg/testcommon" + "github.com/metal-stack/metalctl/cmd/tableprinters" "github.com/spf13/afero" "github.com/stretchr/testify/mock" @@ -149,6 +150,56 @@ var ( }, Tags: []string{"b"}, } + ipmiMachine1 = &models.V1MachineIPMIResponse{ + Allocation: machine1.Allocation, + Bios: &models.V1MachineBIOS{ + Version: pointer.Pointer("2.0"), + }, + Changed: machine1.Changed, + Created: machine1.Created, + Description: machine1.Description, + Events: machine1.Events, + Hardware: machine1.Hardware, + ID: machine1.ID, + Ipmi: &models.V1MachineIPMI{ + Address: pointer.Pointer("1.2.3.4"), + Bmcversion: pointer.Pointer("1.1"), + Fru: &models.V1MachineFru{ + BoardPartNumber: "part123", + ChassisPartSerial: "chassis123", + ProductSerial: "product123", + }, + LastUpdated: pointer.Pointer(strfmt.DateTime(testTime.Add(-5 * time.Second))), + Mac: pointer.Pointer("1.2.3.4"), + Powermetric: &models.V1PowerMetric{ + Averageconsumedwatts: pointer.Pointer(float32(16.0)), + }, + Powerstate: pointer.Pointer("ON"), + }, + Ledstate: &models.V1ChassisIdentifyLEDState{}, + Liveliness: machine1.Liveliness, + Name: machine1.Name, + Partition: machine1.Partition, + Rackid: machine1.Rackid, + Size: machine1.Size, + State: machine1.State, + Tags: machine1.Tags, + } + + machineIssue1 = &models.V1MachineIssue{ + Description: pointer.Pointer("this is a test issue 1"), + Details: pointer.Pointer("more details 1"), + ID: pointer.Pointer("issue-1-id"), + RefURL: pointer.Pointer("https://url-1"), + Severity: pointer.Pointer("minor"), + } + machineIssue2 = &models.V1MachineIssue{ + Description: pointer.Pointer("this is a test issue 2"), + Details: pointer.Pointer("more details 2"), + ID: pointer.Pointer("issue-2-id"), + RefURL: pointer.Pointer("https://url-2"), + Severity: pointer.Pointer("major"), + } ) func Test_MachineCmd_MultiResult(t *testing.T) { @@ -161,8 +212,11 @@ func Test_MachineCmd_MultiResult(t *testing.T) { mocks: &client.MetalMockFns{ Machine: func(mock *mock.Mock) { mock.On("FindMachines", testcommon.MatchIgnoreContext(t, machine.NewFindMachinesParams().WithBody(&models.V1MachineFindRequest{ - NicsMacAddresses: []string{}, - Tags: []string{}, + NicsMacAddresses: nil, + NetworkDestinationPrefixes: []string{}, + NetworkIps: []string{}, + NetworkIds: []string{}, + Tags: []string{}, })), nil).Return(&machine.FindMachinesOK{ Payload: []*models.V1MachineResponse{ machine1, @@ -379,42 +433,6 @@ ID LAST EVENT WHEN AGE DESCRIPTION NAME HOSTNAME } func Test_MachineIPMICmd_MultiResult(t *testing.T) { - ipmiMachine1 := &models.V1MachineIPMIResponse{ - Allocation: machine1.Allocation, - Bios: &models.V1MachineBIOS{ - Version: pointer.Pointer("2.0"), - }, - Changed: machine1.Changed, - Created: machine1.Created, - Description: machine1.Description, - Events: machine1.Events, - Hardware: machine1.Hardware, - ID: machine1.ID, - Ipmi: &models.V1MachineIPMI{ - Address: pointer.Pointer("1.2.3.4"), - Bmcversion: pointer.Pointer("1.1"), - Fru: &models.V1MachineFru{ - BoardPartNumber: "part123", - ChassisPartSerial: "chassis123", - ProductSerial: "product123", - }, - LastUpdated: pointer.Pointer(strfmt.DateTime(testTime.Add(-5 * time.Second))), - Mac: pointer.Pointer("1.2.3.4"), - Powermetric: &models.V1PowerMetric{ - Averageconsumedwatts: pointer.Pointer(float32(16.0)), - }, - Powerstate: pointer.Pointer("ON"), - }, - Ledstate: &models.V1ChassisIdentifyLEDState{}, - Liveliness: machine1.Liveliness, - Name: machine1.Name, - Partition: machine1.Partition, - Rackid: machine1.Rackid, - Size: machine1.Size, - State: machine1.State, - Tags: machine1.Tags, - } - tests := []*test[[]*models.V1MachineIPMIResponse]{ { name: "machine ipmi", @@ -424,8 +442,11 @@ func Test_MachineIPMICmd_MultiResult(t *testing.T) { mocks: &client.MetalMockFns{ Machine: func(mock *mock.Mock) { mock.On("FindIPMIMachines", testcommon.MatchIgnoreContext(t, machine.NewFindIPMIMachinesParams().WithBody(&models.V1MachineFindRequest{ - NicsMacAddresses: []string{}, - Tags: []string{}, + NicsMacAddresses: nil, + NetworkDestinationPrefixes: []string{}, + NetworkIps: []string{}, + NetworkIds: []string{}, + Tags: []string{}, })), nil).Return(&machine.FindIPMIMachinesOK{ Payload: []*models.V1MachineIPMIResponse{ ipmiMachine1, @@ -438,7 +459,7 @@ func Test_MachineIPMICmd_MultiResult(t *testing.T) { }, wantTable: pointer.Pointer(` ID POWER IP MAC BOARD PART NUMBER BIOS BMC SIZE PARTITION RACK UPDATED -1 ●  (16.0W) 1.2.3.4 1.2.3.4 part123 2.0 1.1 1 1 rack-1 5s ago +1 ● (16.0W) 1.2.3.4 1.2.3.4 part123 2.0 1.1 1 1 rack-1 5s ago `), wantWideTable: pointer.Pointer(` ID STATUS POWER IP MAC BOARD PART NUMBER CHASSIS SERIAL PRODUCT SERIAL BIOS VERSION BMC VERSION SIZE PARTITION RACK UPDATED @@ -451,7 +472,135 @@ ID STATUS POWER IP MAC BOARD PART NUMBER CHASSIS SERIAL wantMarkdown: pointer.Pointer(` | ID | | POWER | IP | MAC | BOARD PART NUMBER | BIOS | BMC | SIZE | PARTITION | RACK | UPDATED | |----|--|------------|---------|---------|-------------------|------|-----|------|-----------|--------|---------| -| 1 | | ●  (16.0W) | 1.2.3.4 | 1.2.3.4 | part123 | 2.0 | 1.1 | 1 | 1 | rack-1 | 5s ago | +| 1 | | ● (16.0W) | 1.2.3.4 | 1.2.3.4 | part123 | 2.0 | 1.1 | 1 | 1 | rack-1 | 5s ago | +`), + }, + } + for _, tt := range tests { + tt.testCmd(t) + } +} + +func Test_MachineIssuesListCmd_MultiResult(t *testing.T) { + tests := []*test[[]*models.V1MachineIssue]{ + { + name: "issues list", + cmd: func(want []*models.V1MachineIssue) []string { + return []string{"machine", "issues", "list"} + }, + mocks: &client.MetalMockFns{ + Machine: func(mock *mock.Mock) { + mock.On("ListIssues", testcommon.MatchIgnoreContext(t, machine.NewListIssuesParams()), nil).Return(&machine.ListIssuesOK{ + Payload: []*models.V1MachineIssue{ + machineIssue1, + machineIssue2, + }, + }, nil) + }, + }, + want: []*models.V1MachineIssue{ + machineIssue2, + machineIssue1, + }, + wantTable: pointer.Pointer(` +ID SEVERITY DESCRIPTION REFERENCE URL +issue-2-id major this is a test issue 2 https://url-2 +issue-1-id minor this is a test issue 1 https://url-1 +`), + wantWideTable: pointer.Pointer(` +ID SEVERITY DESCRIPTION REFERENCE URL +issue-2-id major this is a test issue 2 https://url-2 +issue-1-id minor this is a test issue 1 https://url-1 +`), + template: pointer.Pointer("{{ .id }}"), + wantTemplate: pointer.Pointer(` + issue-2-id +issue-1-id +`), + wantMarkdown: pointer.Pointer(` +| ID | SEVERITY | DESCRIPTION | REFERENCE URL | +|------------|----------|------------------------|---------------| +| issue-2-id | major | this is a test issue 2 | https://url-2 | +| issue-1-id | minor | this is a test issue 1 | https://url-1 | +`), + }, + } + for _, tt := range tests { + tt.testCmd(t) + } +} + +func Test_MachineIssuesCmd(t *testing.T) { + machineWithIssues := &tableprinters.MachinesAndIssues{ + EvaluationResult: []*models.V1MachineIssueResponse{ + { + Machineid: machine1.ID, + Issues: []string{ + pointer.SafeDeref(machineIssue1.ID), + pointer.SafeDeref(machineIssue2.ID), + }, + }, + }, + Issues: []*models.V1MachineIssue{ + machineIssue1, + machineIssue2, + }, + Machines: []*models.V1MachineIPMIResponse{ + ipmiMachine1, + }, + } + + tests := []*test[*tableprinters.MachinesAndIssues]{ + { + name: "issues", + cmd: func(want *tableprinters.MachinesAndIssues) []string { + return []string{"machine", "issues"} + }, + mocks: &client.MetalMockFns{ + Machine: func(mock *mock.Mock) { + mock.On("Issues", testcommon.MatchIgnoreContext(t, machine.NewIssuesParams().WithBody(&models.V1MachineIssuesRequest{ + Omit: []string{}, + Only: []string{}, + + NicsMacAddresses: nil, + NetworkDestinationPrefixes: []string{}, + NetworkIps: []string{}, + NetworkIds: []string{}, + Tags: []string{}, + })), nil).Return(&machine.IssuesOK{ + Payload: machineWithIssues.EvaluationResult, + }, nil) + mock.On("ListIssues", testcommon.MatchIgnoreContext(t, machine.NewListIssuesParams()), nil).Return(&machine.ListIssuesOK{ + Payload: machineWithIssues.Issues, + }, nil) + mock.On("FindIPMIMachines", testcommon.MatchIgnoreContext(t, machine.NewFindIPMIMachinesParams().WithBody(&models.V1MachineFindRequest{ + NicsMacAddresses: nil, + NetworkDestinationPrefixes: []string{}, + NetworkIps: []string{}, + NetworkIds: []string{}, + Tags: []string{}, + })), nil).Return(&machine.FindIPMIMachinesOK{ + Payload: machineWithIssues.Machines, + }, nil) + }, + }, + want: machineWithIssues, + wantTable: pointer.Pointer(` +ID POWER ALLOCATED LOCK REASON LAST EVENT WHEN ISSUES +1 ●  (16.0W) yes state Phoned Home 7d this is a test issue 1 (issue-1-id) + this is a test issue 2 (issue-2-id) +`), + wantWideTable: pointer.Pointer(` +ID NAME PARTITION PROJECT POWER STATE LOCK REASON LAST EVENT WHEN ISSUES REF URL DETAILS +1 machine-1 1 project-1 ON 16.00W state Phoned Home 7d this is a test issue 1 (issue-1-id) https://url-1 more details 1 + this is a test issue 2 (issue-2-id) https://url-2 more details 2 + +`), + wantMarkdown: pointer.Pointer(` +| ID | POWER | ALLOCATED | | LOCK REASON | LAST EVENT | WHEN | ISSUES | +|----|------------|-----------|--|-------------|-------------|------|-------------------------------------| +| 1 | ●  (16.0W) | yes | | state | Phoned Home | 7d | this is a test issue 1 (issue-1-id) | +| | | | | | | | this is a test issue 2 (issue-2-id) | `), }, } diff --git a/cmd/network_test.go b/cmd/network_test.go index 3c6a9c1d..973e5a35 100644 --- a/cmd/network_test.go +++ b/cmd/network_test.go @@ -118,17 +118,17 @@ func Test_NetworkCmd_MultiResult(t *testing.T) { }, wantTable: pointer.Pointer(` ID NAME PROJECT PARTITION NAT SHARED PREFIXES IPS -nw1 network-1 partition-1 true false prefix ●  ● -└─╴child1 network-1 project-1 partition-1 true false prefix ●  ● -nw2 network-2 project-1 partition-1 false false prefix  ● +nw1 network-1 partition-1 true false prefix ● ● +└─╴child1 network-1 project-1 partition-1 true false prefix ● ● +nw2 network-2 project-1 partition-1 false false prefix ● `), wantWideTable: pointer.Pointer(` ID DESCRIPTION NAME PROJECT PARTITION NAT SHARED PREFIXES USAGE PRIVATESUPER ANNOTATIONS -nw1 network 1 network-1 partition-1 true false prefix IPs: 300/100 true a=b +nw1 network 1 network-1 partition-1 true false prefix IPs: 300/100 true a=b Prefixes:400/200 -└─╴child1 child 1 network-1 project-1 partition-1 true false prefix IPs: 300/100 false e=f +└─╴child1 child 1 network-1 project-1 partition-1 true false prefix IPs: 300/100 false e=f Prefixes:400/200 -nw2 network 2 network-2 project-1 partition-1 false false prefix IPs: 200/400 false c=d +nw2 network 2 network-2 project-1 partition-1 false false prefix IPs: 200/400 false c=d Prefixes:100/300 `), template: pointer.Pointer("{{ .id }} {{ .name }}"), @@ -140,9 +140,9 @@ nw2 network-2 wantMarkdown: pointer.Pointer(` | ID | NAME | PROJECT | PARTITION | NAT | SHARED | PREFIXES | | IPS | |-----------|-----------|-----------|-------------|-------|--------|----------|---|-----| -| nw1 | network-1 | | partition-1 | true | false | prefix | ● |  ● | -| └─╴child1 | network-1 | project-1 | partition-1 | true | false | prefix | ● |  ● | -| nw2 | network-2 | project-1 | partition-1 | false | false | prefix | |  ● | +| nw1 | network-1 | | partition-1 | true | false | prefix | ● | ● | +| └─╴child1 | network-1 | project-1 | partition-1 | true | false | prefix | ● | ● | +| nw2 | network-2 | project-1 | partition-1 | false | false | prefix | | ● | `), }, { @@ -253,7 +253,7 @@ nw1 network-1 partition-1 true false prefix ●   `), wantWideTable: pointer.Pointer(` ID DESCRIPTION NAME PROJECT PARTITION NAT SHARED PREFIXES USAGE PRIVATESUPER ANNOTATIONS -nw1 network 1 network-1 partition-1 true false prefix IPs: 300/100 true a=b +nw1 network 1 network-1 partition-1 true false prefix IPs: 300/100 true a=b Prefixes:400/200 `), template: pointer.Pointer("{{ .id }} {{ .name }}"), @@ -263,7 +263,7 @@ nw1 network-1 wantMarkdown: pointer.Pointer(` | ID | NAME | PROJECT | PARTITION | NAT | SHARED | PREFIXES | | IPS | |-----|-----------|---------|-------------|------|--------|----------|---|-----| -| nw1 | network-1 | | partition-1 | true | false | prefix | ● |  ● | +| nw1 | network-1 | | partition-1 | true | false | prefix | ● | ● | `), }, { diff --git a/cmd/printers.go b/cmd/printers.go index af45ae26..7bfe5d35 100644 --- a/cmd/printers.go +++ b/cmd/printers.go @@ -20,15 +20,17 @@ func newPrinterFromCLI(out io.Writer) printers.Printer { printer = printers.NewJSONPrinter().WithOut(out) case "table", "wide", "markdown": tp := tableprinters.New() - cfg := &printers.TablePrinterConfig{ + + tablePrinter := printers.NewTablePrinter(&printers.TablePrinterConfig{ ToHeaderAndRows: tp.ToHeaderAndRows, Wide: format == "wide", Markdown: format == "markdown", NoHeaders: viper.GetBool("no-headers"), - } - tablePrinter := printers.NewTablePrinter(cfg).WithOut(out) + }).WithOut(out) + tp.SetPrinter(tablePrinter) tp.SetLastEventErrorThreshold(viper.GetDuration("last-event-error-threshold")) + printer = tablePrinter case "template": printer = printers.NewTemplatePrinter(viper.GetString("template")).WithOut(out) diff --git a/cmd/sorters/machine.go b/cmd/sorters/machine.go index 3dd865a7..71b58b0c 100644 --- a/cmd/sorters/machine.go +++ b/cmd/sorters/machine.go @@ -114,3 +114,14 @@ func MachineIPMISorter() *multisort.Sorter[*models.V1MachineIPMIResponse] { }, }, multisort.Keys{{ID: "partition"}, {ID: "rack"}, {ID: "size"}, {ID: "bios"}, {ID: "bmc"}, {ID: "id"}}) } + +func MachineIssueSorter() *multisort.Sorter[*models.V1MachineIssue] { + return multisort.New(multisort.FieldMap[*models.V1MachineIssue]{ + "id": func(a, b *models.V1MachineIssue, descending bool) multisort.CompareResult { + return multisort.Compare(p.SafeDeref(a.ID), p.SafeDeref(b.ID), descending) + }, + "severity": func(a, b *models.V1MachineIssue, descending bool) multisort.CompareResult { + return multisort.Compare(p.SafeDeref(a.Severity), p.SafeDeref(b.Severity), descending) + }, + }, multisort.Keys{{ID: "severity"}, {ID: "id"}}) +} diff --git a/cmd/tableprinters/machine.go b/cmd/tableprinters/machine.go index beca43da..6b505a37 100644 --- a/cmd/tableprinters/machine.go +++ b/cmd/tableprinters/machine.go @@ -13,6 +13,13 @@ import ( "github.com/olekukonko/tablewriter" ) +// MachinesAndIssues is used for combining issues with more data on machines. +type MachinesAndIssues struct { + EvaluationResult []*models.V1MachineIssueResponse `json:"evaluation_result" yaml:"evaluation_result"` + Machines []*models.V1MachineIPMIResponse `json:"machines" yaml:"machines"` + Issues []*models.V1MachineIssue `json:"issues" yaml:"issues"` +} + func (t *TablePrinter) MachineTable(data []*models.V1MachineResponse, wide bool) ([]string, [][]string, error) { var ( rows [][]string @@ -257,7 +264,25 @@ func (t *TablePrinter) MachineLogsTable(data []*models.V1MachineProvisioningEven return header, rows, nil } -func (t *TablePrinter) MachineIssuesTable(data api.MachineIssues, wide bool) ([]string, [][]string, error) { +func (t *TablePrinter) MachineIssuesListTable(data []*models.V1MachineIssue, wide bool) ([]string, [][]string, error) { + var ( + header = []string{"ID", "Severity", "Description", "Reference URL"} + rows [][]string + ) + + for _, issue := range data { + rows = append(rows, []string{ + pointer.SafeDeref(issue.ID), + pointer.SafeDeref(issue.Severity), + pointer.SafeDeref(issue.Description), + pointer.SafeDeref(issue.RefURL), + }) + } + + return header, rows, nil +} + +func (t *TablePrinter) MachineIssuesTable(data *MachinesAndIssues, wide bool) ([]string, [][]string, error) { var ( rows [][]string ) @@ -267,8 +292,34 @@ func (t *TablePrinter) MachineIssuesTable(data api.MachineIssues, wide bool) ([] header = []string{"ID", "Name", "Partition", "Project", "Power", "State", "Lock Reason", "Last Event", "When", "Issues", "Ref URL", "Details"} } - for _, machineWithIssues := range data { - machine := machineWithIssues.Machine + machinesByID := map[string]*models.V1MachineIPMIResponse{} + for _, m := range data.Machines { + m := m + + if m.ID == nil { + continue + } + + machinesByID[*m.ID] = m + } + + issuesByID := map[string]*models.V1MachineIssue{} + for _, issue := range data.Issues { + issue := issue + + if issue.ID == nil { + continue + } + + issuesByID[*issue.ID] = issue + } + + for _, issue := range data.EvaluationResult { + if issue.Machineid == nil { + continue + } + + machine := machinesByID[*issue.Machineid] widename := "" if machine.Allocation != nil && machine.Allocation.Name != nil { @@ -311,10 +362,24 @@ func (t *TablePrinter) MachineIssuesTable(data api.MachineIssues, wide bool) ([] emojis, _ := t.getMachineStatusEmojis(machine.Liveliness, machine.Events, machine.State, nil) - for _, issue := range machineWithIssues.Issues { - text := fmt.Sprintf("%s (%s)", issue.Description, issue.Type) - ref := issue.RefURL - details := issue.Details + for i, id := range issue.Issues { + iss, ok := issuesByID[id] + if !ok { + continue + } + + text := fmt.Sprintf("%s (%s)", pointer.SafeDeref(iss.Description), pointer.SafeDeref(iss.ID)) + ref := pointer.SafeDeref(iss.RefURL) + details := pointer.SafeDeref(iss.Details) + + if i != 0 { + if wide { + rows = append(rows, []string{"", "", "", "", "", "", "", "", "", text, ref, details}) + } else { + rows = append(rows, []string{"", "", "", "", "", "", "", text}) + } + continue + } if wide { rows = append(rows, []string{pointer.SafeDeref(machine.ID), widename, partition, project, powerText, lockText, lockDescWide, lastEvent, when, text, ref, details}) diff --git a/cmd/tableprinters/printer.go b/cmd/tableprinters/printer.go index a6f11301..c2358bf9 100644 --- a/cmd/tableprinters/printer.go +++ b/cmd/tableprinters/printer.go @@ -37,8 +37,12 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin return t.MachineTable(d, wide) case *models.V1MachineResponse: return t.MachineTable(pointer.WrapInSlice(d), wide) - case api.MachineIssues: + case *MachinesAndIssues: return t.MachineIssuesTable(d, wide) + case []*models.V1MachineIssue: + return t.MachineIssuesListTable(d, wide) + case *models.V1MachineIssue: + return t.MachineIssuesListTable(pointer.WrapInSlice(d), wide) case []*models.V1FirewallResponse: return t.FirewallTable(d, wide) case *models.V1FirewallResponse: diff --git a/docs/metalctl_firewall_list.md b/docs/metalctl_firewall_list.md index 0236dc0b..56290c8c 100644 --- a/docs/metalctl_firewall_list.md +++ b/docs/metalctl_firewall_list.md @@ -13,7 +13,7 @@ metalctl firewall list [flags] --hostname string allocation hostname to filter [optional] --id string ID to filter [optional] --image string allocation image to filter [optional] - --mac strings mac to filter [optional] + --mac string mac to filter [optional] --name string allocation name to filter [optional] --partition string partition to filter [optional] --project string allocation project to filter [optional] diff --git a/docs/metalctl_machine_ipmi.md b/docs/metalctl_machine_ipmi.md index 17c75cf8..d8d176ae 100644 --- a/docs/metalctl_machine_ipmi.md +++ b/docs/metalctl_machine_ipmi.md @@ -15,7 +15,7 @@ Meaning of the emojis: ❓ 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. +🛡 Machine is connected to our VPN, ssh access only possible via this VPN. ``` @@ -25,16 +25,26 @@ metalctl machine ipmi [] [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 ipmi --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] diff --git a/docs/metalctl_machine_issues.md b/docs/metalctl_machine_issues.md index 1fb18b4a..2503a108 100644 --- a/docs/metalctl_machine_issues.md +++ b/docs/metalctl_machine_issues.md @@ -15,7 +15,7 @@ Meaning of the emojis: ❓ 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. +🛡 Machine is connected to our VPN, ssh access only possible via this VPN. ``` @@ -25,18 +25,28 @@ metalctl machine issues [] [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 issues --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 168h0m0s) + --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] --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] --omit strings issue types to omit [optional] --only strings issue types to include [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] --severity string issue severity to include [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 @@ -75,4 +85,5 @@ metalctl machine issues [] [flags] ### SEE ALSO * [metalctl machine](metalctl_machine.md) - manage machine entities +* [metalctl machine issues list](metalctl_machine_issues_list.md) - list all machine issues that the metal-api can evaluate diff --git a/docs/metalctl_machine_issues_list.md b/docs/metalctl_machine_issues_list.md new file mode 100644 index 00000000..5b643916 --- /dev/null +++ b/docs/metalctl_machine_issues_list.md @@ -0,0 +1,47 @@ +## metalctl machine issues list + +list all machine issues that the metal-api can evaluate + +``` +metalctl machine issues list [flags] +``` + +### Options + +``` + -h, --help help for list + --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: id|severity +``` + +### 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 issues](metalctl_machine_issues.md) - display machines which are in a potential bad state + diff --git a/docs/metalctl_machine_list.md b/docs/metalctl_machine_list.md index eb6e2bac..7082e4fa 100644 --- a/docs/metalctl_machine_list.md +++ b/docs/metalctl_machine_list.md @@ -15,7 +15,7 @@ Meaning of the emojis: ❓ 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. +🛡 Machine is connected to our VPN, ssh access only possible via this VPN. ``` @@ -25,16 +25,26 @@ metalctl machine 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 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|event|id|image|liveliness|partition|project|rack|size|when --state string state to filter [optional] diff --git a/go.mod b/go.mod index e9a25014..3173d04c 100644 --- a/go.mod +++ b/go.mod @@ -8,15 +8,15 @@ require ( github.com/go-openapi/runtime v0.26.0 github.com/go-openapi/strfmt v0.21.7 github.com/google/go-cmp v0.5.9 - github.com/metal-stack/metal-go v0.24.1 + github.com/metal-stack/metal-go v0.24.2 github.com/metal-stack/metal-lib v0.13.3 github.com/metal-stack/updater v1.1.5 github.com/metal-stack/v v1.0.3 - github.com/olekukonko/tablewriter v0.0.5 + github.com/olekukonko/tablewriter v0.0.6-0.20230925090304-df64c4bbad77 github.com/spf13/afero v1.10.0 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.16.0 + github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 github.com/undefinedlabs/go-mpatch v1.0.7 gopkg.in/yaml.v3 v3.0.1 @@ -54,7 +54,7 @@ require ( github.com/coreos/go-iptables v0.6.0 // indirect github.com/coreos/go-oidc/v3 v3.6.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dblohm7/wingoes v0.0.0-20230803162905-5c6286bb8c6e // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/emicklei/go-restful-openapi/v2 v2.9.1 // indirect @@ -99,7 +99,7 @@ require ( github.com/jsimonetti/rtnetlink v1.3.4 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/jszwec/csvutil v1.8.0 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.0 // indirect github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.1 // indirect @@ -126,17 +126,19 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pelletier/go-toml/v2 v2.0.9 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/subosito/gotenv v1.4.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d // indirect github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e // indirect github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect @@ -154,17 +156,17 @@ require ( go.uber.org/zap v1.25.0 // indirect go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/oauth2 v0.11.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/oauth2 v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/term v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/term v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.12.0 // indirect + golang.org/x/tools v0.13.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect diff --git a/go.sum b/go.sum index 70f0516a..3912f6ab 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,9 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dblohm7/wingoes v0.0.0-20230803162905-5c6286bb8c6e h1:tTRuQNnXKO6Ffu62nk9bnnPx/m+IyNMdFFfzsETyRO8= github.com/dblohm7/wingoes v0.0.0-20230803162905-5c6286bb8c6e/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= @@ -384,8 +385,8 @@ github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0Lh github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= @@ -430,7 +431,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= @@ -441,8 +442,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/metal-stack/metal-go v0.24.1 h1:S4g3LXeWBELCKIyyCsRMa9B1pC5EClqcX35oV/8xv2U= -github.com/metal-stack/metal-go v0.24.1/go.mod h1:jNJ0dWIBRwKeJoP+RGqTyE5qLsdZFISFrNHU5m3IDwA= +github.com/metal-stack/metal-go v0.24.2 h1:p+TcZ7xFOTMYmDI5KSTACUET0D/dx1pGvj/W68Lworg= +github.com/metal-stack/metal-go v0.24.2/go.mod h1:jNJ0dWIBRwKeJoP+RGqTyE5qLsdZFISFrNHU5m3IDwA= github.com/metal-stack/metal-lib v0.13.3 h1:BOhwcKHILmBZd2pz2YMOhj8QxzDaz3G0F/CGuYhnu8o= github.com/metal-stack/metal-lib v0.13.3/go.mod h1:BAR7fjdoV7DDg8i9GpJQBDaNSFirOcBs0vLYTBnhHQU= github.com/metal-stack/security v0.6.7 h1:8wstGy0pdUmphVclAlT+9RKQmx9lF+cIGklJZAB5cIc= @@ -475,13 +476,13 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olekukonko/tablewriter v0.0.6-0.20230925090304-df64c4bbad77 h1:3bMMZ1f+GPXFQ1uNaYbO/uECWvSfqEA+ZEXn1rFAT88= +github.com/olekukonko/tablewriter v0.0.6-0.20230925090304-df64c4bbad77/go.mod h1:8Hf+pH6thup1sPZPD+NLg7d6vbpsdilu9CPIeikvgMQ= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= -github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -492,9 +493,11 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pkg/sftp v1.13.5 h1:a3RLUqkyjYRtBTZJZ1VRrKbN3zhuPLlUc3sphVz81go= github.com/pkg/sftp v1.13.5/go.mod h1:wHDZ0IZX6JcBYRK1TH9bcVq8G7TLpVHYIGJRFnmPfxg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -505,12 +508,18 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -519,13 +528,11 @@ github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -542,8 +549,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d h1:K3j02b5j2Iw1xoggN9B2DIEkhWGheqFOeDkdJdBrJI8= github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d/go.mod h1:2P+hpOwd53e7JMX/L4f3VXkv1G+33ES6IWZSrkIeWNs= github.com/tailscale/golang-x-crypto v0.0.0-20230713185742-f0b76a10a08e h1:JyeJF/HuSwvxWtsR1c0oKX1lzaSH5Wh4aX+MgiStaGQ= @@ -631,8 +638,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -643,8 +650,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA= -golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 h1:w/MOPdQ1IoYoDou3L55ZbTx2Nhn7JAhX1BBZor8qChU= golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= @@ -712,8 +719,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -723,8 +730,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -798,16 +805,16 @@ golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepC golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -822,8 +829,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -882,8 +889,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/api/emojis.go b/pkg/api/emojis.go index 9e6b697c..ea5b3ff3 100644 --- a/pkg/api/emojis.go +++ b/pkg/api/emojis.go @@ -8,7 +8,7 @@ const ( Lock = "🔒" Question = "❓" Skull = "💀" - VPN = "🛡️ " + VPN = "🛡" ) func EmojiHelpText() string { @@ -22,6 +22,6 @@ Meaning of the emojis: ❓ 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. +🛡 Machine is connected to our VPN, ssh access only possible via this VPN. ` } diff --git a/pkg/api/issues.go b/pkg/api/issues.go deleted file mode 100644 index c09415cf..00000000 --- a/pkg/api/issues.go +++ /dev/null @@ -1,678 +0,0 @@ -package api - -import ( - "fmt" - "sort" - "strings" - "time" - - "github.com/metal-stack/metal-go/api/models" - "github.com/metal-stack/metal-lib/pkg/pointer" -) - -const ( - // IssueSeverityMinor is an issue that should be checked from time to time but has no bad effects for the user. - IssueSeverityMinor IssueSeverity = "minor" - // IssueSeverityMajor is an issue where user experience is affected or provider resources are wasted. - // overall functionality is still maintained though. major issues should be resolved as soon as possible. - IssueSeverityMajor IssueSeverity = "major" - // IssueSeverityCritical is an issue that can lead to disfunction of the system and need to be handled as quickly as possible. - IssueSeverityCritical IssueSeverity = "critical" - - IssueTypeNoPartition IssueType = "no-partition" - IssueTypeLivelinessDead IssueType = "liveliness-dead" - IssueTypeLivelinessUnknown IssueType = "liveliness-unknown" - IssueTypeLivelinessNotAvailable IssueType = "liveliness-not-available" - IssueTypeFailedMachineReclaim IssueType = "failed-machine-reclaim" - IssueTypeCrashLoop IssueType = "crashloop" - IssueTypeLastEventError IssueType = "last-event-error" - IssueTypeBMCWithoutMAC IssueType = "bmc-without-mac" - IssueTypeBMCWithoutIP IssueType = "bmc-without-ip" - IssueTypeBMCInfoOutdated IssueType = "bmc-info-outdated" - IssueTypeASNUniqueness IssueType = "asn-not-unique" - IssueTypeNonDistinctBMCIP IssueType = "bmc-no-distinct-ip" -) - -type ( - IssueSeverity string - IssueType string - - // Issue formulates an issue of a machine - Issue struct { - Type IssueType - Severity IssueSeverity - Description string - RefURL string - Details string - } - // Issues is a list of issues - Issues []Issue - - // IssueConfig contains configuration parameters for finding machine issues - IssueConfig struct { - Machines []*models.V1MachineIPMIResponse - Severity IssueSeverity - Only []IssueType - Omit []IssueType - LastErrorThreshold time.Duration - } - - IssueNoPartition struct{} - IssueLivelinessDead struct{} - IssueLivelinessUnknown struct{} - IssueLivelinessNotAvailable struct{} - IssueFailedMachineReclaim struct{} - IssueCrashLoop struct{} - IssueLastEventError struct { - details string - } - IssueBMCWithoutMAC struct{} - IssueBMCWithoutIP struct{} - IssueBMCInfoOutdated struct { - details string - } - IssueASNUniqueness struct { - details string - } - IssueNonDistinctBMCIP struct { - details string - } - - // MachineWithIssues summarizes a machine with issues - MachineWithIssues struct { - Machine *models.V1MachineIPMIResponse - Issues Issues - } - // MachineIssues is map of a machine response to a list of machine issues - MachineIssues []*MachineWithIssues - - issue interface { - // Evaluate decides whether a given machine has the machine issue. - // the second argument contains additional information that may be required for the issue evaluation - Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool - // Spec returns the issue spec of this issue. - Spec() *issueSpec - // Details returns additional information on the issue after the evaluation. - Details() string - } - - issueSpec struct { - Type IssueType - Severity IssueSeverity - Description string - RefURL string - } - - machineIssueMap map[*models.V1MachineIPMIResponse]Issues -) - -func DefaultLastErrorThreshold() time.Duration { - return 7 * 24 * time.Hour -} - -func AllIssueTypes() []IssueType { - return []IssueType{ - IssueTypeNoPartition, - IssueTypeLivelinessDead, - IssueTypeLivelinessUnknown, - IssueTypeLivelinessNotAvailable, - IssueTypeFailedMachineReclaim, - IssueTypeCrashLoop, - IssueTypeLastEventError, - IssueTypeBMCWithoutMAC, - IssueTypeBMCWithoutIP, - IssueTypeBMCInfoOutdated, - IssueTypeASNUniqueness, - IssueTypeNonDistinctBMCIP, - } -} - -func AllSevereties() []IssueSeverity { - return []IssueSeverity{ - IssueSeverityMinor, - IssueSeverityMajor, - IssueSeverityCritical, - } -} - -func SeverityFromString(input string) (IssueSeverity, error) { - switch IssueSeverity(input) { - case IssueSeverityCritical: - return IssueSeverityCritical, nil - case IssueSeverityMajor: - return IssueSeverityMajor, nil - case IssueSeverityMinor: - return IssueSeverityMinor, nil - default: - return "", fmt.Errorf("unknown issue severity: %s", input) - } -} - -func (s IssueSeverity) LowerThan(o IssueSeverity) bool { - smap := map[IssueSeverity]int{ - IssueSeverityCritical: 10, - IssueSeverityMajor: 5, - IssueSeverityMinor: 0, - } - - return smap[s] < smap[o] -} - -func AllIssues() Issues { - var res Issues - - for _, t := range AllIssueTypes() { - i, err := newIssueFromType(t) - if err != nil { - continue - } - - res = append(res, toIssue(i)) - } - - return res -} - -func toIssue(i issue) Issue { - return Issue{ - Type: i.Spec().Type, - Severity: i.Spec().Severity, - Description: i.Spec().Description, - RefURL: i.Spec().RefURL, - Details: i.Details(), - } -} - -func (mis MachineIssues) Get(id string) *MachineWithIssues { - for _, m := range mis { - m := m - - if m.Machine == nil || m.Machine.ID == nil { - continue - } - - if *m.Machine.ID == id { - return m - } - } - - return nil -} - -func FindIssues(c *IssueConfig) (MachineIssues, error) { - res := machineIssueMap{} - - for _, t := range AllIssueTypes() { - if !c.includeIssue(t) { - continue - } - - for _, m := range c.Machines { - m := m - - i, err := newIssueFromType(t) - if err != nil { - return nil, err - } - - if m.ID == nil { - continue - } - - if i.Evaluate(m, c) { - res.add(m, toIssue(i)) - } - } - } - - return res.toList(), nil -} - -func (c *IssueConfig) includeIssue(t IssueType) bool { - issue, err := newIssueFromType(t) - if err != nil { - return false - } - - if issue.Spec().Severity.LowerThan(c.Severity) { - return false - } - - for _, o := range c.Omit { - if t == o { - return false - } - } - - if len(c.Only) > 0 { - for _, o := range c.Only { - if t == o { - return true - } - } - return false - } - - return true -} - -func newIssueFromType(t IssueType) (issue, error) { - switch t { - case IssueTypeNoPartition: - return &IssueNoPartition{}, nil - case IssueTypeLivelinessDead: - return &IssueLivelinessDead{}, nil - case IssueTypeLivelinessUnknown: - return &IssueLivelinessUnknown{}, nil - case IssueTypeLivelinessNotAvailable: - return &IssueLivelinessNotAvailable{}, nil - case IssueTypeFailedMachineReclaim: - return &IssueFailedMachineReclaim{}, nil - case IssueTypeCrashLoop: - return &IssueCrashLoop{}, nil - case IssueTypeLastEventError: - return &IssueLastEventError{}, nil - case IssueTypeBMCWithoutMAC: - return &IssueBMCWithoutMAC{}, nil - case IssueTypeBMCWithoutIP: - return &IssueBMCWithoutIP{}, nil - case IssueTypeBMCInfoOutdated: - return &IssueBMCInfoOutdated{}, nil - case IssueTypeASNUniqueness: - return &IssueASNUniqueness{}, nil - case IssueTypeNonDistinctBMCIP: - return &IssueNonDistinctBMCIP{}, nil - default: - return nil, fmt.Errorf("unknown issue type: %s", t) - } -} - -func (mim machineIssueMap) add(m *models.V1MachineIPMIResponse, issue Issue) { - issues, ok := mim[m] - if !ok { - issues = Issues{} - } - issues = append(issues, issue) - mim[m] = issues -} - -func (mim machineIssueMap) toList() MachineIssues { - var res MachineIssues - - for m, issues := range mim { - res = append(res, &MachineWithIssues{ - Machine: m, - Issues: issues, - }) - } - - sort.Slice(res, func(i, j int) bool { - return pointer.SafeDeref(res[i].Machine.ID) < pointer.SafeDeref(res[j].Machine.ID) - }) - - return res -} - -func (i *IssueNoPartition) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeNoPartition, - Severity: IssueSeverityMajor, - Description: "machine with no partition", - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#no-partition", - } -} - -func (i *IssueNoPartition) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - return m.Partition == nil -} - -func (i *IssueNoPartition) Details() string { - return "" -} - -func (i *IssueLivelinessDead) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeLivelinessDead, - Severity: IssueSeverityMajor, - Description: "the machine is not sending events anymore", - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#liveliness-dead", - } -} - -func (i *IssueLivelinessDead) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - return m.Liveliness != nil && *m.Liveliness == "Dead" -} - -func (i *IssueLivelinessDead) Details() string { - return "" -} - -func (i *IssueLivelinessUnknown) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeLivelinessUnknown, - Severity: IssueSeverityMajor, - Description: "the machine is not sending LLDP alive messages anymore", - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#liveliness-unknown", - } -} - -func (i *IssueLivelinessUnknown) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - return m.Liveliness != nil && *m.Liveliness == "Unknown" -} - -func (i *IssueLivelinessUnknown) Details() string { - return "" -} - -func (i *IssueLivelinessNotAvailable) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeLivelinessNotAvailable, - Severity: IssueSeverityMinor, - Description: "the machine liveliness is not available", - } -} -func (i *IssueLivelinessNotAvailable) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - if m.Liveliness == nil { - return true - } - - allowed := map[string]bool{ - "Alive": true, - "Dead": true, - "Unknown": true, - } - - return !allowed[*m.Liveliness] -} - -func (i *IssueLivelinessNotAvailable) Details() string { - return "" -} - -func (i *IssueFailedMachineReclaim) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeFailedMachineReclaim, - Severity: IssueSeverityCritical, - Description: "machine phones home but not allocated", - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#failed-machine-reclaim", - } -} - -func (i *IssueFailedMachineReclaim) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - if pointer.SafeDeref(pointer.SafeDeref(m.Events).FailedMachineReclaim) { - return true - } - - // compatibility: before the provisioning FSM was renewed, this state could be detected the following way - // we should keep this condition - if m.Allocation == nil && pointer.SafeDeref(pointer.SafeDeref(pointer.FirstOrZero(pointer.SafeDeref(m.Events).Log)).Event) == "Phoned Home" { - return true - } - - return false -} - -func (i *IssueFailedMachineReclaim) Details() string { - return "" -} - -func (i *IssueCrashLoop) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeCrashLoop, - Severity: IssueSeverityMajor, - Description: fmt.Sprintf("machine is in a provisioning crash loop (%s)", Loop), - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#crashloop", - } -} - -func (i *IssueCrashLoop) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - if pointer.SafeDeref(pointer.SafeDeref(m.Events).CrashLoop) { - if m.Events != nil && len(m.Events.Log) > 0 && *m.Events.Log[0].Event == "Waiting" { - // Machine which are waiting are not considered to have issues - } else { - return true - } - } - return false -} - -func (i *IssueCrashLoop) Details() string { - return "" -} - -func (i *IssueLastEventError) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeLastEventError, - Severity: IssueSeverityMinor, - Description: "the machine had an error during the provisioning lifecycle", - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#last-event-error", - } -} - -func (i *IssueLastEventError) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - if c.LastErrorThreshold == 0 { - return false - } - - if pointer.SafeDeref(m.Events).LastErrorEvent != nil { - timeSince := time.Since(time.Time(m.Events.LastErrorEvent.Time)) - if timeSince < c.LastErrorThreshold { - i.details = fmt.Sprintf("occurred %s ago", timeSince.String()) - return true - } - } - - return false -} - -func (i *IssueLastEventError) Details() string { - return i.details -} - -func (i *IssueBMCWithoutMAC) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeBMCWithoutMAC, - Severity: IssueSeverityMajor, - Description: "BMC has no mac address", - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#bmc-without-mac", - } -} - -func (i *IssueBMCWithoutMAC) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - return m.Ipmi != nil && (m.Ipmi.Mac == nil || *m.Ipmi.Mac == "") -} - -func (i *IssueBMCWithoutMAC) Details() string { - return "" -} - -func (i *IssueBMCWithoutIP) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeBMCWithoutIP, - Severity: IssueSeverityMajor, - Description: "BMC has no ip address", - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#bmc-without-ip", - } -} - -func (i *IssueBMCWithoutIP) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - return m.Ipmi != nil && (m.Ipmi.Address == nil || *m.Ipmi.Address == "") -} - -func (i *IssueBMCWithoutIP) Details() string { - return "" -} - -func (i *IssueBMCInfoOutdated) Details() string { - return i.details -} - -func (i *IssueBMCInfoOutdated) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - if m.Ipmi == nil { - i.details = "machine ipmi has never been set" - return true - } - - if m.Ipmi.LastUpdated == nil || m.Ipmi.LastUpdated.IsZero() { - // "last_updated has not been set yet" - return false - } - - lastUpdated := time.Since(time.Time(*m.Ipmi.LastUpdated)) - - if lastUpdated > 20*time.Minute { - i.details = fmt.Sprintf("last updated %s ago", lastUpdated.String()) - return true - } - - return false -} - -func (*IssueBMCInfoOutdated) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeBMCInfoOutdated, - Severity: IssueSeverityMajor, - Description: "BMC has not been updated from either metal-hammer or metal-bmc", - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#bmc-info-outdated", - } -} - -func (i *IssueASNUniqueness) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeASNUniqueness, - Severity: IssueSeverityMinor, - Description: "The ASN is not unique (only impact on firewalls)", - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#asn-not-unique", - } -} - -func (i *IssueASNUniqueness) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - var ( - machineASNs = map[int64][]*models.V1MachineIPMIResponse{} - overlaps []string - isNoFirewall = func(m *models.V1MachineIPMIResponse) bool { - return m.Allocation == nil || m.Allocation.Role == nil || *m.Allocation.Role != models.V1MachineAllocationRoleFirewall - } - ) - - if isNoFirewall(m) { - return false - } - - for _, n := range m.Allocation.Networks { - if n.Asn == nil { - continue - } - - machineASNs[*n.Asn] = nil - } - - for _, machineFromAll := range c.Machines { - if pointer.SafeDeref(machineFromAll.ID) == pointer.SafeDeref(m.ID) { - continue - } - otherMachine := machineFromAll - - if isNoFirewall(otherMachine) { - continue - } - - for _, n := range otherMachine.Allocation.Networks { - if n.Asn == nil { - continue - } - - _, ok := machineASNs[*n.Asn] - if !ok { - continue - } - - machineASNs[*n.Asn] = append(machineASNs[*n.Asn], otherMachine) - } - } - - var asnList []int64 - for asn := range machineASNs { - asnList = append(asnList, asn) - } - sort.Slice(asnList, func(i, j int) bool { - return asnList[i] < asnList[j] - }) - - for _, asn := range asnList { - overlappingMachines, ok := machineASNs[asn] - if !ok || len(overlappingMachines) == 0 { - continue - } - - var sharedIDs []string - for _, m := range overlappingMachines { - m := m - sharedIDs = append(sharedIDs, *m.ID) - } - - overlaps = append(overlaps, fmt.Sprintf("- ASN (%d) not unique, shared with %s", asn, sharedIDs)) - } - - if len(overlaps) == 0 { - return false - } - - sort.Slice(overlaps, func(i, j int) bool { - return overlaps[i] < overlaps[j] - }) - - i.details = strings.Join(overlaps, "\n") - - return true -} - -func (i *IssueASNUniqueness) Details() string { - return i.details -} - -func (i *IssueNonDistinctBMCIP) Spec() *issueSpec { - return &issueSpec{ - Type: IssueTypeNonDistinctBMCIP, - Description: "BMC IP address is not distinct", - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#bmc-no-distinct-ip", - } -} - -func (i *IssueNonDistinctBMCIP) Evaluate(m *models.V1MachineIPMIResponse, c *IssueConfig) bool { - if m.Ipmi == nil || m.Ipmi.Address == nil { - return false - } - - var ( - bmcIP = *m.Ipmi.Address - overlaps []string - ) - - for _, machineFromAll := range c.Machines { - if pointer.SafeDeref(machineFromAll.ID) == pointer.SafeDeref(m.ID) { - continue - } - otherMachine := machineFromAll - - if otherMachine.Ipmi == nil || otherMachine.Ipmi.Address == nil { - continue - } - - if bmcIP == *otherMachine.Ipmi.Address { - overlaps = append(overlaps, *otherMachine.ID) - } - } - - if len(overlaps) == 0 { - return false - } - - i.details = fmt.Sprintf("BMC IP (%s) not unique, shared with %s", bmcIP, overlaps) - - return true -} - -func (i *IssueNonDistinctBMCIP) Details() string { - return i.details -} diff --git a/pkg/api/issues_test.go b/pkg/api/issues_test.go deleted file mode 100644 index 6953a2ed..00000000 --- a/pkg/api/issues_test.go +++ /dev/null @@ -1,471 +0,0 @@ -package api - -import ( - "fmt" - "testing" - "time" - - "github.com/go-openapi/strfmt" - "github.com/google/go-cmp/cmp" - "github.com/metal-stack/metal-go/api/models" - "github.com/metal-stack/metal-lib/pkg/pointer" - "github.com/stretchr/testify/require" - "github.com/undefinedlabs/go-mpatch" -) - -var ( - testTime = time.Date(2022, time.May, 19, 1, 2, 3, 4, time.UTC) -) - -func init() { - _, err := mpatch.PatchMethod(time.Now, func() time.Time { return testTime }) - if err != nil { - panic(err) - } -} - -func TestFindIssues(t *testing.T) { - goodMachine := func(id string) *models.V1MachineIPMIResponse { - return &models.V1MachineIPMIResponse{ - ID: pointer.Pointer(id), - Liveliness: pointer.Pointer("Alive"), - Partition: &models.V1PartitionResponse{ID: pointer.Pointer("a")}, - Ipmi: &models.V1MachineIPMI{ - Address: pointer.Pointer("1.2.3.4"), - Mac: pointer.Pointer("aa:bb:00"), - LastUpdated: pointer.Pointer(strfmt.DateTime(testTime.Add(-1 * time.Minute))), - }, - } - } - - tests := []struct { - name string - only []IssueType - machines func() []*models.V1MachineIPMIResponse - want func(machines []*models.V1MachineIPMIResponse) MachineIssues - }{ - { - name: "good machine has no issues", - machines: func() []*models.V1MachineIPMIResponse { - return []*models.V1MachineIPMIResponse{ - goodMachine("0"), - } - }, - want: nil, - }, - { - name: "no partition", - only: []IssueType{IssueTypeNoPartition}, - machines: func() []*models.V1MachineIPMIResponse { - noPartitionMachine := goodMachine("no-partition") - noPartitionMachine.ID = pointer.Pointer("no-partition") - noPartitionMachine.Partition = nil - - return []*models.V1MachineIPMIResponse{ - noPartitionMachine, - goodMachine("0"), - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[0], - Issues: Issues{ - toIssue(&IssueNoPartition{}), - }, - }, - } - }, - }, - { - name: "liveliness dead", - only: []IssueType{IssueTypeLivelinessDead}, - machines: func() []*models.V1MachineIPMIResponse { - deadMachine := goodMachine("dead") - deadMachine.Liveliness = pointer.Pointer("Dead") - - return []*models.V1MachineIPMIResponse{ - deadMachine, - goodMachine("0"), - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[0], - Issues: Issues{ - toIssue(&IssueLivelinessDead{}), - }, - }, - } - }, - }, - { - name: "liveliness unknown", - only: []IssueType{IssueTypeLivelinessUnknown}, - machines: func() []*models.V1MachineIPMIResponse { - unknownMachine := goodMachine("unknown") - unknownMachine.Liveliness = pointer.Pointer("Unknown") - - return []*models.V1MachineIPMIResponse{ - unknownMachine, - goodMachine("0"), - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[0], - Issues: Issues{ - toIssue(&IssueLivelinessUnknown{}), - }, - }, - } - }, - }, - { - name: "liveliness not available", - only: []IssueType{IssueTypeLivelinessNotAvailable}, - machines: func() []*models.V1MachineIPMIResponse { - notAvailableMachine1 := goodMachine("na1") - notAvailableMachine1.Liveliness = nil - - notAvailableMachine2 := goodMachine("na2") - notAvailableMachine2.Liveliness = pointer.Pointer("") - - return []*models.V1MachineIPMIResponse{ - notAvailableMachine1, - notAvailableMachine2, - goodMachine("0"), - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[0], - Issues: Issues{ - toIssue(&IssueLivelinessNotAvailable{}), - }, - }, - { - Machine: machines[1], - Issues: Issues{ - toIssue(&IssueLivelinessNotAvailable{}), - }, - }, - } - }, - }, - { - name: "failed machine reclaim, flag is set", - only: []IssueType{IssueTypeFailedMachineReclaim}, - machines: func() []*models.V1MachineIPMIResponse { - failedReclaimMachine := goodMachine("failed") - failedReclaimMachine.Events = &models.V1MachineRecentProvisioningEvents{FailedMachineReclaim: pointer.Pointer(true)} - - return []*models.V1MachineIPMIResponse{ - goodMachine("0"), - failedReclaimMachine, - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[1], - Issues: Issues{ - toIssue(&IssueFailedMachineReclaim{}), - }, - }, - } - }, - }, - { - name: "failed machine reclaim, phoned home and not allocated (old behavior)", - only: []IssueType{IssueTypeFailedMachineReclaim}, - machines: func() []*models.V1MachineIPMIResponse { - failedReclaimMachine := goodMachine("failed") - failedReclaimMachine.Allocation = nil - failedReclaimMachine.Events = &models.V1MachineRecentProvisioningEvents{ - Log: []*models.V1MachineProvisioningEvent{ - { - Event: pointer.Pointer("Phoned Home"), - }, - }, - } - - return []*models.V1MachineIPMIResponse{ - goodMachine("0"), - failedReclaimMachine, - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[1], - Issues: Issues{ - toIssue(&IssueFailedMachineReclaim{}), - }, - }, - } - }, - }, - { - name: "crashloop", - only: []IssueType{IssueTypeCrashLoop}, - machines: func() []*models.V1MachineIPMIResponse { - crashingMachine := goodMachine("crash") - crashingMachine.Events = &models.V1MachineRecentProvisioningEvents{ - CrashLoop: pointer.Pointer(true), - } - - return []*models.V1MachineIPMIResponse{ - goodMachine("0"), - crashingMachine, - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[1], - Issues: Issues{ - toIssue(&IssueCrashLoop{}), - }, - }, - } - }, - }, - { - name: "last event error", - only: []IssueType{IssueTypeLastEventError}, - machines: func() []*models.V1MachineIPMIResponse { - lastEventErrorMachine := goodMachine("last") - lastEventErrorMachine.Events = &models.V1MachineRecentProvisioningEvents{ - LastErrorEvent: &models.V1MachineProvisioningEvent{ - Time: strfmt.DateTime(testTime.Add(-5 * time.Minute)), - }, - } - - return []*models.V1MachineIPMIResponse{ - goodMachine("0"), - lastEventErrorMachine, - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[1], - Issues: Issues{ - toIssue(&IssueLastEventError{details: "occurred 5m0s ago"}), - }, - }, - } - }, - }, - { - name: "bmc without mac", - only: []IssueType{IssueTypeBMCWithoutMAC}, - machines: func() []*models.V1MachineIPMIResponse { - bmcWithoutMacMachine := goodMachine("no-mac") - bmcWithoutMacMachine.Ipmi.Mac = nil - - return []*models.V1MachineIPMIResponse{ - goodMachine("0"), - bmcWithoutMacMachine, - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[1], - Issues: Issues{ - toIssue(&IssueBMCWithoutMAC{}), - }, - }, - } - }, - }, - { - name: "bmc without ip", - only: []IssueType{IssueTypeBMCWithoutIP}, - machines: func() []*models.V1MachineIPMIResponse { - bmcWithoutMacMachine := goodMachine("no-ip") - bmcWithoutMacMachine.Ipmi.Address = nil - - return []*models.V1MachineIPMIResponse{ - goodMachine("0"), - bmcWithoutMacMachine, - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[1], - Issues: Issues{ - toIssue(&IssueBMCWithoutIP{}), - }, - }, - } - }, - }, - { - name: "bmc info outdated", - only: []IssueType{IssueTypeBMCInfoOutdated}, - machines: func() []*models.V1MachineIPMIResponse { - bmcOutdatedMachine := goodMachine("outdated") - bmcOutdatedMachine.Ipmi.LastUpdated = pointer.Pointer(strfmt.DateTime(testTime.Add(-3 * 60 * time.Minute))) - - return []*models.V1MachineIPMIResponse{ - goodMachine("0"), - bmcOutdatedMachine, - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[1], - Issues: Issues{ - toIssue(&IssueBMCInfoOutdated{ - details: "last updated 3h0m0s ago", - }), - }, - }, - } - }, - }, - { - name: "asn shared", - only: []IssueType{IssueTypeASNUniqueness}, - machines: func() []*models.V1MachineIPMIResponse { - asnSharedMachine1 := goodMachine("shared1") - asnSharedMachine1.Allocation = &models.V1MachineAllocation{ - Role: pointer.Pointer(models.V1MachineAllocationRoleFirewall), - Networks: []*models.V1MachineNetwork{ - { - Asn: pointer.Pointer(int64(0)), - }, - { - Asn: pointer.Pointer(int64(100)), - }, - { - Asn: pointer.Pointer(int64(200)), - }, - }, - } - - asnSharedMachine2 := goodMachine("shared2") - asnSharedMachine2.Allocation = &models.V1MachineAllocation{ - Role: pointer.Pointer(models.V1MachineAllocationRoleFirewall), - Networks: []*models.V1MachineNetwork{ - { - Asn: pointer.Pointer(int64(1)), - }, - { - Asn: pointer.Pointer(int64(100)), - }, - { - Asn: pointer.Pointer(int64(200)), - }, - }, - } - - return []*models.V1MachineIPMIResponse{ - asnSharedMachine1, - asnSharedMachine2, - goodMachine("0"), - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[0], - Issues: Issues{ - toIssue(&IssueASNUniqueness{ - details: fmt.Sprintf("- ASN (100) not unique, shared with [%[1]s]\n- ASN (200) not unique, shared with [%[1]s]", *machines[1].ID), - }), - }, - }, - { - Machine: machines[1], - Issues: Issues{ - toIssue(&IssueASNUniqueness{ - details: fmt.Sprintf("- ASN (100) not unique, shared with [%[1]s]\n- ASN (200) not unique, shared with [%[1]s]", *machines[0].ID), - }), - }, - }, - } - }, - }, - { - name: "non distinct bmc ip", - only: []IssueType{IssueTypeNonDistinctBMCIP}, - machines: func() []*models.V1MachineIPMIResponse { - nonDistinctBMCMachine1 := goodMachine("bmc1") - nonDistinctBMCMachine1.Ipmi.Address = pointer.Pointer("127.0.0.1") - - nonDistinctBMCMachine2 := goodMachine("bmc2") - nonDistinctBMCMachine2.Ipmi.Address = pointer.Pointer("127.0.0.1") - - return []*models.V1MachineIPMIResponse{ - nonDistinctBMCMachine1, - nonDistinctBMCMachine2, - goodMachine("0"), - } - }, - want: func(machines []*models.V1MachineIPMIResponse) MachineIssues { - return MachineIssues{ - { - Machine: machines[0], - Issues: Issues{ - toIssue(&IssueNonDistinctBMCIP{ - details: fmt.Sprintf("BMC IP (127.0.0.1) not unique, shared with [%[1]s]", *machines[1].ID), - }), - }, - }, - { - Machine: machines[1], - Issues: Issues{ - toIssue(&IssueNonDistinctBMCIP{ - details: fmt.Sprintf("BMC IP (127.0.0.1) not unique, shared with [%[1]s]", *machines[0].ID), - }), - }, - }, - } - }, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - ms := tt.machines() - - got, err := FindIssues(&IssueConfig{ - Machines: ms, - Only: tt.only, - LastErrorThreshold: DefaultLastErrorThreshold(), - }) - require.NoError(t, err) - - var want MachineIssues - if tt.want != nil { - want = tt.want(ms) - } - - if diff := cmp.Diff(want, got, cmp.AllowUnexported(IssueLastEventError{}, IssueASNUniqueness{}, IssueNonDistinctBMCIP{})); diff != "" { - t.Errorf("diff (+got -want):\n %s", diff) - } - }) - } -} - -func TestAllIssues(t *testing.T) { - issuesTypes := map[IssueType]bool{} - for _, i := range AllIssues() { - issuesTypes[i.Type] = true - } - - for _, ty := range AllIssueTypes() { - if _, ok := issuesTypes[ty]; !ok { - t.Errorf("issue of type %s not contained in all issues", ty) - } - } -}