// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package command import ( "encoding/json" "fmt" "strings" "github.com/hashicorp/nomad/api" "github.com/posener/complete" ) type OperatorAutopilotHealthCommand struct { Meta } func (c *OperatorAutopilotHealthCommand) AutocompleteFlags() complete.Flags { return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient)) } func (c *OperatorAutopilotHealthCommand) AutocompleteArgs() complete.Predictor { return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), complete.Flags{ "-json": complete.PredictNothing, }) } func (c *OperatorAutopilotHealthCommand) Name() string { return "operator autopilot health" } func (c *OperatorAutopilotHealthCommand) Run(args []string) int { var fJson bool flags := c.Meta.FlagSet("autopilot", FlagSetClient) flags.Usage = func() { c.Ui.Output(c.Help()) } flags.BoolVar(&fJson, "json", false, "") if err := flags.Parse(args); err != nil { c.Ui.Error(fmt.Sprintf("Failed to parse args: %v", err)) return 1 } // Set up a client. client, err := c.Meta.Client() if err != nil { c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) return 1 } // Fetch the current configuration. state, _, err := client.Operator().AutopilotServerHealth(nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying Autopilot configuration: %s", err)) return 1 } if fJson { bytes, err := json.Marshal(state) if err != nil { c.Ui.Error(fmt.Sprintf("failed to serialize client state: %v", err)) return 1 } c.Ui.Output(string(bytes)) } else { c.Ui.Output(formatAutopilotState(state)) } return 0 } func (c *OperatorAutopilotHealthCommand) Synopsis() string { return "Display the current Autopilot health" } func (c *OperatorAutopilotHealthCommand) Help() string { helpText := ` Usage: nomad operator autopilot health [options] Displays the current Autopilot state. If ACLs are enabled, this command requires a token with the 'operator:read' capability. General Options: Output Options: -json Output the autopilot health in JSON format. ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) return strings.TrimSpace(helpText) } func formatAutopilotState(state *api.OperatorHealthReply) string { var out string out = fmt.Sprintf("Healthy: %t\n", state.Healthy) out = out + fmt.Sprintf("FailureTolerance: %d\n", state.FailureTolerance) out = out + fmt.Sprintf("Leader: %s\n", state.Leader) out = out + fmt.Sprintf("Voters: \n\t%s\n", renderServerIDList(state.Voters)) out = out + fmt.Sprintf("Servers: \n%s\n", formatServerHealth(state.Servers)) out = formatCommandToEnt(out, state) return out } func formatVoters(voters []string) string { out := make([]string, len(voters)) for i, p := range voters { out[i] = fmt.Sprintf("\t%s", p) } return formatList(out) } func formatServerHealth(servers []api.ServerHealth) string { out := make([]string, len(servers)+1) out[0] = "ID|Name|Address|SerfStatus|Version|Leader|Voter|Healthy|LastContact|LastTerm|LastIndex|StableSince" for i, p := range servers { out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%t|%t|%t|%s|%d|%d|%s", p.ID, p.Name, p.Address, p.SerfStatus, p.Version, p.Leader, p.Voter, p.Healthy, p.LastContact, p.LastTerm, p.LastIndex, p.StableSince, ) } return formatList(out) } func renderServerIDList(ids []string) string { rows := make([]string, len(ids)) for i, id := range ids { rows[i] = fmt.Sprintf("\t%s", id) } return formatList(rows) } func formatCommandToEnt(out string, state *api.OperatorHealthReply) string { if len(state.ReadReplicas) > 0 { out = out + "\nReadReplicas:" out = out + formatList(state.ReadReplicas) } if len(state.RedundancyZones) > 0 { out = out + "\nRedundancyZones:" for _, zone := range state.RedundancyZones { out = out + fmt.Sprintf(" %v", zone) } } if state.Upgrade != nil { out = out + "Upgrade: \n" out = out + fmt.Sprintf(" \tStatus: %v\n", state.Upgrade.Status) out = out + fmt.Sprintf(" \tTargetVersion: %v\n", state.Upgrade.TargetVersion) if len(state.Upgrade.TargetVersionVoters) > 0 { out = out + fmt.Sprintf(" \tTargetVersionVoters: \n\t\t%s\n", renderServerIDList(state.Upgrade.TargetVersionVoters)) } if len(state.Upgrade.TargetVersionNonVoters) > 0 { out = out + fmt.Sprintf(" \tTargetVersionNonVoters: \n\t\t%s\n", renderServerIDList(state.Upgrade.TargetVersionNonVoters)) } if len(state.Upgrade.TargetVersionReadReplicas) > 0 { out = out + fmt.Sprintf(" \tTargetVersionReadReplicas: \n\t\t%s\n", renderServerIDList(state.Upgrade.TargetVersionReadReplicas)) } if len(state.Upgrade.OtherVersionVoters) > 0 { out = out + fmt.Sprintf(" \tOtherVersionVoters: \n\t\t%s\n", renderServerIDList(state.Upgrade.OtherVersionVoters)) } if len(state.Upgrade.OtherVersionNonVoters) > 0 { out = out + fmt.Sprintf(" \tOtherVersionNonVoters: \n\t\t%s\n", renderServerIDList(state.Upgrade.OtherVersionNonVoters)) } if len(state.Upgrade.OtherVersionReadReplicas) > 0 { out = out + fmt.Sprintf(" \tOtherVersionReadReplicas: \n\t\t%s\n", renderServerIDList(state.Upgrade.OtherVersionReadReplicas)) } if len(state.Upgrade.RedundancyZones) > 0 { out = out + " \tRedundancyZones:\n" for _, zone := range state.Upgrade.RedundancyZones { out = out + fmt.Sprintf(" \t\t%v", zone) } } } return out }