Files
nomad/command/operator_autopilot_health.go

189 lines
5.3 KiB
Go

// 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
}